mirror of
https://github.com/irmen/prog8.git
synced 2025-06-14 11:23:37 +00:00
Compare commits
562 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 |
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@ -16,7 +16,7 @@
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="false" project-jdk-name="Kotlin SDK" project-jdk-type="KotlinSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="false" project-jdk-name="Kotlin SDK" project-jdk-type="KotlinSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
@ -4,8 +4,8 @@ sudo: false
|
||||
# dist: xenial
|
||||
|
||||
before_install:
|
||||
- chmod +x gradlew
|
||||
- chmod +x ./gradlew
|
||||
|
||||
script:
|
||||
- gradle test
|
||||
- ./gradlew test
|
||||
|
||||
|
111
README.md
111
README.md
@ -2,61 +2,72 @@
|
||||
[](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/
|
||||
|
||||
|
||||
What does Prog8 provide?
|
||||
------------------------
|
||||
|
||||
- big reduction of source code length over raw assembly
|
||||
- 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
|
||||
- subroutines with an input- and output parameter signature
|
||||
- no stack frame allocations because parameters and local variables are automatically allocated statically
|
||||
- constant folding in expressions and other high-level program optimizations
|
||||
- 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...)
|
||||
- inline assembly allows you to have full control when every cycle or byte matters
|
||||
- 'when' statement to provide a concise jump table alternative to if/elseif chains
|
||||
- many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``, ``sort`` and ``reverse``
|
||||
- structs to group together sets of variables and manipulate them at once
|
||||
- convenience abstractions for low level aspects such as ZeroPage handling, program startup, explicit memory addresses
|
||||
- fast execution speed due to compilation to native assembly code
|
||||
- inline assembly allows you to have full control when every cycle or byte matters
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
--------------------
|
||||
https://prog8.readthedocs.io/
|
||||
- "c64": Commodore-64 (6510 CPU = almost a 6502), the main target.
|
||||
- "cx16": [CommanderX16](https://www.commanderx16.com) (65c02 CPU) .
|
||||
- If you only use standard kernel and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
|
||||
|
||||
Required tools
|
||||
--------------
|
||||
|
||||
|
||||
Additional required tools
|
||||
-------------------------
|
||||
|
||||
[64tass](https://sourceforge.net/projects/tass64/) - cross assembler. Install this on your shell path.
|
||||
A recent .exe version of this tool for Windows can be obtained from my [clone](https://github.com/irmen/64tass/releases) of this project.
|
||||
For other platforms it is very easy to compile it yourself (make ; make install).
|
||||
|
||||
A **Java runtime (jre or jdk), version 8 or newer** is required to run a prepackaged version of the compiler.
|
||||
A **Java runtime (jre or jdk), version 11 or newer** is required to run a prepackaged version of the compiler.
|
||||
If you want to build it from source, you'll need a Java SDK + Kotlin 1.3.x SDK (or for instance,
|
||||
IntelliJ IDEA with the Kotlin plugin).
|
||||
|
||||
It's handy to have a C-64 emulator or a real C-64 to run the programs on. The compiler assumes the presence
|
||||
of the [Vice emulator](http://vice-emu.sourceforge.net/)
|
||||
It's handy to have an emulator (or a real machine perhaps!) to run the programs on. The compiler assumes the presence
|
||||
of the [Vice emulator](http://vice-emu.sourceforge.net/) for the C64 target,
|
||||
and the [x16emu emulator](https://github.com/commanderx16/x16-emulator) for the CommanderX16 target.
|
||||
|
||||
|
||||
Example code
|
||||
@ -64,44 +75,45 @@ 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")
|
||||
; clear the sieve, to reset starting situation on subsequent runs
|
||||
memset(sieve, 256, false)
|
||||
; calculate primes
|
||||
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.chrout('\n')
|
||||
txt.print("number of primes (expected 54): ")
|
||||
txt.print_ub(amount)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
|
||||
|
||||
sub find_next_prime() -> ubyte {
|
||||
|
||||
while sieve[candidate_prime] {
|
||||
candidate_prime++
|
||||
if candidate_prime==0
|
||||
return 0
|
||||
return 0 ; we wrapped; no more primes available in the sieve
|
||||
}
|
||||
|
||||
|
||||
; found next one, mark the multiples and return it.
|
||||
sieve[candidate_prime] = true
|
||||
uword multiple = candidate_prime
|
||||
|
||||
while multiple < len(sieve) {
|
||||
sieve[lsb(multiple)] = true
|
||||
multiple += candidate_prime
|
||||
@ -111,11 +123,11 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
||||
}
|
||||
|
||||
|
||||
|
||||
when compiled an ran on a C-64 you'll get:
|
||||
|
||||

|
||||
|
||||
|
||||
One of the included examples (wizzine.p8) animates a bunch of sprite balloons and looks like this:
|
||||
|
||||

|
||||
@ -127,3 +139,8 @@ 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:
|
||||
|
||||

|
||||
|
||||
The CommanderX16 compiler target is quite capable already too, here's a well known space ship
|
||||
animated in 3D with hidden line removal, in the CommanderX16 emulator:
|
||||
|
||||

|
||||
|
@ -1,11 +1,11 @@
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.70"
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.20"
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
// id "org.jetbrains.kotlin.jvm" version "1.3.70"
|
||||
// id "org.jetbrains.kotlin.jvm" version "1.4.20"
|
||||
id 'application'
|
||||
id 'org.jetbrains.dokka' version "0.9.18"
|
||||
id 'com.github.johnrengelman.shadow' version '5.2.0'
|
||||
@ -15,8 +15,8 @@ plugins {
|
||||
apply plugin: "kotlin"
|
||||
apply plugin: "java"
|
||||
|
||||
targetCompatibility = 1.8
|
||||
sourceCompatibility = 1.8
|
||||
targetCompatibility = 11
|
||||
sourceCompatibility = 11
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
@ -45,7 +45,7 @@ dependencies {
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
jvmTarget = "11"
|
||||
// verbose = true
|
||||
// freeCompilerArgs += "-XXLanguage:+NewInference"
|
||||
}
|
||||
@ -53,7 +53,7 @@ compileKotlin {
|
||||
|
||||
compileTestKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
jvmTarget = "11"
|
||||
}
|
||||
}
|
||||
|
||||
@ -110,3 +110,7 @@ dokka {
|
||||
outputFormat = 'html'
|
||||
outputDirectory = "$buildDir/kdoc"
|
||||
}
|
||||
|
||||
task wrapper(type: Wrapper) {
|
||||
gradleVersion = '6.1.1'
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
|
||||
<orderEntry type="jdk" jdkName="11" jdkType="JavaSDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="module" module-name="parser" />
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 8.9 KiB |
Binary file not shown.
Before Width: | Height: | Size: 9.0 KiB |
676
compiler/res/prog8lib/c64/floats.asm
Normal file
676
compiler/res/prog8lib/c64/floats.asm
Normal file
@ -0,0 +1,676 @@
|
||||
; --- 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
|
||||
|
||||
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
|
||||
.pend
|
||||
|
||||
var_fac1_greatereq_f .proc
|
||||
; -- is the float in FAC1 >= the variable AY?
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr FCOMP
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
cmp #0
|
||||
beq +
|
||||
cmp #1
|
||||
beq +
|
||||
lda #0
|
||||
rts
|
||||
+ lda #1
|
||||
rts
|
||||
.pend
|
||||
|
||||
var_fac1_notequal_f .proc
|
||||
; -- are the floats numbers in FAC1 and the variable AY *not* identical?
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr FCOMP
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
and #1
|
||||
rts
|
||||
.pend
|
||||
|
||||
vars_equal_f .proc
|
||||
; -- are the mflpt5 numbers in P8ZP_SCRATCH_W1 and AY identical?
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
bne _false
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
bne _false
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
bne _false
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
bne _false
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
bne _false
|
||||
lda #1
|
||||
rts
|
||||
_false lda #0
|
||||
rts
|
||||
.pend
|
||||
|
||||
equal_f .proc
|
||||
; -- are the two mflpt5 numbers on the stack identical?
|
||||
inx
|
||||
inx
|
||||
inx
|
||||
inx
|
||||
lda P8ESTACK_LO-3,x
|
||||
cmp P8ESTACK_LO,x
|
||||
bne _equals_false
|
||||
lda P8ESTACK_LO-2,x
|
||||
cmp P8ESTACK_LO+1,x
|
||||
bne _equals_false
|
||||
lda P8ESTACK_LO-1,x
|
||||
cmp P8ESTACK_LO+2,x
|
||||
bne _equals_false
|
||||
lda P8ESTACK_HI-2,x
|
||||
cmp P8ESTACK_HI+1,x
|
||||
bne _equals_false
|
||||
lda P8ESTACK_HI-1,x
|
||||
cmp P8ESTACK_HI+2,x
|
||||
bne _equals_false
|
||||
_equals_true lda #1
|
||||
_equals_store inx
|
||||
sta P8ESTACK_LO+1,x
|
||||
rts
|
||||
_equals_false lda #0
|
||||
beq _equals_store
|
||||
.pend
|
||||
|
||||
notequal_f .proc
|
||||
; -- are the two mflpt5 numbers on the stack different?
|
||||
jsr equal_f
|
||||
eor #1 ; invert the result
|
||||
sta P8ESTACK_LO+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
vars_less_f .proc
|
||||
; -- is float in AY < float in P8ZP_SCRATCH_W2 ?
|
||||
jsr MOVFM
|
||||
lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr FCOMP
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
cmp #255
|
||||
bne +
|
||||
lda #1
|
||||
rts
|
||||
+ lda #0
|
||||
rts
|
||||
.pend
|
||||
|
||||
vars_lesseq_f .proc
|
||||
; -- is float in AY <= float in P8ZP_SCRATCH_W2 ?
|
||||
jsr MOVFM
|
||||
lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr FCOMP
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
cmp #255
|
||||
bne +
|
||||
- lda #1
|
||||
rts
|
||||
+ cmp #0
|
||||
beq -
|
||||
lda #0
|
||||
rts
|
||||
.pend
|
||||
|
||||
less_f .proc
|
||||
; -- is f1 < f2?
|
||||
jsr compare_floats
|
||||
cmp #255
|
||||
beq compare_floats._return_true
|
||||
bne compare_floats._return_false
|
||||
.pend
|
||||
|
||||
|
||||
lesseq_f .proc
|
||||
; -- is f1 <= f2?
|
||||
jsr compare_floats
|
||||
cmp #255
|
||||
beq compare_floats._return_true
|
||||
cmp #0
|
||||
beq compare_floats._return_true
|
||||
bne compare_floats._return_false
|
||||
.pend
|
||||
|
||||
greater_f .proc
|
||||
; -- is f1 > f2?
|
||||
jsr compare_floats
|
||||
cmp #1
|
||||
beq compare_floats._return_true
|
||||
bne compare_floats._return_false
|
||||
.pend
|
||||
|
||||
greatereq_f .proc
|
||||
; -- is f1 >= f2?
|
||||
jsr compare_floats
|
||||
cmp #1
|
||||
beq compare_floats._return_true
|
||||
cmp #0
|
||||
beq compare_floats._return_true
|
||||
bne compare_floats._return_false
|
||||
.pend
|
||||
|
||||
compare_floats .proc
|
||||
lda #<fmath_float2
|
||||
ldy #>fmath_float2
|
||||
jsr pop_float
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr pop_float
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr MOVFM ; fac1 = flt1
|
||||
lda #<fmath_float2
|
||||
ldy #>fmath_float2
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr FCOMP ; A = flt1 compared with flt2 (0=equal, 1=flt1>flt2, 255=flt1<flt2)
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
_return_false lda #0
|
||||
_return_result sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
_return_true lda #1
|
||||
bne _return_result
|
||||
.pend
|
||||
|
||||
set_array_float_from_fac1 .proc
|
||||
; -- set the float in FAC1 in the array (index in A, array in P8ZP_SCRATCH_W1)
|
||||
sta P8ZP_SCRATCH_B1
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc P8ZP_SCRATCH_B1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
clc
|
||||
adc P8ZP_SCRATCH_W1
|
||||
bcc +
|
||||
iny
|
||||
+ stx floats_store_reg
|
||||
tax
|
||||
jsr MOVMF
|
||||
ldx floats_store_reg
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
set_0_array_float .proc
|
||||
; -- set a float in an array to zero (index in A, array in P8ZP_SCRATCH_W1)
|
||||
sta P8ZP_SCRATCH_B1
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc P8ZP_SCRATCH_B1
|
||||
tay
|
||||
lda #0
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
iny
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
iny
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
iny
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
iny
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
set_array_float .proc
|
||||
; -- set a float in an array to a value (index in A, float in P8ZP_SCRATCH_W1, array in P8ZP_SCRATCH_W2)
|
||||
sta P8ZP_SCRATCH_B1
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc P8ZP_SCRATCH_B1
|
||||
adc P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
bcc +
|
||||
iny
|
||||
+ jmp copy_float
|
||||
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
|
||||
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
|
||||
.pend
|
||||
|
||||
|
@ -4,14 +4,14 @@
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
%target c64
|
||||
%option enable_floats
|
||||
|
||||
|
||||
c64flt {
|
||||
floats {
|
||||
; ---- this block contains C-64 floating point related functions ----
|
||||
|
||||
const float PI = 3.141592653589793
|
||||
const float TWOPI = 6.283185307179586
|
||||
const float PI = 3.141592653589793
|
||||
const float TWOPI = 6.283185307179586
|
||||
|
||||
|
||||
; ---- C64 basic and kernal ROM float constants and functions ----
|
||||
@ -35,13 +35,11 @@ c64flt {
|
||||
&float FL_TWOPI = $e2e5 ; 2 * PI
|
||||
&float FL_FR4 = $e2ea ; .25
|
||||
; oddly enough, 0.0 isn't available in the kernel.
|
||||
float FL_ZERO = 0.0 ; oddly enough 0.0 isn't available in the kernel
|
||||
|
||||
|
||||
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
|
||||
; note: for subtraction and division, the left operand is in fac2, the right operand in fac1.
|
||||
|
||||
; checked functions below:
|
||||
romsub $bba2 = MOVFM(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac1
|
||||
romsub $bba6 = FREADMEM() clobbers(A,Y) ; load mflpt value from memory in $22/$23 into fac1
|
||||
romsub $ba8c = CONUPK(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac2
|
||||
@ -52,22 +50,22 @@ romsub $bc0f = MOVEF() clobbers(A,X) ; copy fac1 to fac2
|
||||
romsub $bbd4 = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
|
||||
|
||||
; fac1-> signed word in Y/A (might throw ILLEGAL QUANTITY)
|
||||
; (tip: use c64flt.FTOSWRDAY to get A/Y output; lo/hi switched to normal little endian order)
|
||||
; (tip: use floats.FTOSWRDAY to get A/Y output; lo/hi switched to normal little endian order)
|
||||
romsub $b1aa = FTOSWORDYA() clobbers(X) -> ubyte @ Y, ubyte @ A ; note: calls AYINT.
|
||||
|
||||
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
|
||||
; (tip: use c64flt.GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
|
||||
; (tip: use floats.GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
|
||||
romsub $b7f7 = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
|
||||
|
||||
romsub $bc9b = QINT() clobbers(A,X,Y) ; fac1 -> 4-byte signed integer in 98-101 ($62-$65), with the MSB FIRST.
|
||||
romsub $b1bf = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 100-101 ($64-$65) MSB FIRST. (might throw ILLEGAL QUANTITY)
|
||||
|
||||
; GIVAYF: signed word in Y/A (note different lsb/msb order) -> float in fac1
|
||||
; (tip: use c64flt.GIVAYFAY to use A/Y input; lo/hi switched to normal order)
|
||||
; there is also c64flt.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
|
||||
; there is also c64flt.FREADS32 that reads from 98-101 ($62-$65) MSB FIRST
|
||||
; there is also c64flt.FREADUS32 that reads from 98-101 ($62-$65) MSB FIRST
|
||||
; there is also c64flt.FREADS24AXY that reads signed int24 into fac1 from A/X/Y (lo/mid/hi bytes)
|
||||
; (tip: use floats.GIVAYFAY to use A/Y input; lo/hi switched to normal order)
|
||||
; there is also floats.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
|
||||
; there is also floats.FREADS32 that reads from 98-101 ($62-$65) MSB FIRST
|
||||
; there is also floats.FREADUS32 that reads from 98-101 ($62-$65) MSB FIRST
|
||||
; there is also floats.FREADS24AXY that reads signed int24 into fac1 from A/X/Y (lo/mid/hi bytes)
|
||||
romsub $b391 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
|
||||
|
||||
romsub $b3a2 = FREADUY(ubyte value @ Y) clobbers(A,X,Y) ; 8 bit unsigned Y -> float in fac1
|
||||
@ -91,6 +89,7 @@ romsub $bb12 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1
|
||||
romsub $bb0f = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
|
||||
romsub $bf7b = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
|
||||
romsub $bf78 = FPWR(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = fac2 ** mflpt from A/Y
|
||||
romsub $bd7e = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
|
||||
|
||||
romsub $aed4 = NOTOP() clobbers(A,X,Y) ; fac1 = NOT(fac1)
|
||||
romsub $bccc = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc
|
||||
@ -163,9 +162,9 @@ asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||
asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1
|
||||
%asm {{
|
||||
sta c64.SCRATCH_ZPREG
|
||||
sta P8ZP_SCRATCH_REG
|
||||
tya
|
||||
ldy c64.SCRATCH_ZPREG
|
||||
ldy P8ZP_SCRATCH_REG
|
||||
jmp GIVAYF ; this uses the inverse order, Y/A
|
||||
}}
|
||||
}
|
||||
@ -174,9 +173,9 @@ asmsub FTOSWRDAY () clobbers(X) -> uword @ AY {
|
||||
; ---- fac1 to signed word in A/Y
|
||||
%asm {{
|
||||
jsr FTOSWORDYA ; note the inverse Y/A order
|
||||
sta c64.SCRATCH_ZPREG
|
||||
sta P8ZP_SCRATCH_REG
|
||||
tya
|
||||
ldy c64.SCRATCH_ZPREG
|
||||
ldy P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
@ -185,41 +184,35 @@ asmsub GETADRAY () clobbers(X) -> uword @ AY {
|
||||
; ---- fac1 to unsigned word in A/Y
|
||||
%asm {{
|
||||
jsr GETADR ; this uses the inverse order, Y/A
|
||||
sta c64.SCRATCH_ZPB1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
tya
|
||||
ldy c64.SCRATCH_ZPB1
|
||||
ldy P8ZP_SCRATCH_B1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub print_f (float value) {
|
||||
; ---- prints the floating point value (without a newline) using basic rom routines.
|
||||
; ---- prints the floating point value (without a newline).
|
||||
%asm {{
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
stx floats_store_reg
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr MOVFM ; load float into fac1
|
||||
jsr FOUT ; fac1 to string in A/Y
|
||||
jsr c64.STROUT ; print string in A/Y
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
beq +
|
||||
jsr c64.CHROUT
|
||||
iny
|
||||
bne -
|
||||
+ ldx floats_store_reg
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub print_fln (float value) {
|
||||
; ---- prints the floating point value (with a newline at the end) using basic rom routines
|
||||
%asm {{
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr MOVFM ; load float into fac1
|
||||
jsr FPRINTLN ; print fac1 with newline
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
rts
|
||||
}}
|
||||
%asminclude "library:c64/floats.asm", ""
|
||||
%asminclude "library:c64/floats_funcs.asm", ""
|
||||
|
||||
}
|
||||
|
||||
%asminclude "library:c64floats.asm", ""
|
||||
|
||||
} ; ------ end of block c64flt
|
437
compiler/res/prog8lib/c64/floats_funcs.asm
Normal file
437
compiler/res/prog8lib/c64/floats_funcs.asm
Normal file
@ -0,0 +1,437 @@
|
||||
; --- floating point builtin functions
|
||||
|
||||
|
||||
abs_f_stack .proc
|
||||
; -- push abs(AY) on stack
|
||||
jsr floats.MOVFM
|
||||
jsr floats.ABS
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
abs_f_fac1 .proc
|
||||
; -- FAC1 = abs(AY)
|
||||
jsr floats.MOVFM
|
||||
jmp floats.ABS
|
||||
.pend
|
||||
|
||||
func_atan_stack .proc
|
||||
jsr func_atan_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_atan_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr ATN
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_ceil_stack .proc
|
||||
jsr func_ceil_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_ceil_fac1 .proc
|
||||
; -- ceil: tr = int(f); if tr==f -> return else return tr+1
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
ldx #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr MOVMF
|
||||
jsr INT
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr FCOMP
|
||||
cmp #0
|
||||
beq +
|
||||
lda #<FL_ONE_const
|
||||
ldy #>FL_ONE_const
|
||||
jsr FADD
|
||||
+ ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_floor_stack .proc
|
||||
jsr func_floor_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_floor_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr INT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_round_stack .proc
|
||||
jsr func_round_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_round_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr FADDH
|
||||
jsr INT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sin_stack .proc
|
||||
jsr func_sin_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_sin_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr SIN
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cos_stack .proc
|
||||
jsr func_cos_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_cos_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr COS
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_tan_stack .proc
|
||||
jsr func_tan_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_tan_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr TAN
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_rad_stack .proc
|
||||
jsr func_rad_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_rad_fac1 .proc
|
||||
; -- convert degrees to radians (d * pi / 180)
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #<_pi_div_180
|
||||
ldy #>_pi_div_180
|
||||
jsr FMULT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
_pi_div_180 .byte 123, 14, 250, 53, 18 ; pi / 180
|
||||
.pend
|
||||
|
||||
func_deg_stack .proc
|
||||
jsr func_deg_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_deg_fac1 .proc
|
||||
; -- convert radians to degrees (d * (1/ pi * 180))
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #<_one_over_pi_div_180
|
||||
ldy #>_one_over_pi_div_180
|
||||
jsr FMULT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
_one_over_pi_div_180 .byte 134, 101, 46, 224, 211 ; 1 / (pi * 180)
|
||||
.pend
|
||||
|
||||
func_ln_stack .proc
|
||||
jsr func_ln_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_ln_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr LOG
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_log2_stack .proc
|
||||
jsr func_log2_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_log2_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr LOG
|
||||
jsr MOVEF
|
||||
lda #<FL_LOG2
|
||||
ldy #>FL_LOG2
|
||||
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
|
253
compiler/res/prog8lib/c64/graphics.p8
Normal file
253
compiler/res/prog8lib/c64/graphics.p8
Normal file
@ -0,0 +1,253 @@
|
||||
%target c64
|
||||
%import textio
|
||||
|
||||
; bitmap pixel graphics module for the C64
|
||||
; only black/white monchrome 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) {
|
||||
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.
|
||||
; TODO rewrite this in optimized assembly
|
||||
if y1>y2 {
|
||||
; make sure dy is always positive to avoid 8 instead of just 4 special cases
|
||||
swap(x1, x2)
|
||||
swap(y1, y2)
|
||||
}
|
||||
word @zp d = 0
|
||||
ubyte positive_ix = true
|
||||
word @zp dx = x2-x1 as word
|
||||
word @zp dy = y2-y1
|
||||
if dx < 0 {
|
||||
dx = -dx
|
||||
positive_ix = false
|
||||
}
|
||||
dx *= 2
|
||||
dy *= 2
|
||||
internal_plotx = x1
|
||||
|
||||
if dx >= dy {
|
||||
if positive_ix {
|
||||
repeat {
|
||||
internal_plot(y1)
|
||||
if internal_plotx==x2
|
||||
return
|
||||
internal_plotx++
|
||||
d += dy
|
||||
if d > dx {
|
||||
y1++
|
||||
d -= dx
|
||||
}
|
||||
}
|
||||
} else {
|
||||
repeat {
|
||||
internal_plot(y1)
|
||||
if internal_plotx==x2
|
||||
return
|
||||
internal_plotx--
|
||||
d += dy
|
||||
if d > dx {
|
||||
y1++
|
||||
d -= dx
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if positive_ix {
|
||||
repeat {
|
||||
internal_plot(y1)
|
||||
if y1 == y2
|
||||
return
|
||||
y1++
|
||||
d += dx
|
||||
if d > dy {
|
||||
internal_plotx++
|
||||
d -= dy
|
||||
}
|
||||
}
|
||||
} else {
|
||||
repeat {
|
||||
internal_plot(y1)
|
||||
if y1 == y2
|
||||
return
|
||||
y1++
|
||||
d += dx
|
||||
if d > dy {
|
||||
internal_plotx--
|
||||
d -= dy
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||
; Midpoint algorithm
|
||||
ubyte @zp ploty
|
||||
ubyte @zp xx = radius
|
||||
ubyte @zp yy = 0
|
||||
byte @zp decisionOver2 = 1-xx as byte
|
||||
|
||||
while xx>=yy {
|
||||
internal_plotx = xcenter + xx
|
||||
ploty = ycenter + yy
|
||||
internal_plot(ploty)
|
||||
internal_plotx = xcenter - xx
|
||||
internal_plot(ploty)
|
||||
internal_plotx = xcenter + xx
|
||||
ploty = ycenter - yy
|
||||
internal_plot(ploty)
|
||||
internal_plotx = xcenter - xx
|
||||
internal_plot(ploty)
|
||||
internal_plotx = xcenter + yy
|
||||
ploty = ycenter + xx
|
||||
internal_plot(ploty)
|
||||
internal_plotx = xcenter - yy
|
||||
internal_plot(ploty)
|
||||
internal_plotx = xcenter + yy
|
||||
ploty = ycenter - xx
|
||||
internal_plot(ploty)
|
||||
internal_plotx = xcenter - yy
|
||||
internal_plot(ploty)
|
||||
yy++
|
||||
if decisionOver2<=0
|
||||
decisionOver2 += 2*yy+1
|
||||
else {
|
||||
xx--
|
||||
decisionOver2 += 2*(yy-xx)+1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||
; Midpoint algorithm, filled
|
||||
ubyte xx = radius
|
||||
ubyte yy = 0
|
||||
byte decisionOver2 = 1-xx as byte
|
||||
|
||||
while xx>=yy {
|
||||
ubyte ycenter_plus_yy = ycenter + yy
|
||||
ubyte ycenter_min_yy = ycenter - yy
|
||||
ubyte ycenter_plus_xx = ycenter + xx
|
||||
ubyte ycenter_min_xx = ycenter - xx
|
||||
|
||||
for internal_plotx in xcenter to xcenter+xx {
|
||||
internal_plot(ycenter_plus_yy)
|
||||
internal_plot(ycenter_min_yy)
|
||||
}
|
||||
for internal_plotx in xcenter-xx to xcenter-1 {
|
||||
internal_plot(ycenter_plus_yy)
|
||||
internal_plot(ycenter_min_yy)
|
||||
}
|
||||
for internal_plotx in xcenter to xcenter+yy {
|
||||
internal_plot(ycenter_plus_xx)
|
||||
internal_plot(ycenter_min_xx)
|
||||
}
|
||||
for internal_plotx in xcenter-yy to xcenter {
|
||||
internal_plot(ycenter_plus_xx)
|
||||
internal_plot(ycenter_min_xx)
|
||||
}
|
||||
yy++
|
||||
if decisionOver2<=0
|
||||
decisionOver2 += 2*yy+1
|
||||
else {
|
||||
xx--
|
||||
decisionOver2 += 2*(yy-xx)+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]
|
||||
; }
|
||||
|
||||
asmsub plot(uword plotx @XY, ubyte ploty @A) clobbers (A, X, Y) {
|
||||
%asm {{
|
||||
stx internal_plotx
|
||||
sty internal_plotx+1
|
||||
jmp 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
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -5,20 +5,13 @@
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
%target c64
|
||||
|
||||
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 STATUS = $90 ; kernel 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)
|
||||
|
||||
@ -183,19 +176,11 @@ c64 {
|
||||
; ---- end of SID registers ----
|
||||
|
||||
|
||||
|
||||
; ---- C64 basic routines ----
|
||||
|
||||
romsub $E544 = CLEARSCR() clobbers(A,X,Y) ; clear the screen
|
||||
romsub $E566 = HOMECRSR() clobbers(A,X,Y) ; cursor to top left of screen
|
||||
|
||||
|
||||
; ---- end of C64 basic routines ----
|
||||
|
||||
|
||||
; ---- C64 kernal routines ----
|
||||
; ---- C64 ROM kernal routines ----
|
||||
|
||||
romsub $AB1E = STROUT(uword strptr @ AY) clobbers(A, X, Y) ; print null-terminated string (use c64scr.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
|
||||
@ -219,25 +204,245 @@ romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial
|
||||
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
|
||||
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte address @ Y) ; set logical file parameters
|
||||
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
|
||||
romsub $FFC0 = OPEN() clobbers(A,X,Y) ; (via 794 ($31A)) open a logical file
|
||||
romsub $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) ; (via 798 ($31E)) define an input channel
|
||||
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> ubyte @Pc ; (via 798 ($31E)) define an input channel
|
||||
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
|
||||
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
|
||||
romsub $FFCF = CHRIN() clobbers(Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
|
||||
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y ; (via 816 ($330)) load from device
|
||||
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
||||
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
||||
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock
|
||||
romsub $FFE1 = STOP() clobbers(A,X) -> ubyte @ Pz, ubyte @ Pc ; (via 808 ($328)) check the STOP key
|
||||
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @ A ; (via 810 ($32A)) get a character
|
||||
romsub $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 c64scr.plot for a 'safe' wrapper that preserves X.
|
||||
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
|
||||
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
|
||||
|
||||
; ---- end of C64 kernal routines ----
|
||||
; ---- end of C64 ROM kernal routines ----
|
||||
|
||||
|
||||
; ---- 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 reset_system() {
|
||||
; Soft-reset the system back to Basic prompt.
|
||||
%asm {{
|
||||
sei
|
||||
lda #14
|
||||
sta $01 ; bank the kernal in
|
||||
jmp (c64.RESET_VEC)
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub disable_runstop_and_charsetswitch() {
|
||||
%asm {{
|
||||
lda #$80
|
||||
sta 657 ; disable charset switching
|
||||
lda #239
|
||||
sta 808 ; disable run/stop key
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_irqvec_excl() clobbers(A) {
|
||||
%asm {{
|
||||
sei
|
||||
lda #<_irq_handler
|
||||
sta c64.CINV
|
||||
lda #>_irq_handler
|
||||
sta c64.CINV+1
|
||||
cli
|
||||
rts
|
||||
_irq_handler jsr set_irqvec._irq_handler_init
|
||||
jsr irq.irq
|
||||
jsr set_irqvec._irq_handler_end
|
||||
lda #$ff
|
||||
sta c64.VICIRQ ; acknowledge raster irq
|
||||
lda c64.CIA1ICR ; acknowledge CIA1 interrupt
|
||||
jmp c64.IRQDFEND ; end irq processing - don't call kernel
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_irqvec() clobbers(A) {
|
||||
%asm {{
|
||||
sei
|
||||
lda #<_irq_handler
|
||||
sta c64.CINV
|
||||
lda #>_irq_handler
|
||||
sta c64.CINV+1
|
||||
cli
|
||||
rts
|
||||
_irq_handler jsr _irq_handler_init
|
||||
jsr irq.irq
|
||||
jsr _irq_handler_end
|
||||
jmp c64.IRQDFRT ; continue with normal kernel irq routine
|
||||
|
||||
_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_irqvec() {
|
||||
%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 rasterpos @ AY) clobbers(A) {
|
||||
%asm {{
|
||||
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_irqvec._irq_handler_init
|
||||
jsr irq.irq
|
||||
jsr set_irqvec._irq_handler_end
|
||||
lda #$ff
|
||||
sta c64.VICIRQ ; acknowledge raster irq
|
||||
jmp c64.IRQDFRT
|
||||
|
||||
_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
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_rasterirq_excl(uword rasterpos @ AY) clobbers(A) {
|
||||
%asm {{
|
||||
sei
|
||||
jsr set_rasterirq._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_irqvec._irq_handler_init
|
||||
jsr irq.irq
|
||||
jsr set_irqvec._irq_handler_end
|
||||
lda #$ff
|
||||
sta c64.VICIRQ ; acknowledge raster irq
|
||||
jmp c64.IRQDFEND ; end irq processing - don't call kernel
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
; ---- end of C64 specific system utility routines ----
|
||||
|
||||
|
||||
}
|
587
compiler/res/prog8lib/c64/textio.p8
Normal file
587
compiler/res/prog8lib/c64/textio.p8
Normal file
@ -0,0 +1,587 @@
|
||||
; 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() {
|
||||
clear_screenchars(' ')
|
||||
}
|
||||
|
||||
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 #0
|
||||
_loop sta c64.Screen,y
|
||||
sta c64.Screen+$0100,y
|
||||
sta c64.Screen+$0200,y
|
||||
sta c64.Screen+$02e8,y
|
||||
iny
|
||||
bne _loop
|
||||
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 #0
|
||||
_loop sta c64.Colors,y
|
||||
sta c64.Colors+$0100,y
|
||||
sta c64.Colors+$0200,y
|
||||
sta c64.Colors+$02e8,y
|
||||
iny
|
||||
bne _loop
|
||||
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
|
||||
bcs +
|
||||
jmp _scroll_screen
|
||||
|
||||
+ ; scroll the color memory
|
||||
ldx #0
|
||||
ldy #38
|
||||
-
|
||||
.for row=0, row<=24, row+=1
|
||||
lda c64.Colors + 40*row + 1,x
|
||||
sta c64.Colors + 40*row,x
|
||||
.next
|
||||
inx
|
||||
dey
|
||||
bpl -
|
||||
|
||||
_scroll_screen ; scroll the screen memory
|
||||
ldx #0
|
||||
ldy #38
|
||||
-
|
||||
.for row=0, row<=24, row+=1
|
||||
lda c64.Screen + 40*row + 1,x
|
||||
sta c64.Screen + 40*row,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
|
||||
bcs +
|
||||
jmp _scroll_screen
|
||||
|
||||
+ ; scroll the color memory
|
||||
ldx #38
|
||||
-
|
||||
.for row=0, row<=24, row+=1
|
||||
lda c64.Colors + 40*row + 0,x
|
||||
sta c64.Colors + 40*row + 1,x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
|
||||
_scroll_screen ; scroll 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
|
||||
bcs +
|
||||
jmp _scroll_screen
|
||||
|
||||
+ ; scroll the color memory
|
||||
ldx #39
|
||||
-
|
||||
.for row=1, row<=24, row+=1
|
||||
lda c64.Colors + 40*row,x
|
||||
sta c64.Colors + 40*(row-1),x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
|
||||
_scroll_screen ; scroll 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
|
||||
bcs +
|
||||
jmp _scroll_screen
|
||||
|
||||
+ ; scroll 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
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
|
||||
_scroll_screen ; scroll 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 kernel 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,741 +0,0 @@
|
||||
; --- low level floating point assembly routines for the C64
|
||||
|
||||
ub2float .proc
|
||||
; -- convert ubyte in SCRATCH_ZPB1 to float at address A/Y
|
||||
; clobbers A, Y
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
sta c64.SCRATCH_ZPWORD2
|
||||
sty c64.SCRATCH_ZPWORD2+1
|
||||
ldy c64.SCRATCH_ZPB1
|
||||
jsr FREADUY
|
||||
_fac_to_mem ldx c64.SCRATCH_ZPWORD2
|
||||
ldy c64.SCRATCH_ZPWORD2+1
|
||||
jsr MOVMF
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
rts
|
||||
.pend
|
||||
|
||||
b2float .proc
|
||||
; -- convert byte in SCRATCH_ZPB1 to float at address A/Y
|
||||
; clobbers A, Y
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
sta c64.SCRATCH_ZPWORD2
|
||||
sty c64.SCRATCH_ZPWORD2+1
|
||||
lda c64.SCRATCH_ZPB1
|
||||
jsr FREADSA
|
||||
jmp ub2float._fac_to_mem
|
||||
.pend
|
||||
|
||||
uw2float .proc
|
||||
; -- convert uword in SCRATCH_ZPWORD1 to float at address A/Y
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
sta c64.SCRATCH_ZPWORD2
|
||||
sty c64.SCRATCH_ZPWORD2+1
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
ldy c64.SCRATCH_ZPWORD1+1
|
||||
jsr GIVUAYFAY
|
||||
jmp ub2float._fac_to_mem
|
||||
.pend
|
||||
|
||||
w2float .proc
|
||||
; -- convert word in SCRATCH_ZPWORD1 to float at address A/Y
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
sta c64.SCRATCH_ZPWORD2
|
||||
sty c64.SCRATCH_ZPWORD2+1
|
||||
ldy c64.SCRATCH_ZPWORD1
|
||||
lda c64.SCRATCH_ZPWORD1+1
|
||||
jsr GIVAYF
|
||||
jmp ub2float._fac_to_mem
|
||||
.pend
|
||||
|
||||
stack_b2float .proc
|
||||
; -- b2float operating on the stack
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr FREADSA
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
stack_w2float .proc
|
||||
; -- w2float operating on the stack
|
||||
inx
|
||||
ldy c64.ESTACK_LO,x
|
||||
lda c64.ESTACK_HI,x
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr GIVAYF
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
stack_ub2float .proc
|
||||
; -- ub2float operating on the stack
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
tay
|
||||
jsr FREADUY
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
stack_uw2float .proc
|
||||
; -- uw2float operating on the stack
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
ldy c64.ESTACK_HI,x
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr GIVUAYFAY
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
stack_float2w .proc ; also used for float2b
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr AYINT
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
lda $64
|
||||
sta c64.ESTACK_HI,x
|
||||
lda $65
|
||||
sta c64.ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
stack_float2uw .proc ; also used for float2ub
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr GETADR
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
sta c64.ESTACK_HI,x
|
||||
tya
|
||||
sta c64.ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
push_float .proc
|
||||
; ---- push mflpt5 in A/Y onto stack
|
||||
; (taking 3 stack positions = 6 bytes of which 1 is padding)
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
sty c64.SCRATCH_ZPWORD1+1
|
||||
ldy #0
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
sta c64.ESTACK_LO,x
|
||||
iny
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
sta c64.ESTACK_HI,x
|
||||
dex
|
||||
iny
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
sta c64.ESTACK_LO,x
|
||||
iny
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
sta c64.ESTACK_HI,x
|
||||
dex
|
||||
iny
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
sta c64.ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_rndf .proc
|
||||
; -- put a random floating point value on the stack
|
||||
stx c64.SCRATCH_ZPREG
|
||||
lda #1
|
||||
jsr FREADSA
|
||||
jsr RND ; rng into fac1
|
||||
ldx #<_rndf_rnum5
|
||||
ldy #>_rndf_rnum5
|
||||
jsr MOVMF ; fac1 to mem X/Y
|
||||
ldx c64.SCRATCH_ZPREG
|
||||
lda #<_rndf_rnum5
|
||||
ldy #>_rndf_rnum5
|
||||
jmp push_float
|
||||
_rndf_rnum5 .byte 0,0,0,0,0
|
||||
.pend
|
||||
|
||||
push_float_from_indexed_var .proc
|
||||
; -- push the float from the array at A/Y with index on stack, onto the stack.
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
sty c64.SCRATCH_ZPWORD1+1
|
||||
jsr prog8_lib.pop_index_times_5
|
||||
jsr prog8_lib.add_a_to_zpword
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
ldy c64.SCRATCH_ZPWORD1+1
|
||||
jmp push_float
|
||||
.pend
|
||||
|
||||
pop_float .proc
|
||||
; ---- pops mflpt5 from stack to memory A/Y
|
||||
; (frees 3 stack positions = 6 bytes of which 1 is padding)
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
sty c64.SCRATCH_ZPWORD1+1
|
||||
ldy #4
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
dey
|
||||
inx
|
||||
lda c64.ESTACK_HI,x
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
dey
|
||||
lda c64.ESTACK_LO,x
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
dey
|
||||
inx
|
||||
lda c64.ESTACK_HI,x
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
dey
|
||||
lda c64.ESTACK_LO,x
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
rts
|
||||
.pend
|
||||
|
||||
pop_float_fac1 .proc
|
||||
; -- pops float from stack into FAC1
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr pop_float
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jmp MOVFM
|
||||
.pend
|
||||
|
||||
pop_float_to_indexed_var .proc
|
||||
; -- pop the float on the stack, to the memory in the array at A/Y indexed by the byte on stack
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
sty c64.SCRATCH_ZPWORD1+1
|
||||
jsr prog8_lib.pop_index_times_5
|
||||
jsr prog8_lib.add_a_to_zpword
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
ldy c64.SCRATCH_ZPWORD1+1
|
||||
jmp pop_float
|
||||
.pend
|
||||
|
||||
copy_float .proc
|
||||
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
|
||||
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
|
||||
sta c64.SCRATCH_ZPWORD2
|
||||
sty c64.SCRATCH_ZPWORD2+1
|
||||
ldy #0
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
sta (c64.SCRATCH_ZPWORD2),y
|
||||
iny
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
sta (c64.SCRATCH_ZPWORD2),y
|
||||
iny
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
sta (c64.SCRATCH_ZPWORD2),y
|
||||
iny
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
sta (c64.SCRATCH_ZPWORD2),y
|
||||
iny
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
sta (c64.SCRATCH_ZPWORD2),y
|
||||
rts
|
||||
.pend
|
||||
|
||||
inc_var_f .proc
|
||||
; -- add 1 to float pointed to by A/Y
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
sty c64.SCRATCH_ZPWORD1+1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr MOVFM
|
||||
lda #<FL_FONE
|
||||
ldy #>FL_FONE
|
||||
jsr FADD
|
||||
ldx c64.SCRATCH_ZPWORD1
|
||||
ldy c64.SCRATCH_ZPWORD1+1
|
||||
jsr MOVMF
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
rts
|
||||
.pend
|
||||
|
||||
dec_var_f .proc
|
||||
; -- subtract 1 from float pointed to by A/Y
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
sty c64.SCRATCH_ZPWORD1+1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
lda #<FL_FONE
|
||||
ldy #>FL_FONE
|
||||
jsr MOVFM
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
ldy c64.SCRATCH_ZPWORD1+1
|
||||
jsr FSUB
|
||||
ldx c64.SCRATCH_ZPWORD1
|
||||
ldy c64.SCRATCH_ZPWORD1+1
|
||||
jsr MOVMF
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
rts
|
||||
.pend
|
||||
|
||||
inc_indexed_var_f .proc
|
||||
; -- add 1 to float in array pointed to by A/Y, at index X
|
||||
pha
|
||||
txa
|
||||
sta c64.SCRATCH_ZPB1
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc c64.SCRATCH_ZPB1
|
||||
sta c64.SCRATCH_ZPB1
|
||||
pla
|
||||
clc
|
||||
adc c64.SCRATCH_ZPB1
|
||||
bcc +
|
||||
iny
|
||||
+ jmp inc_var_f
|
||||
.pend
|
||||
|
||||
dec_indexed_var_f .proc
|
||||
; -- subtract 1 to float in array pointed to by A/Y, at index X
|
||||
pha
|
||||
txa
|
||||
sta c64.SCRATCH_ZPB1
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc c64.SCRATCH_ZPB1
|
||||
sta c64.SCRATCH_ZPB1
|
||||
pla
|
||||
clc
|
||||
adc c64.SCRATCH_ZPB1
|
||||
bcc +
|
||||
iny
|
||||
+ jmp dec_var_f
|
||||
.pend
|
||||
|
||||
|
||||
pop_2_floats_f2_in_fac1 .proc
|
||||
; -- pop 2 floats from stack, load the second one in FAC1 as well
|
||||
lda #<fmath_float2
|
||||
ldy #>fmath_float2
|
||||
jsr pop_float
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr pop_float
|
||||
lda #<fmath_float2
|
||||
ldy #>fmath_float2
|
||||
jmp MOVFM
|
||||
.pend
|
||||
|
||||
|
||||
fmath_float1 .byte 0,0,0,0,0 ; storage for a mflpt5 value
|
||||
fmath_float2 .byte 0,0,0,0,0 ; storage for a mflpt5 value
|
||||
|
||||
push_fac1_as_result .proc
|
||||
; -- push the float in FAC1 onto the stack, and return from calculation
|
||||
ldx #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr MOVMF
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
jmp push_float
|
||||
.pend
|
||||
|
||||
pow_f .proc
|
||||
; -- push f1 ** f2 on stack
|
||||
lda #<fmath_float2
|
||||
ldy #>fmath_float2
|
||||
jsr pop_float
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr pop_float
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr CONUPK ; fac2 = float1
|
||||
lda #<fmath_float2
|
||||
ldy #>fmath_float2
|
||||
jsr FPWR
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
div_f .proc
|
||||
; -- push f1/f2 on stack
|
||||
jsr pop_2_floats_f2_in_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr FDIV
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
add_f .proc
|
||||
; -- push f1+f2 on stack
|
||||
jsr pop_2_floats_f2_in_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr FADD
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
sub_f .proc
|
||||
; -- push f1-f2 on stack
|
||||
jsr pop_2_floats_f2_in_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr FSUB
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
mul_f .proc
|
||||
; -- push f1*f2 on stack
|
||||
jsr pop_2_floats_f2_in_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr FMULT
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
neg_f .proc
|
||||
; -- push -flt back on stack
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr NEGOP
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
abs_f .proc
|
||||
; -- push abs(float) on stack (as float)
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr ABS
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
equal_f .proc
|
||||
; -- are the two mflpt5 numbers on the stack identical?
|
||||
inx
|
||||
inx
|
||||
inx
|
||||
inx
|
||||
lda c64.ESTACK_LO-3,x
|
||||
cmp c64.ESTACK_LO,x
|
||||
bne _equals_false
|
||||
lda c64.ESTACK_LO-2,x
|
||||
cmp c64.ESTACK_LO+1,x
|
||||
bne _equals_false
|
||||
lda c64.ESTACK_LO-1,x
|
||||
cmp c64.ESTACK_LO+2,x
|
||||
bne _equals_false
|
||||
lda c64.ESTACK_HI-2,x
|
||||
cmp c64.ESTACK_HI+1,x
|
||||
bne _equals_false
|
||||
lda c64.ESTACK_HI-1,x
|
||||
cmp c64.ESTACK_HI+2,x
|
||||
bne _equals_false
|
||||
_equals_true lda #1
|
||||
_equals_store inx
|
||||
sta c64.ESTACK_LO+1,x
|
||||
rts
|
||||
_equals_false lda #0
|
||||
beq _equals_store
|
||||
.pend
|
||||
|
||||
notequal_f .proc
|
||||
; -- are the two mflpt5 numbers on the stack different?
|
||||
jsr equal_f
|
||||
eor #1 ; invert the result
|
||||
sta c64.ESTACK_LO+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
less_f .proc
|
||||
; -- is f1 < f2?
|
||||
jsr compare_floats
|
||||
cmp #255
|
||||
beq compare_floats._return_true
|
||||
bne compare_floats._return_false
|
||||
.pend
|
||||
|
||||
|
||||
lesseq_f .proc
|
||||
; -- is f1 <= f2?
|
||||
jsr compare_floats
|
||||
cmp #255
|
||||
beq compare_floats._return_true
|
||||
cmp #0
|
||||
beq compare_floats._return_true
|
||||
bne compare_floats._return_false
|
||||
.pend
|
||||
|
||||
greater_f .proc
|
||||
; -- is f1 > f2?
|
||||
jsr compare_floats
|
||||
cmp #1
|
||||
beq compare_floats._return_true
|
||||
bne compare_floats._return_false
|
||||
.pend
|
||||
|
||||
greatereq_f .proc
|
||||
; -- is f1 >= f2?
|
||||
jsr compare_floats
|
||||
cmp #1
|
||||
beq compare_floats._return_true
|
||||
cmp #0
|
||||
beq compare_floats._return_true
|
||||
bne compare_floats._return_false
|
||||
.pend
|
||||
|
||||
compare_floats .proc
|
||||
lda #<fmath_float2
|
||||
ldy #>fmath_float2
|
||||
jsr pop_float
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr pop_float
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr MOVFM ; fac1 = flt1
|
||||
lda #<fmath_float2
|
||||
ldy #>fmath_float2
|
||||
stx c64.SCRATCH_ZPREG
|
||||
jsr FCOMP ; A = flt1 compared with flt2 (0=equal, 1=flt1>flt2, 255=flt1<flt2)
|
||||
ldx c64.SCRATCH_ZPREG
|
||||
rts
|
||||
_return_false lda #0
|
||||
_return_result sta c64.ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
_return_true lda #1
|
||||
bne _return_result
|
||||
.pend
|
||||
|
||||
func_sin .proc
|
||||
; -- push sin(f) back onto stack
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr SIN
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
func_cos .proc
|
||||
; -- push cos(f) back onto stack
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr COS
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
func_tan .proc
|
||||
; -- push tan(f) back onto stack
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr TAN
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
func_atan .proc
|
||||
; -- push atan(f) back onto stack
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr ATN
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
func_ln .proc
|
||||
; -- push ln(f) back onto stack
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr LOG
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
func_log2 .proc
|
||||
; -- push log base 2, ln(f)/ln(2), back onto stack
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr LOG
|
||||
jsr MOVEF
|
||||
lda #<c64.FL_LOG2
|
||||
ldy #>c64.FL_LOG2
|
||||
jsr MOVFM
|
||||
jsr FDIVT
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
func_sqrt .proc
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr SQR
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
func_rad .proc
|
||||
; -- convert degrees to radians (d * pi / 180)
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
lda #<_pi_div_180
|
||||
ldy #>_pi_div_180
|
||||
jsr FMULT
|
||||
jmp push_fac1_as_result
|
||||
_pi_div_180 .byte 123, 14, 250, 53, 18 ; pi / 180
|
||||
.pend
|
||||
|
||||
func_deg .proc
|
||||
; -- convert radians to degrees (d * (1/ pi * 180))
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
lda #<_one_over_pi_div_180
|
||||
ldy #>_one_over_pi_div_180
|
||||
jsr FMULT
|
||||
jmp push_fac1_as_result
|
||||
_one_over_pi_div_180 .byte 134, 101, 46, 224, 211 ; 1 / (pi * 180)
|
||||
.pend
|
||||
|
||||
func_round .proc
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr FADDH
|
||||
jsr INT
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
func_floor .proc
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr INT
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
func_ceil .proc
|
||||
; -- ceil: tr = int(f); if tr==f -> return else return tr+1
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
ldx #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr MOVMF
|
||||
jsr INT
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr FCOMP
|
||||
cmp #0
|
||||
beq +
|
||||
lda #<FL_FONE
|
||||
ldy #>FL_FONE
|
||||
jsr FADD
|
||||
+ jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
func_any_f .proc
|
||||
inx
|
||||
lda c64.ESTACK_LO,x ; array size
|
||||
sta c64.SCRATCH_ZPB1
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc c64.SCRATCH_ZPB1 ; times 5 because of float
|
||||
jmp prog8_lib.func_any_b._entry
|
||||
.pend
|
||||
|
||||
func_all_f .proc
|
||||
inx
|
||||
jsr prog8_lib.peek_address
|
||||
lda c64.ESTACK_LO,x ; array size
|
||||
sta c64.SCRATCH_ZPB1
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc c64.SCRATCH_ZPB1 ; times 5 because of float
|
||||
tay
|
||||
dey
|
||||
- lda (c64.SCRATCH_ZPWORD1),y
|
||||
clc
|
||||
dey
|
||||
adc (c64.SCRATCH_ZPWORD1),y
|
||||
dey
|
||||
adc (c64.SCRATCH_ZPWORD1),y
|
||||
dey
|
||||
adc (c64.SCRATCH_ZPWORD1),y
|
||||
dey
|
||||
adc (c64.SCRATCH_ZPWORD1),y
|
||||
dey
|
||||
cmp #0
|
||||
beq +
|
||||
cpy #255
|
||||
bne -
|
||||
lda #1
|
||||
sta c64.ESTACK_LO+1,x
|
||||
rts
|
||||
+ sta c64.ESTACK_LO+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_max_f .proc
|
||||
lda #255
|
||||
sta _minmax_cmp+1
|
||||
lda #<_largest_neg_float
|
||||
ldy #>_largest_neg_float
|
||||
_minmax_entry jsr MOVFM
|
||||
jsr prog8_lib.pop_array_and_lengthmin1Y
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
- sty c64.SCRATCH_ZPREG
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
ldy c64.SCRATCH_ZPWORD1+1
|
||||
jsr FCOMP
|
||||
_minmax_cmp cmp #255 ; modified
|
||||
bne +
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
ldy c64.SCRATCH_ZPWORD1+1
|
||||
jsr MOVFM
|
||||
+ lda c64.SCRATCH_ZPWORD1
|
||||
clc
|
||||
adc #5
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
bcc +
|
||||
inc c64.SCRATCH_ZPWORD1+1
|
||||
+ ldy c64.SCRATCH_ZPREG
|
||||
dey
|
||||
cpy #255
|
||||
bne -
|
||||
jmp push_fac1_as_result
|
||||
_largest_neg_float .byte 255,255,255,255,255 ; largest negative float -1.7014118345e+38
|
||||
.pend
|
||||
|
||||
func_min_f .proc
|
||||
lda #1
|
||||
sta func_max_f._minmax_cmp+1
|
||||
lda #<_largest_pos_float
|
||||
ldy #>_largest_pos_float
|
||||
jmp func_max_f._minmax_entry
|
||||
_largest_pos_float .byte 255,127,255,255,255 ; largest positive float
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sum_f .proc
|
||||
lda #<FL_ZERO
|
||||
ldy #>FL_ZERO
|
||||
jsr MOVFM
|
||||
jsr prog8_lib.pop_array_and_lengthmin1Y
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
- sty c64.SCRATCH_ZPREG
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
ldy c64.SCRATCH_ZPWORD1+1
|
||||
jsr FADD
|
||||
ldy c64.SCRATCH_ZPREG
|
||||
dey
|
||||
cpy #255
|
||||
beq +
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
clc
|
||||
adc #5
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
bcc -
|
||||
inc c64.SCRATCH_ZPWORD1+1
|
||||
bne -
|
||||
+ jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
sign_f .proc
|
||||
jsr pop_float_fac1
|
||||
jsr SIGN
|
||||
sta c64.ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
File diff suppressed because it is too large
Load Diff
453
compiler/res/prog8lib/conv.p8
Normal file
453
compiler/res/prog8lib/conv.p8
Normal file
@ -0,0 +1,453 @@
|
||||
; Prog8 definitions for number conversions routines.
|
||||
;
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
|
||||
conv {
|
||||
|
||||
; ----- 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
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
; ----- utility functions ----
|
||||
|
||||
|
||||
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)
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub str2ubyte(str string @ AY) clobbers(Y) -> ubyte @A {
|
||||
; -- returns 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)
|
||||
%asm {{
|
||||
jmp str2uword
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str2byte(str string @ AY) clobbers(Y) -> ubyte @A {
|
||||
; -- returns 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)
|
||||
%asm {{
|
||||
jmp 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)
|
||||
%asm {{
|
||||
_result = P8ZP_SCRATCH_W2
|
||||
sta _mod+1
|
||||
sty _mod+2
|
||||
ldy #0
|
||||
sty _result
|
||||
sty _result+1
|
||||
_mod lda $ffff,y ; modified
|
||||
sec
|
||||
sbc #48
|
||||
bpl +
|
||||
_done ; return result
|
||||
lda _result
|
||||
ldy _result+1
|
||||
rts
|
||||
+ cmp #10
|
||||
bcs _done
|
||||
; add digit to result
|
||||
pha
|
||||
jsr _result_times_10
|
||||
pla
|
||||
clc
|
||||
adc _result
|
||||
sta _result
|
||||
bcc +
|
||||
inc _result+1
|
||||
+ iny
|
||||
bne _mod
|
||||
; never reached
|
||||
|
||||
_result_times_10 ; (W*4 + W)*2
|
||||
lda _result+1
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda _result
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
clc
|
||||
adc _result
|
||||
sta _result
|
||||
lda P8ZP_SCRATCH_REG
|
||||
adc _result+1
|
||||
asl _result
|
||||
rol a
|
||||
sta _result+1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str2word(str string @ AY) -> word @ AY {
|
||||
; -- returns the signed word value of the string number argument in AY
|
||||
; the number may be preceded by a + or - sign but may NOT contain spaces
|
||||
; (any non-digit character will terminate the number string that is parsed)
|
||||
%asm {{
|
||||
_result = P8ZP_SCRATCH_W2
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
sty _result
|
||||
sty _result+1
|
||||
sty _negative
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
cmp #'+'
|
||||
bne +
|
||||
iny
|
||||
+ cmp #'-'
|
||||
bne _parse
|
||||
inc _negative
|
||||
iny
|
||||
_parse lda (P8ZP_SCRATCH_W1),y
|
||||
sec
|
||||
sbc #48
|
||||
bpl _digit
|
||||
_done ; return result
|
||||
lda _negative
|
||||
beq +
|
||||
sec
|
||||
lda #0
|
||||
sbc _result
|
||||
sta _result
|
||||
lda #0
|
||||
sbc _result+1
|
||||
sta _result+1
|
||||
+ lda _result
|
||||
ldy _result+1
|
||||
rts
|
||||
_digit cmp #10
|
||||
bcs _done
|
||||
; add digit to result
|
||||
pha
|
||||
jsr str2uword._result_times_10
|
||||
pla
|
||||
clc
|
||||
adc _result
|
||||
sta _result
|
||||
bcc +
|
||||
inc _result+1
|
||||
+ iny
|
||||
bne _parse
|
||||
; never reached
|
||||
_negative .byte 0
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub hex2uword(str string @ AY) -> uword @AY {
|
||||
; -- hexadecimal string with or without '$' to uword.
|
||||
; string may be in petscii or c64-screencode encoding.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #0
|
||||
sty P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
_loop ldy #0
|
||||
sty P8ZP_SCRATCH_B1
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
beq _stop
|
||||
cmp #'$'
|
||||
beq _skip
|
||||
cmp #7
|
||||
bcc _add_nine
|
||||
cmp #'9'
|
||||
beq _calc
|
||||
bcs _add_nine
|
||||
_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
|
||||
_skip inc P8ZP_SCRATCH_W2
|
||||
bne _loop
|
||||
inc P8ZP_SCRATCH_W2+1
|
||||
bne _loop
|
||||
_stop lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
rts
|
||||
_add_nine ldy #9
|
||||
sty P8ZP_SCRATCH_B1
|
||||
bne _calc
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub bin2uword(str string @ AY) -> uword @AY {
|
||||
; -- binary string with or without '%' to uword.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #0
|
||||
sty P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
_loop lda (P8ZP_SCRATCH_W2),y
|
||||
beq _stop
|
||||
cmp #'%'
|
||||
beq +
|
||||
asl P8ZP_SCRATCH_W1
|
||||
rol P8ZP_SCRATCH_W1+1
|
||||
and #1
|
||||
ora P8ZP_SCRATCH_W1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
+ inc P8ZP_SCRATCH_W2
|
||||
bne _loop
|
||||
inc P8ZP_SCRATCH_W2+1
|
||||
bne _loop
|
||||
_stop lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
154
compiler/res/prog8lib/cx16/floats.p8
Normal file
154
compiler/res/prog8lib/cx16/floats.p8
Normal file
@ -0,0 +1,154 @@
|
||||
; 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 floating point related functions ----
|
||||
|
||||
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 $fe33 = CONUPK(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac2
|
||||
romsub $fe36 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
|
||||
romsub $fe3c = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
|
||||
romsub $fe3f = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
|
||||
romsub $fe42 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 (remainder in fac2) mind the order of the operands
|
||||
|
||||
romsub $fe48 = MOVFM(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac1
|
||||
romsub $fe4b = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
|
||||
romsub $fe4e = MOVFA() clobbers(A,X) ; copy fac2 to fac1
|
||||
romsub $fe51 = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded)
|
||||
romsub $fe54 = MOVEF() clobbers(A,X) ; copy fac1 to fac2
|
||||
romsub $fe5a = SIGN() -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
|
||||
romsub $fe5d = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
|
||||
romsub $fe60 = FREADSA(byte value @ A) clobbers(A,X,Y) ; 8 bit signed A -> float in fac1
|
||||
romsub $fe6c = ABS() ; fac1 = ABS(fac1)
|
||||
romsub $fe6f = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
|
||||
romsub $fe78 = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc
|
||||
romsub $fe7e = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
|
||||
romsub $fe81 = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
|
||||
romsub $fe8a = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
|
||||
romsub $fe8d = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
|
||||
romsub $fe93 = NEGOP() clobbers(A) ; switch the sign of fac1
|
||||
romsub $fe96 = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
|
||||
romsub $fe9f = RND2(byte value @A) clobbers(A,X,Y) ; fac1 = RND(A) float random number generator
|
||||
romsub $fea2 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
|
||||
romsub $fea5 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
|
||||
romsub $fea8 = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
|
||||
romsub $feab = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
|
||||
romsub $feae = 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 P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_B1
|
||||
tya
|
||||
ldy P8ZP_SCRATCH_W2
|
||||
jsr GIVAYF ; load it as signed... correct afterwards
|
||||
lda P8ZP_SCRATCH_B1
|
||||
bpl +
|
||||
lda #<_flt65536
|
||||
ldy #>_flt65536
|
||||
jsr FADD
|
||||
+ plx
|
||||
rts
|
||||
_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_W2
|
||||
tya
|
||||
ldy P8ZP_SCRATCH_W2
|
||||
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 {{
|
||||
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", ""
|
||||
|
||||
}
|
164
compiler/res/prog8lib/cx16/graphics.p8
Normal file
164
compiler/res/prog8lib/cx16/graphics.p8
Normal file
@ -0,0 +1,164 @@
|
||||
%target cx16
|
||||
%import syslib
|
||||
%import textio
|
||||
|
||||
; bitmap pixel graphics module for the CommanderX16
|
||||
; wraps the graphics functions that are in ROM.
|
||||
; only black/white monchrome 320x200 for now.
|
||||
|
||||
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.r0 = 0
|
||||
cx16.GRAPH_init()
|
||||
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) ; TODO 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.r0 = x1
|
||||
cx16.r1 = y1
|
||||
cx16.r2 = x2
|
||||
cx16.r3 = y2
|
||||
cx16.GRAPH_draw_line()
|
||||
}
|
||||
|
||||
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) ; TODO currently is not implemented on cx16, does a BRK
|
||||
|
||||
; Midpoint algorithm
|
||||
ubyte @zp xx = radius
|
||||
ubyte @zp yy = 0
|
||||
byte @zp decisionOver2 = 1-xx as byte
|
||||
|
||||
while xx>=yy {
|
||||
cx16.r0 = xcenter + xx
|
||||
cx16.r1 = ycenter + yy
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r0 = xcenter - xx
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r0 = xcenter + xx
|
||||
cx16.r1 = ycenter - yy
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r0 = xcenter - xx
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r0 = xcenter + yy
|
||||
cx16.r1 = ycenter + xx
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r0 = xcenter - yy
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r0 = xcenter + yy
|
||||
cx16.r1 = ycenter - xx
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r0 = xcenter - yy
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
yy++
|
||||
if decisionOver2<=0 {
|
||||
decisionOver2 += 2*yy+1
|
||||
} else {
|
||||
xx--
|
||||
decisionOver2 += 2*(yy-xx)+1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub disc(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(true) ; TODO currently is not implemented on cx16, does a BRK
|
||||
|
||||
ubyte xx = radius
|
||||
ubyte yy = 0
|
||||
byte decisionOver2 = 1-xx as byte
|
||||
|
||||
while xx>=yy {
|
||||
ubyte ycenter_plus_yy = ycenter + yy
|
||||
ubyte ycenter_min_yy = ycenter - yy
|
||||
ubyte ycenter_plus_xx = ycenter + xx
|
||||
ubyte ycenter_min_xx = ycenter - xx
|
||||
uword @zp plotx
|
||||
|
||||
for plotx in xcenter to xcenter+xx {
|
||||
cx16.r0 = plotx
|
||||
cx16.r1 = ycenter_plus_yy
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r1 = ycenter_min_yy
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
}
|
||||
for plotx in xcenter-xx to xcenter-1 {
|
||||
cx16.r0 = plotx
|
||||
cx16.r1 = ycenter_plus_yy
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r1 = ycenter_min_yy
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
}
|
||||
for plotx in xcenter to xcenter+yy {
|
||||
cx16.r0 = plotx
|
||||
cx16.r1 = ycenter_plus_xx
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r1 = ycenter_min_xx
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
}
|
||||
for plotx in xcenter-yy to xcenter {
|
||||
cx16.r0 = plotx
|
||||
cx16.r1 = ycenter_plus_xx
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r1 = ycenter_min_xx
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
}
|
||||
yy++
|
||||
if decisionOver2<=0
|
||||
decisionOver2 += 2*yy+1
|
||||
else {
|
||||
xx--
|
||||
decisionOver2 += 2*(yy-xx)+1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub plot(uword plotx, ubyte ploty) {
|
||||
cx16.r0 = plotx
|
||||
cx16.r1 = ploty
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
}
|
||||
}
|
||||
|
||||
|
291
compiler/res/prog8lib/cx16/syslib.p8
Normal file
291
compiler/res/prog8lib/cx16/syslib.p8
Normal file
@ -0,0 +1,291 @@
|
||||
; 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
|
||||
romsub $FF9C = MEMBOT(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
|
||||
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
|
||||
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
|
||||
romsub $FFA5 = ACPTR() -> ubyte @ A ; (alias: IECIN) input byte from serial bus
|
||||
romsub $FFA8 = CIOUT(ubyte databyte @ A) ; (alias: IECOUT) output byte to serial bus
|
||||
romsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK
|
||||
romsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN
|
||||
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
|
||||
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
|
||||
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
|
||||
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte address @ Y) ; set logical file parameters
|
||||
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
|
||||
romsub $FFC0 = OPEN() clobbers(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, ubyte @ X, ubyte @ Y ; (via 816 ($330)) load from device
|
||||
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
||||
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
||||
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock
|
||||
romsub $FFE1 = STOP() clobbers(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
|
||||
|
||||
}
|
||||
|
||||
cx16 {
|
||||
|
||||
; 65c02 hardware vectors:
|
||||
&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 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
|
||||
|
||||
; 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 = $9f60 ;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 = $9f70 ;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
|
||||
|
||||
|
||||
; ---- 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() clobbers(A, X, Y) ; args: r0, r1, r2, r3L
|
||||
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) ; outout args: r0, r1, r2, r3L
|
||||
|
||||
; 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() clobbers(A,X,Y) ; uses vectors=r0
|
||||
romsub $ff23 = GRAPH_clear() clobbers(A,X,Y)
|
||||
romsub $ff26 = GRAPH_set_window() clobbers(A,X,Y) ; uses x=r0, y=r1, width=r2, height=r3
|
||||
romsub $ff29 = GRAPH_set_colors(ubyte stroke @A, ubyte fill @X, ubyte background @Y) clobbers (A,X,Y)
|
||||
romsub $ff2c = GRAPH_draw_line() clobbers(A,X,Y) ; uses x1=r0, y1=r1, x2=r2, y2=r3
|
||||
romsub $ff2f = GRAPH_draw_rect(ubyte fill @Pc) clobbers(A,X,Y) ; uses x=r0, y=r1, width=r2, height=r3, cornerradius=r4
|
||||
romsub $ff32 = GRAPH_move_rect() clobbers(A,X,Y) ; uses sx=r0, sy=r1, tx=r2, ty=r3, width=r4, height=r5
|
||||
romsub $ff35 = GRAPH_draw_oval(ubyte fill @Pc) clobbers(A,X,Y) ; uses x=r0, y=r1, width=r2, height=r3
|
||||
romsub $ff38 = GRAPH_draw_image() clobbers(A,X,Y) ; uses x=r0, y=r1, ptr=r2, width=r3, height=r4
|
||||
romsub $ff3b = GRAPH_set_font() clobbers(A,X,Y) ; uses ptr=r0
|
||||
romsub $ff3e = GRAPH_get_char_size(ubyte baseline @A, ubyte width @X, ubyte height_or_style @Y, ubyte is_control @Pc) clobbers(A,X,Y)
|
||||
romsub $ff41 = GRAPH_put_char(ubyte char @A) clobbers(A,X,Y) ; uses x=r0, y=r1
|
||||
|
||||
; framebuffer
|
||||
romsub $fef6 = FB_init() clobbers(A,X,Y)
|
||||
romsub $fef9 = FB_get_info() clobbers(X,Y) -> byte @A ; also outputs width=r0, height=r1
|
||||
romsub $fefc = FB_set_palette(ubyte index @A, ubyte bytecount @X) clobbers(A,X,Y) ; also uses pointer=r0
|
||||
romsub $feff = FB_cursor_position() clobbers(A,X,Y) ; uses x=r0, y=r1
|
||||
romsub $ff02 = FB_cursor_next_line() clobbers(A,X,Y) ; uses x=r0
|
||||
romsub $ff05 = FB_get_pixel() clobbers(X,Y) -> ubyte @A
|
||||
romsub $ff08 = FB_get_pixels() clobbers(A,X,Y) ; uses ptr=r0, count=r1
|
||||
romsub $ff0b = FB_set_pixel(ubyte color @A) clobbers(A,X,Y)
|
||||
romsub $ff0e = FB_set_pixels() clobbers(A,X,Y) ; uses ptr=r0, count=r1
|
||||
romsub $ff11 = FB_set_8_pixels(ubyte pattern @A, ubyte color @X) clobbers(A,X,Y)
|
||||
romsub $ff14 = FB_set_8_pixels_opaque(ubyte pattern @A, ubyte color1 @X, ubyte color2 @Y) clobbers(A,X,Y) ; also uses mask=r0L
|
||||
romsub $ff17 = FB_fill_pixels(ubyte color @A) clobbers(A,X,Y) ; also uses count=r0, step=r1
|
||||
romsub $ff1a = FB_filter_pixels() clobbers(A,X,Y) ; uses ptr=r0, count=r1
|
||||
romsub $ff1d = FB_move_pixels() clobbers(A,X,Y) ; uses sx=r0, sy=r1, tx=r2, ty=r3, count=r4
|
||||
|
||||
; misc
|
||||
romsub $fef0 = sprite_set_image(ubyte number @A, ubyte width @X, ubyte height @Y, ubyte apply_mask @Pc) clobbers(A,X,Y) -> ubyte @Pc ; also uses pixels=r0, mask=r1, bpp=r2L
|
||||
romsub $fef3 = sprite_set_position(ubyte number @A) clobbers(A,X,Y) ; also uses x=r0 and y=r1
|
||||
romsub $fee4 = memory_fill(ubyte value @A) clobbers(A,X,Y) ; uses address=r0, num_bytes=r1
|
||||
romsub $fee7 = memory_copy() clobbers(A,X,Y) ; uses source=r0, target=r1, num_bytes=r2
|
||||
romsub $feea = memory_crc() clobbers(A,X,Y) ; uses address=r0, num_bytes=r1 result->r2
|
||||
romsub $feed = memory_decompress() clobbers(A,X,Y) ; uses input=r0, output=r1 result->r1
|
||||
romsub $fedb = console_init() clobbers(A,X,Y) ; uses x=r0, y=r1, width=r2, height=r3
|
||||
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() clobbers(A,X,Y) ; uses ptr=r0, width=r1, height=r2
|
||||
romsub $fed5 = console_set_paging_message() clobbers(A,X,Y) ; uses messageptr=r0
|
||||
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 ----
|
||||
|
||||
asmsub init_system() {
|
||||
; Initializes the machine to a sane starting state.
|
||||
; Called automatically by the loader program logic.
|
||||
%asm {{
|
||||
sei
|
||||
cld
|
||||
;stz $00
|
||||
;stz $01
|
||||
;stz d1prb ; select rom bank 0
|
||||
lda #$80
|
||||
sta VERA_CTRL
|
||||
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 reset_system() {
|
||||
; Soft-reset the system back to Basic prompt.
|
||||
%asm {{
|
||||
sei
|
||||
lda #14
|
||||
sta $01
|
||||
stz cx16.d1prb ; bank the kernal in
|
||||
jmp (cx16.RESET_VEC)
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
699
compiler/res/prog8lib/cx16/textio.p8
Normal file
699
compiler/res/prog8lib/cx16/textio.p8
Normal file
@ -0,0 +1,699 @@
|
||||
; 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() {
|
||||
clear_screenchars(' ')
|
||||
}
|
||||
|
||||
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 (ubyte dummy @ 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 is a dummy on the cx16
|
||||
%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 (ubyte dummy @ 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 is a dummy on the cx16
|
||||
%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 (ubyte dummy @ Pc) clobbers(A, Y) {
|
||||
; ---- scroll the whole screen 1 character up
|
||||
; contents of the bottom row are unchanged, you should refill/clear this yourself
|
||||
; Carry flag is a dummy on the cx16
|
||||
%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 (ubyte dummy @ Pc) clobbers(A, Y) {
|
||||
; ---- scroll the whole screen 1 character down
|
||||
; contents of the top row are unchanged, you should refill/clear this yourself
|
||||
; Carry flag is a dummy on the cx16
|
||||
%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
|
||||
jmp _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
|
||||
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 {{
|
||||
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
|
||||
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 {{
|
||||
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
|
||||
+ 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) {
|
||||
; ---- 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
|
||||
%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
|
||||
%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
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) {
|
||||
; ---- safe wrapper around PLOT kernel 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
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
29
compiler/res/prog8lib/cx16logo.p8
Normal file
29
compiler/res/prog8lib/cx16logo.p8
Normal file
@ -0,0 +1,29 @@
|
||||
%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.chrout('\n')
|
||||
}
|
||||
|
||||
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"
|
||||
]
|
||||
}
|
162
compiler/res/prog8lib/diskio.p8
Normal file
162
compiler/res/prog8lib/diskio.p8
Normal file
@ -0,0 +1,162 @@
|
||||
%import textio
|
||||
%import syslib
|
||||
|
||||
; Note: this code is compatible with C64 and CX16.
|
||||
|
||||
diskio {
|
||||
|
||||
|
||||
sub directory(ubyte drivenumber) -> byte {
|
||||
; -- Shows the directory contents of disk drive 8-11 (provide as argument).
|
||||
|
||||
c64.SETNAM(1, "$")
|
||||
c64.SETLFS(1, drivenumber, 0)
|
||||
void c64.OPEN() ; open 1,8,0,"$"
|
||||
if_cs
|
||||
goto io_error
|
||||
void c64.CHKIN(1) ; use #1 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 {
|
||||
txt.print_uw(mkword(c64.CHRIN(), c64.CHRIN()))
|
||||
txt.chrout(' ')
|
||||
ubyte @zp char
|
||||
do {
|
||||
char = c64.CHRIN()
|
||||
txt.chrout(char)
|
||||
} until char==0
|
||||
txt.chrout('\n')
|
||||
void c64.CHRIN() ; skip 2 bytes
|
||||
void c64.CHRIN()
|
||||
status = c64.READST()
|
||||
void c64.STOP()
|
||||
if_nz
|
||||
break
|
||||
}
|
||||
|
||||
io_error:
|
||||
status = c64.READST()
|
||||
c64.CLRCHN() ; restore default i/o devices
|
||||
c64.CLOSE(1)
|
||||
|
||||
if status and status != 64 { ; 64=end of file
|
||||
txt.print("\ni/o error, status: ")
|
||||
txt.print_ub(status)
|
||||
txt.chrout('\n')
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
sub status(ubyte drivenumber) {
|
||||
; -- display the disk drive's current status message
|
||||
c64.SETNAM(0, $0000)
|
||||
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()
|
||||
txt.chrout(c64.CHRIN())
|
||||
|
||||
io_error:
|
||||
c64.CLRCHN() ; restore default i/o devices
|
||||
c64.CLOSE(15)
|
||||
}
|
||||
|
||||
|
||||
sub save(ubyte drivenumber, uword filenameptr, uword address, uword size) -> byte {
|
||||
c64.SETNAM(strlen(filenameptr), filenameptr)
|
||||
c64.SETLFS(1, drivenumber, 0)
|
||||
uword end_address = address + size
|
||||
|
||||
%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
|
||||
return c64.READST()==0
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
sub load(ubyte drivenumber, uword filenameptr, uword address_override) -> uword {
|
||||
c64.SETNAM(strlen(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
|
||||
}}
|
||||
|
||||
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
|
||||
ubyte flen = strlen(filenameptr)
|
||||
filename[0] = 's'
|
||||
filename[1] = ':'
|
||||
memcopy(filenameptr, &filename+2, flen+1)
|
||||
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
|
||||
ubyte flen_old = strlen(oldfileptr)
|
||||
ubyte flen_new = strlen(newfileptr)
|
||||
filename[0] = 'r'
|
||||
filename[1] = ':'
|
||||
memcopy(newfileptr, &filename+2, flen_new)
|
||||
filename[flen_new+2] = '='
|
||||
memcopy(oldfileptr, &filename+3+flen_new, flen_old+1)
|
||||
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
@ -4,8 +4,6 @@
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
%import c64lib
|
||||
|
||||
math {
|
||||
%asminclude "library:math.asm", ""
|
||||
}
|
||||
|
1348
compiler/res/prog8lib/prog8_funcs.asm
Normal file
1348
compiler/res/prog8lib/prog8_funcs.asm
Normal file
File diff suppressed because it is too large
Load Diff
1074
compiler/res/prog8lib/prog8_lib.asm
Normal file
1074
compiler/res/prog8lib/prog8_lib.asm
Normal file
File diff suppressed because it is too large
Load Diff
@ -4,8 +4,7 @@
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
%import c64lib
|
||||
|
||||
prog8_lib {
|
||||
%asminclude "library:prog8lib.asm", ""
|
||||
%asminclude "library:prog8_lib.asm", ""
|
||||
%asminclude "library:prog8_funcs.asm", ""
|
||||
}
|
File diff suppressed because it is too large
Load Diff
48
compiler/res/prog8lib/test_stack.p8
Normal file
48
compiler/res/prog8lib/test_stack.p8
Normal file
@ -0,0 +1,48 @@
|
||||
%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.90
|
||||
5.2
|
||||
|
@ -4,12 +4,10 @@ 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.compiler.target.CompilationTarget
|
||||
import prog8.compiler.target.c64.C64MachineDefinition
|
||||
import prog8.compiler.target.c64.Petscii
|
||||
import prog8.compiler.target.c64.codegen.AsmGen
|
||||
import prog8.parser.ParsingFailedError
|
||||
import java.io.IOException
|
||||
import java.nio.file.FileSystems
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.StandardWatchEventKinds
|
||||
@ -35,12 +33,14 @@ fun pathFrom(stringPath: String, vararg rest: String): Path = FileSystems.getDe
|
||||
|
||||
private fun compileMain(args: Array<String>) {
|
||||
val cli = CommandLineInterface("prog8compiler")
|
||||
val startEmulator by cli.flagArgument("-emu", "auto-start the Vice C-64 emulator after successful compilation")
|
||||
val startEmulator by cli.flagArgument("-emu", "auto-start emulator after successful compilation")
|
||||
val outputDir by cli.flagValueArgument("-out", "directory", "directory for output files instead of current directory", ".")
|
||||
val dontWriteAssembly by cli.flagArgument("-noasm", "don't create assembly code")
|
||||
val dontOptimize by cli.flagArgument("-noopt", "don't perform any optimizations")
|
||||
val watchMode by cli.flagArgument("-watch", "continuous compilation mode (watches for file changes), greatly increases compilation speed")
|
||||
val compilationTarget by cli.flagValueArgument("-target", "compilertarget", "target output of the compiler, currently only 'c64' (C64 6502 assembly) available", "c64")
|
||||
val slowCodegenWarnings by cli.flagArgument("-slowwarn", "show debug warnings about slow/problematic assembly code generation")
|
||||
val compilationTarget by cli.flagValueArgument("-target", "compilertarget",
|
||||
"target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available", C64Target.name)
|
||||
val moduleFiles by cli.positionalArgumentsList("modules", "main module file(s) to compile", minArgs = 1)
|
||||
|
||||
try {
|
||||
@ -49,58 +49,48 @@ private fun compileMain(args: Array<String>) {
|
||||
exitProcess(1)
|
||||
}
|
||||
|
||||
when(compilationTarget) {
|
||||
"c64" -> {
|
||||
with(CompilationTarget) {
|
||||
name = "c64"
|
||||
machine = C64MachineDefinition
|
||||
encodeString = { str, altEncoding ->
|
||||
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||
}
|
||||
decodeString = { bytes, altEncoding ->
|
||||
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||
}
|
||||
asmGenerator = ::AsmGen
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
System.err.println("invalid compilation target")
|
||||
exitProcess(1)
|
||||
}
|
||||
}
|
||||
|
||||
val outputPath = pathFrom(outputDir)
|
||||
if(!outputPath.toFile().isDirectory) {
|
||||
System.err.println("Output path doesn't exist")
|
||||
exitProcess(1)
|
||||
}
|
||||
|
||||
if(watchMode && moduleFiles.size<=1) {
|
||||
if(watchMode) {
|
||||
val watchservice = FileSystems.getDefault().newWatchService()
|
||||
|
||||
while(true) {
|
||||
val filepath = pathFrom(moduleFiles.single()).normalize()
|
||||
println("Continuous watch mode active. Main module: $filepath")
|
||||
println("Continuous watch mode active. Modules: $moduleFiles")
|
||||
val results = mutableListOf<CompilationResult>()
|
||||
for(filepathRaw in moduleFiles) {
|
||||
val filepath = pathFrom(filepathRaw).normalize()
|
||||
val compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, slowCodegenWarnings, compilationTarget, outputPath)
|
||||
results.add(compilationResult)
|
||||
}
|
||||
|
||||
try {
|
||||
val compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, outputDir=outputPath)
|
||||
println("Imported files (now watching:)")
|
||||
for (importedFile in compilationResult.importedFiles) {
|
||||
print(" ")
|
||||
println(importedFile)
|
||||
importedFile.parent.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY)
|
||||
}
|
||||
println("[${LocalDateTime.now().withNano(0)}] Waiting for file changes.")
|
||||
val allImportedFiles = results.flatMap { it.importedFiles }
|
||||
println("Imported files (now watching:)")
|
||||
for (importedFile in allImportedFiles) {
|
||||
print(" ")
|
||||
println(importedFile)
|
||||
val watchDir = importedFile.parent ?: Path.of(".")
|
||||
watchDir.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY)
|
||||
}
|
||||
println("[${LocalDateTime.now().withNano(0)}] Waiting for file changes.")
|
||||
|
||||
var recompile=false
|
||||
while(!recompile) {
|
||||
val event = watchservice.take()
|
||||
for(changed in event.pollEvents()) {
|
||||
for (changed in event.pollEvents()) {
|
||||
val changedPath = changed.context() as Path
|
||||
println(" change detected: $changedPath")
|
||||
if(allImportedFiles.any { it.fileName == changedPath.fileName }) {
|
||||
println(" change detected: $changedPath")
|
||||
recompile = true
|
||||
}
|
||||
}
|
||||
event.reset()
|
||||
println("\u001b[H\u001b[2J") // clear the screen
|
||||
} catch (x: Exception) {
|
||||
throw x
|
||||
}
|
||||
|
||||
println("\u001b[H\u001b[2J") // clear the screen
|
||||
}
|
||||
|
||||
} else {
|
||||
@ -108,7 +98,7 @@ private fun compileMain(args: Array<String>) {
|
||||
val filepath = pathFrom(filepathRaw).normalize()
|
||||
val compilationResult: CompilationResult
|
||||
try {
|
||||
compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, outputDir=outputPath)
|
||||
compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, slowCodegenWarnings, compilationTarget, outputPath)
|
||||
if(!compilationResult.success)
|
||||
exitProcess(1)
|
||||
} catch (x: ParsingFailedError) {
|
||||
@ -121,20 +111,7 @@ private fun compileMain(args: Array<String>) {
|
||||
if (compilationResult.programName.isEmpty())
|
||||
println("\nCan't start emulator because no program was assembled.")
|
||||
else if(startEmulator) {
|
||||
for(emulator in listOf("x64sc", "x64")) {
|
||||
println("\nStarting C-64 emulator $emulator...")
|
||||
val cmdline = listOf(emulator, "-silent", "-moncommands", "${compilationResult.programName}.vice-mon-list",
|
||||
"-autostartprgmode", "1", "-autostart-warp", "-autostart", compilationResult.programName + ".prg")
|
||||
val processb = ProcessBuilder(cmdline).inheritIO()
|
||||
val process: Process
|
||||
try {
|
||||
process=processb.start()
|
||||
} catch(x: IOException) {
|
||||
continue // try the next emulator executable
|
||||
}
|
||||
process.waitFor()
|
||||
break
|
||||
}
|
||||
CompilationTarget.instance.machine.launchEmulator(compilationResult.programName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -53,12 +53,14 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
}
|
||||
|
||||
override fun visit(expr: BinaryExpression) {
|
||||
output("(")
|
||||
expr.left.accept(this)
|
||||
if(expr.operator.any { it.isLetter() })
|
||||
output(" ${expr.operator} ")
|
||||
else
|
||||
output(expr.operator)
|
||||
expr.right.accept(this)
|
||||
output(")")
|
||||
}
|
||||
|
||||
override fun visit(directive: Directive) {
|
||||
@ -85,7 +87,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
DataType.ARRAY_W -> "word["
|
||||
DataType.ARRAY_F -> "float["
|
||||
DataType.STRUCT -> "" // the name of the struct is enough
|
||||
else -> "?????2"
|
||||
else -> "?????"
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,15 +104,25 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
}
|
||||
|
||||
override fun visit(decl: VarDecl) {
|
||||
|
||||
// if the vardecl is a parameter of a subroutine, don't output it again
|
||||
val paramNames = (decl.definingScope() as? Subroutine)?.parameters?.map { it.name }
|
||||
if(paramNames!=null && decl.name in paramNames)
|
||||
return
|
||||
|
||||
when(decl.type) {
|
||||
VarDeclType.VAR -> {}
|
||||
VarDeclType.CONST -> output("const ")
|
||||
VarDeclType.MEMORY -> output("&")
|
||||
}
|
||||
output(decl.struct?.name ?: "")
|
||||
|
||||
if(decl.datatype==DataType.STRUCT && decl.struct!=null)
|
||||
output(decl.struct!!.name)
|
||||
|
||||
output(datatypeString(decl.datatype))
|
||||
if(decl.arraysize!=null) {
|
||||
decl.arraysize!!.index.accept(this)
|
||||
decl.arraysize!!.indexNum?.accept(this)
|
||||
decl.arraysize!!.indexVar?.accept(this)
|
||||
}
|
||||
if(decl.isArray)
|
||||
output("]")
|
||||
@ -131,10 +143,9 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
for(param in subroutine.parameters.zip(subroutine.asmParameterRegisters)) {
|
||||
val reg =
|
||||
when {
|
||||
param.second.stack -> "stack"
|
||||
param.second.registerOrPair!=null -> param.second.registerOrPair.toString()
|
||||
param.second.statusflag!=null -> param.second.statusflag.toString()
|
||||
else -> "?????1"
|
||||
else -> "?????"
|
||||
}
|
||||
output("${datatypeString(param.first.type)} ${param.first.name} @$reg")
|
||||
if(param.first!==subroutine.parameters.last())
|
||||
@ -177,8 +188,6 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
|
||||
private fun outputStatements(statements: List<Statement>) {
|
||||
for(stmt in statements) {
|
||||
if(stmt is VarDecl && stmt.autogeneratedDontRemove)
|
||||
continue // skip autogenerated decls (to avoid generating a newline)
|
||||
outputi("")
|
||||
stmt.accept(this)
|
||||
output("\n")
|
||||
@ -283,12 +292,19 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
}
|
||||
|
||||
override fun visit(assignment: Assignment) {
|
||||
assignment.target.accept(this)
|
||||
if (assignment.aug_op != null)
|
||||
output(" ${assignment.aug_op} ")
|
||||
else
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
if(binExpr!=null && binExpr.left isSameAs assignment.target
|
||||
&& binExpr.operator !in setOf("and", "or", "xor")
|
||||
&& binExpr.operator !in comparisonOperators) {
|
||||
// we only support the inplace assignments of the form A = A <operator> <value>
|
||||
assignment.target.accept(this)
|
||||
output(" ${binExpr.operator}= ")
|
||||
binExpr.right.accept(this)
|
||||
} else {
|
||||
assignment.target.accept(this)
|
||||
output(" = ")
|
||||
assignment.value.accept(this)
|
||||
assignment.value.accept(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(postIncrDecr: PostIncrDecr) {
|
||||
@ -296,20 +312,13 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
output(postIncrDecr.operator)
|
||||
}
|
||||
|
||||
override fun visit(contStmt: Continue) {
|
||||
output("continue")
|
||||
}
|
||||
|
||||
override fun visit(breakStmt: Break) {
|
||||
output("break")
|
||||
}
|
||||
|
||||
override fun visit(forLoop: ForLoop) {
|
||||
output("for ")
|
||||
if(forLoop.loopRegister!=null)
|
||||
output(forLoop.loopRegister.toString())
|
||||
else
|
||||
forLoop.loopVar!!.accept(this)
|
||||
forLoop.loopVar.accept(this)
|
||||
output(" in ")
|
||||
forLoop.iterable.accept(this)
|
||||
output(" ")
|
||||
@ -323,16 +332,18 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
whileLoop.body.accept(this)
|
||||
}
|
||||
|
||||
override fun visit(foreverLoop: ForeverLoop) {
|
||||
output("forever ")
|
||||
foreverLoop.body.accept(this)
|
||||
}
|
||||
|
||||
override fun visit(repeatLoop: RepeatLoop) {
|
||||
output("repeat ")
|
||||
repeatLoop.iterations?.accept(this)
|
||||
output(" ")
|
||||
repeatLoop.body.accept(this)
|
||||
}
|
||||
|
||||
override fun visit(untilLoop: UntilLoop) {
|
||||
output("do ")
|
||||
untilLoop.body.accept(this)
|
||||
output(" until ")
|
||||
repeatLoop.untilCondition.accept(this)
|
||||
untilLoop.condition.accept(this)
|
||||
}
|
||||
|
||||
override fun visit(returnStmt: Return) {
|
||||
@ -341,19 +352,16 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
}
|
||||
|
||||
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
|
||||
arrayIndexedExpression.identifier.accept(this)
|
||||
arrayIndexedExpression.arrayvar.accept(this)
|
||||
output("[")
|
||||
arrayIndexedExpression.arrayspec.index.accept(this)
|
||||
arrayIndexedExpression.indexer.indexNum?.accept(this)
|
||||
arrayIndexedExpression.indexer.indexVar?.accept(this)
|
||||
output("]")
|
||||
}
|
||||
|
||||
override fun visit(assignTarget: AssignTarget) {
|
||||
if(assignTarget.register!=null)
|
||||
output(assignTarget.register.toString())
|
||||
else {
|
||||
assignTarget.memoryAddress?.accept(this)
|
||||
assignTarget.identifier?.accept(this)
|
||||
}
|
||||
assignTarget.memoryAddress?.accept(this)
|
||||
assignTarget.identifier?.accept(this)
|
||||
assignTarget.arrayindexed?.accept(this)
|
||||
}
|
||||
|
||||
@ -394,10 +402,6 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
outputlni("}}")
|
||||
}
|
||||
|
||||
override fun visit(registerExpr: RegisterExpr) {
|
||||
output(registerExpr.register.toString())
|
||||
}
|
||||
|
||||
override fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder) {
|
||||
output(builtinFunctionStatementPlaceholder.name)
|
||||
}
|
||||
@ -431,12 +435,4 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
whenChoice.statements.accept(this)
|
||||
outputln("")
|
||||
}
|
||||
|
||||
override fun visit(structLv: StructLiteralValue) {
|
||||
outputListMembers(structLv.values.asSequence(), '{', '}')
|
||||
}
|
||||
|
||||
override fun visit(nopStatement: NopStatement) {
|
||||
output("; NOP @ ${nopStatement.position} $nopStatement")
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import prog8.ast.base.*
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModifyingVisitor
|
||||
import prog8.ast.processing.IAstVisitor
|
||||
import prog8.ast.statements.*
|
||||
import prog8.functions.BuiltinFunctions
|
||||
@ -37,6 +36,18 @@ interface Node {
|
||||
throw FatalAstException("scope missing from $this")
|
||||
}
|
||||
|
||||
fun definingBlock(): Block {
|
||||
if(this is Block)
|
||||
return this
|
||||
return findParentNode<Block>(this)!!
|
||||
}
|
||||
|
||||
fun containingStatement(): Statement {
|
||||
if(this is Statement)
|
||||
return this
|
||||
return findParentNode<Statement>(this)!!
|
||||
}
|
||||
|
||||
fun replaceChildNode(node: Node, replacement: Node)
|
||||
}
|
||||
|
||||
@ -45,6 +56,7 @@ interface IFunctionCall {
|
||||
var args: MutableList<Expression>
|
||||
}
|
||||
|
||||
|
||||
interface INameScope {
|
||||
val name: String
|
||||
val position: Position
|
||||
@ -53,32 +65,31 @@ interface INameScope {
|
||||
|
||||
fun linkParents(parent: Node)
|
||||
|
||||
fun subScopes(): Map<String, INameScope> {
|
||||
val subscopes = mutableMapOf<String, INameScope>()
|
||||
fun subScope(name: 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 ForLoop -> if(stmt.body.name==name) return stmt.body
|
||||
is UntilLoop -> if(stmt.body.name==name) return stmt.body
|
||||
is WhileLoop -> if(stmt.body.name==name) return stmt.body
|
||||
is BranchStatement -> {
|
||||
subscopes[stmt.truepart.name] = stmt.truepart
|
||||
if(stmt.elsepart.containsCodeOrVars())
|
||||
subscopes[stmt.elsepart.name] = stmt.elsepart
|
||||
if(stmt.truepart.name==name) return stmt.truepart
|
||||
if(stmt.elsepart.containsCodeOrVars() && stmt.elsepart.name==name) return stmt.elsepart
|
||||
}
|
||||
is IfStatement -> {
|
||||
subscopes[stmt.truepart.name] = stmt.truepart
|
||||
if(stmt.elsepart.containsCodeOrVars())
|
||||
subscopes[stmt.elsepart.name] = stmt.elsepart
|
||||
if(stmt.truepart.name==name) return stmt.truepart
|
||||
if(stmt.elsepart.containsCodeOrVars() && stmt.elsepart.name==name) return stmt.elsepart
|
||||
}
|
||||
is WhenStatement -> {
|
||||
stmt.choices.forEach { subscopes[it.statements.name] = it.statements }
|
||||
val scope = stmt.choices.firstOrNull { it.statements.name==name }
|
||||
if(scope!=null)
|
||||
return scope.statements
|
||||
}
|
||||
is INameScope -> subscopes[stmt.name] = stmt
|
||||
is INameScope -> if(stmt.name==name) return stmt
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
return subscopes
|
||||
return null
|
||||
}
|
||||
|
||||
fun getLabelOrVariable(name: String): Statement? {
|
||||
@ -126,7 +137,7 @@ interface INameScope {
|
||||
for(module in localContext.definingModule().program.modules) {
|
||||
var scope: INameScope? = module
|
||||
for(name in scopedName.dropLast(1)) {
|
||||
scope = scope?.subScopes()?.get(name)
|
||||
scope = scope?.subScope(name)
|
||||
if(scope==null)
|
||||
break
|
||||
}
|
||||
@ -134,19 +145,27 @@ interface INameScope {
|
||||
val result = scope.getLabelOrVariable(scopedName.last())
|
||||
if(result!=null)
|
||||
return result
|
||||
return scope.subScopes()[scopedName.last()] as Statement?
|
||||
return scope.subScope(scopedName.last()) as Statement?
|
||||
}
|
||||
}
|
||||
return null
|
||||
} else {
|
||||
// unqualified name, find the scope the localContext is in, look in that first
|
||||
// unqualified name
|
||||
// special case: the do....until statement can also look INSIDE the anonymous scope
|
||||
if(localContext.parent.parent is UntilLoop) {
|
||||
val symbolFromInnerScope = (localContext.parent.parent as UntilLoop).body.getLabelOrVariable(scopedName[0])
|
||||
if(symbolFromInnerScope!=null)
|
||||
return symbolFromInnerScope
|
||||
}
|
||||
|
||||
// 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?
|
||||
val subscope = localScope.subScope(scopedName[0]) as Statement?
|
||||
if (subscope != null)
|
||||
return subscope
|
||||
// not found in this scope, look one higher up
|
||||
@ -176,8 +195,8 @@ interface INameScope {
|
||||
find(it.truepart)
|
||||
find(it.elsepart)
|
||||
}
|
||||
is UntilLoop -> find(it.body)
|
||||
is RepeatLoop -> find(it.body)
|
||||
is ForeverLoop -> find(it.body)
|
||||
is WhileLoop -> find(it.body)
|
||||
is WhenStatement -> it.choices.forEach { choice->find(choice.statements) }
|
||||
else -> { /* do nothing */ }
|
||||
@ -188,6 +207,30 @@ interface INameScope {
|
||||
find(this)
|
||||
return result
|
||||
}
|
||||
|
||||
fun nextSibling(stmt: Statement): Statement? {
|
||||
val nextIdx = statements.indexOfFirst { it===stmt } + 1
|
||||
return if(nextIdx < statements.size)
|
||||
statements[nextIdx]
|
||||
else
|
||||
null
|
||||
}
|
||||
|
||||
fun previousSibling(stmt: Statement): Statement? {
|
||||
val previousIdx = statements.indexOfFirst { it===stmt } - 1
|
||||
return if(previousIdx>=0)
|
||||
statements[previousIdx]
|
||||
else
|
||||
null
|
||||
}
|
||||
|
||||
fun indexOfChild(stmt: Statement): Int {
|
||||
val idx = statements.indexOfFirst { it===stmt }
|
||||
if(idx>=0)
|
||||
return idx
|
||||
else
|
||||
throw FatalAstException("attempt to find a non-child")
|
||||
}
|
||||
}
|
||||
|
||||
interface IAssignable {
|
||||
@ -212,7 +255,7 @@ class Program(val name: String, val modules: MutableList<Module>): Node {
|
||||
return if(mainBlocks.isEmpty()) {
|
||||
null
|
||||
} else {
|
||||
mainBlocks[0].subScopes()["start"] as Subroutine?
|
||||
mainBlocks[0].subScope("start") as Subroutine?
|
||||
}
|
||||
}
|
||||
|
||||
@ -221,18 +264,19 @@ class Program(val name: String, val modules: MutableList<Module>): Node {
|
||||
override val position: Position = Position.DUMMY
|
||||
override var parent: Node
|
||||
get() = throw FatalAstException("program has no parent")
|
||||
set(value) = throw FatalAstException("can't set parent of program")
|
||||
set(_) = throw FatalAstException("can't set parent of program")
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
modules.forEach {
|
||||
it.linkParents(this)
|
||||
it.linkParents(namespace)
|
||||
}
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(node is Module && replacement is Module)
|
||||
val idx = modules.indexOf(node)
|
||||
val idx = modules.indexOfFirst { it===node }
|
||||
modules[idx] = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
}
|
||||
|
||||
@ -247,7 +291,10 @@ class Module(override val name: String,
|
||||
val importedBy = mutableListOf<Module>()
|
||||
val imports = mutableSetOf<Module>()
|
||||
|
||||
var loadAddress: Int = 0 // can be set with the %address directive
|
||||
val loadAddress: Int by lazy {
|
||||
val address = (statements.singleOrNull { it is Directive && it.directive == "%address" } as? Directive)?.args?.single()?.int ?: 0
|
||||
address
|
||||
}
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -257,13 +304,13 @@ class Module(override val name: String,
|
||||
override fun definingScope(): INameScope = program.namespace
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(node is Statement && replacement is Statement)
|
||||
val idx = statements.indexOf(node)
|
||||
val idx = statements.indexOfFirst { it===node }
|
||||
statements[idx] = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun toString() = "Module(name=$name, pos=$position, lib=$isLibraryModule)"
|
||||
|
||||
fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
@ -272,7 +319,7 @@ class Module(override val name: String,
|
||||
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 val statements = mutableListOf<Statement>() // not used
|
||||
override var parent: Node = ParentSentinel
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
@ -304,11 +351,19 @@ class GlobalNamespace(val modules: List<Module>): Node, INameScope {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// special case: the do....until statement can also look INSIDE the anonymous scope
|
||||
if(localContext.parent.parent is UntilLoop) {
|
||||
val symbolFromInnerScope = (localContext.parent.parent as UntilLoop).body.lookup(scopedName, localContext)
|
||||
if(symbolFromInnerScope!=null)
|
||||
return symbolFromInnerScope
|
||||
}
|
||||
|
||||
// lookup something from the module.
|
||||
return when (val stmt = localContext.definingModule().lookup(scopedName, localContext)) {
|
||||
is Label, is VarDecl, is Block, is Subroutine -> stmt
|
||||
is Label, is VarDecl, is Block, is Subroutine, is StructDecl -> stmt
|
||||
null -> null
|
||||
else -> throw SyntaxError("wrong identifier target for $scopedName: $stmt", stmt.position)
|
||||
else -> throw SyntaxError("invalid identifier target type", stmt.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ private fun ParserRuleContext.toPosition() : Position {
|
||||
val customTokensource = this.start.tokenSource as? CustomLexer
|
||||
val filename =
|
||||
when {
|
||||
customTokensource!=null -> customTokensource.modulePath.fileName.toString()
|
||||
customTokensource!=null -> customTokensource.modulePath.toString()
|
||||
start.tokenSource.sourceName == IntStream.UNKNOWN_SOURCE_NAME -> "@internal@"
|
||||
else -> File(start.inputStream.sourceName).name
|
||||
}
|
||||
@ -161,14 +161,15 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
|
||||
if(vardecl!=null) return vardecl
|
||||
|
||||
assignment()?.let {
|
||||
return Assignment(it.assign_target().toAst(), null, it.expression().toAst(), it.toPosition())
|
||||
return Assignment(it.assign_target().toAst(), it.expression().toAst(), it.toPosition())
|
||||
}
|
||||
|
||||
augassignment()?.let {
|
||||
return Assignment(it.assign_target().toAst(),
|
||||
it.operator.text,
|
||||
it.expression().toAst(),
|
||||
it.toPosition())
|
||||
// replace A += X with A = A + X
|
||||
val target = it.assign_target().toAst()
|
||||
val oper = it.operator.text.substringBefore('=')
|
||||
val expression = BinaryExpression(target.toExpression(), oper, it.expression().toAst(), it.expression().toPosition())
|
||||
return Assignment(it.assign_target().toAst(), expression, it.toPosition())
|
||||
}
|
||||
|
||||
postincrdecr()?.let {
|
||||
@ -205,21 +206,18 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
|
||||
val forloop = forloop()?.toAst()
|
||||
if(forloop!=null) return forloop
|
||||
|
||||
val repeatloop = repeatloop()?.toAst()
|
||||
if(repeatloop!=null) return repeatloop
|
||||
val untilloop = untilloop()?.toAst()
|
||||
if(untilloop!=null) return untilloop
|
||||
|
||||
val whileloop = whileloop()?.toAst()
|
||||
if(whileloop!=null) return whileloop
|
||||
|
||||
val foreverloop = foreverloop()?.toAst()
|
||||
if(foreverloop!=null) return foreverloop
|
||||
val repeatloop = repeatloop()?.toAst()
|
||||
if(repeatloop!=null) return repeatloop
|
||||
|
||||
val breakstmt = breakstmt()?.toAst()
|
||||
if(breakstmt!=null) return breakstmt
|
||||
|
||||
val continuestmt = continuestmt()?.toAst()
|
||||
if(continuestmt!=null) return continuestmt
|
||||
|
||||
val whenstmt = whenstmt()?.toAst()
|
||||
if(whenstmt!=null) return whenstmt
|
||||
|
||||
@ -247,7 +245,7 @@ private class AsmsubDecl(val name: String,
|
||||
val returntypes: List<DataType>,
|
||||
val asmParameterRegisters: List<RegisterOrStatusflag>,
|
||||
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
|
||||
val asmClobbers: Set<Register>)
|
||||
val asmClobbers: Set<CpuRegister>)
|
||||
|
||||
private fun prog8Parser.Asmsub_declContext.toAst(): AsmsubDecl {
|
||||
val name = identifier().text
|
||||
@ -256,8 +254,8 @@ private fun prog8Parser.Asmsub_declContext.toAst(): AsmsubDecl {
|
||||
val clobbers = asmsub_clobbers()?.clobber()?.toAst() ?: emptySet()
|
||||
val normalParameters = params.map { SubroutineParameter(it.name, it.type, it.position) }
|
||||
val normalReturntypes = 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 paramRegisters = params.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag) }
|
||||
val returnRegisters = returns.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag) }
|
||||
return AsmsubDecl(name, normalParameters, normalReturntypes, paramRegisters, returnRegisters, clobbers)
|
||||
}
|
||||
|
||||
@ -265,33 +263,49 @@ 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()) }
|
||||
= asmsub_return().map {
|
||||
val register = it.identifier()?.toAst()
|
||||
var registerorpair: RegisterOrPair? = null
|
||||
var statusregister: Statusflag? = null
|
||||
if(register!=null) {
|
||||
when (val name = register.nameInSource.single()) {
|
||||
in RegisterOrPair.names -> registerorpair = RegisterOrPair.valueOf(name)
|
||||
in Statusflag.names -> statusregister = Statusflag.valueOf(name)
|
||||
else -> throw FatalAstException("invalid register or status flag in $it")
|
||||
}
|
||||
}
|
||||
AsmSubroutineReturn(
|
||||
it.datatype().toAst(),
|
||||
registerorpair,
|
||||
statusregister,
|
||||
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())
|
||||
val register = it.identifier()?.toAst()
|
||||
var registerorpair: RegisterOrPair? = null
|
||||
var statusregister: Statusflag? = null
|
||||
if(register!=null) {
|
||||
when (val name = register.nameInSource.single()) {
|
||||
in RegisterOrPair.names -> registerorpair = RegisterOrPair.valueOf(name)
|
||||
in Statusflag.names -> statusregister = Statusflag.valueOf(name)
|
||||
else -> throw FatalAstException("invalid register or status flag '$name'")
|
||||
}
|
||||
}
|
||||
AsmSubroutineParameter(vardecl.varname.text, datatype, registerorpair, statusregister, toPosition())
|
||||
}
|
||||
|
||||
private fun prog8Parser.StatusregisterContext.toAst() = Statusflag.valueOf(text)
|
||||
|
||||
private fun prog8Parser.Functioncall_stmtContext.toAst(): Statement {
|
||||
val void = this.VOID() != null
|
||||
val location = scoped_identifier().toAst()
|
||||
@ -326,14 +340,11 @@ private fun prog8Parser.LabeldefContext.toAst(): Statement =
|
||||
Label(children[0].text, toPosition())
|
||||
|
||||
private fun prog8Parser.SubroutineContext.toAst() : Subroutine {
|
||||
// non-asm subroutine
|
||||
val returntypes = sub_return_part()?.toAst() ?: emptyList()
|
||||
return Subroutine(identifier().text,
|
||||
sub_params()?.toAst() ?: emptyList(),
|
||||
sub_return_part()?.toAst() ?: emptyList(),
|
||||
emptyList(),
|
||||
emptyList(),
|
||||
emptySet(),
|
||||
null,
|
||||
false,
|
||||
returntypes,
|
||||
statement_block()?.toAst() ?: mutableListOf(),
|
||||
toPosition())
|
||||
}
|
||||
@ -350,23 +361,22 @@ private fun prog8Parser.Sub_paramsContext.toAst(): List<SubroutineParameter> =
|
||||
}
|
||||
|
||||
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())
|
||||
identifier!=null -> AssignTarget(identifier.toAst(), null, null, toPosition())
|
||||
arrayindexed()!=null -> AssignTarget(null, arrayindexed().toAst(), null, toPosition())
|
||||
directmemory()!=null -> AssignTarget(null, null, DirectMemoryWrite(directmemory().expression().toAst(), toPosition()), toPosition())
|
||||
else -> AssignTarget(scoped_identifier()?.toAst(), null, null, toPosition())
|
||||
}
|
||||
}
|
||||
|
||||
private fun prog8Parser.RegisterContext.toAst() = Register.valueOf(text.toUpperCase())
|
||||
private fun prog8Parser.ClobberContext.toAst() : Set<CpuRegister> {
|
||||
val names = this.identifier().map { it.toAst().nameInSource.single() }
|
||||
return names.map { CpuRegister.valueOf(it) }.toSet()
|
||||
}
|
||||
|
||||
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())
|
||||
|
||||
@ -382,7 +392,7 @@ private fun prog8Parser.DirectiveargContext.toAst() : DirectiveArg {
|
||||
}
|
||||
|
||||
private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
|
||||
fun makeLiteral(text: String, radix: Int, forceWord: Boolean): NumericLiteral {
|
||||
fun makeLiteral(text: String, radix: Int): NumericLiteral {
|
||||
val integer: Int
|
||||
var datatype = DataType.UBYTE
|
||||
when (radix) {
|
||||
@ -420,14 +430,14 @@ private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
|
||||
}
|
||||
else -> throw FatalAstException("invalid radix")
|
||||
}
|
||||
return NumericLiteral(integer, if (forceWord) DataType.UWORD else datatype)
|
||||
return NumericLiteral(integer, 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)
|
||||
prog8Parser.DEC_INTEGER -> makeLiteral(integerPart, 10)
|
||||
prog8Parser.HEX_INTEGER -> makeLiteral(integerPart.substring(1), 16)
|
||||
prog8Parser.BIN_INTEGER -> makeLiteral(integerPart.substring(1), 2)
|
||||
else -> throw FatalAstException(terminal.text)
|
||||
}
|
||||
}
|
||||
@ -455,8 +465,7 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
|
||||
litval.stringliteral()!=null -> litval.stringliteral().toAst()
|
||||
litval.charliteral()!=null -> {
|
||||
try {
|
||||
val cc=litval.charliteral()
|
||||
NumericLiteralValue(DataType.UBYTE, CompilationTarget.encodeString(
|
||||
NumericLiteralValue(DataType.UBYTE, CompilationTarget.instance.encodeString(
|
||||
unescape(litval.charliteral().SINGLECHAR().text, litval.toPosition()),
|
||||
litval.charliteral().ALT_STRING_ENCODING()!=null)[0], litval.toPosition())
|
||||
} catch (ce: CharConversionException) {
|
||||
@ -469,18 +478,11 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
|
||||
// the ConstantFold takes care of that and converts the type if needed.
|
||||
ArrayLiteralValue(InferredTypes.InferredType.unknown(), 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()
|
||||
|
||||
@ -572,19 +574,16 @@ private fun prog8Parser.Branch_stmtContext.toAst(): BranchStatement {
|
||||
private fun prog8Parser.BranchconditionContext.toAst() = BranchCondition.valueOf(text.substringAfter('_').toUpperCase())
|
||||
|
||||
private fun prog8Parser.ForloopContext.toAst(): ForLoop {
|
||||
val loopregister = register()?.toAst()
|
||||
val loopvar = identifier()?.toAst()
|
||||
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, loopvar, iterable, scope, toPosition())
|
||||
return ForLoop(loopvar, iterable, scope, toPosition())
|
||||
}
|
||||
|
||||
private fun prog8Parser.ContinuestmtContext.toAst() = Continue(toPosition())
|
||||
|
||||
private fun prog8Parser.BreakstmtContext.toAst() = Break(toPosition())
|
||||
|
||||
private fun prog8Parser.WhileloopContext.toAst(): WhileLoop {
|
||||
@ -595,19 +594,20 @@ private fun prog8Parser.WhileloopContext.toAst(): WhileLoop {
|
||||
return WhileLoop(condition, scope, toPosition())
|
||||
}
|
||||
|
||||
private fun prog8Parser.ForeverloopContext.toAst(): ForeverLoop {
|
||||
private fun prog8Parser.RepeatloopContext.toAst(): RepeatLoop {
|
||||
val iterations = expression()?.toAst()
|
||||
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||
val scope = AnonymousScope(statements, statement_block()?.toPosition()
|
||||
?: statement().toPosition())
|
||||
return ForeverLoop(scope, toPosition())
|
||||
return RepeatLoop(iterations, scope, toPosition())
|
||||
}
|
||||
|
||||
private fun prog8Parser.RepeatloopContext.toAst(): RepeatLoop {
|
||||
private fun prog8Parser.UntilloopContext.toAst(): UntilLoop {
|
||||
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())
|
||||
return UntilLoop(scope, untilCondition, toPosition())
|
||||
}
|
||||
|
||||
private fun prog8Parser.WhenstmtContext.toAst(): WhenStatement {
|
||||
@ -641,7 +641,20 @@ private fun prog8Parser.VardeclContext.toAst(): VarDecl {
|
||||
)
|
||||
}
|
||||
|
||||
internal fun escape(str: String) = str.replace("\t", "\\t").replace("\n", "\\n").replace("\r", "\\r")
|
||||
internal fun escape(str: String): String {
|
||||
val es = str.map {
|
||||
when(it) {
|
||||
'\t' -> "\\t"
|
||||
'\n' -> "\\n"
|
||||
'\r' -> "\\r"
|
||||
'"' -> "\\\""
|
||||
in '\u8000'..'\u80ff' -> "\\x" + (it.toInt() - 0x8000).toString(16).padStart(2, '0')
|
||||
in '\u0000'..'\u00ff' -> it.toString()
|
||||
else -> "\\u" + it.toInt().toString(16).padStart(4, '0')
|
||||
}
|
||||
}
|
||||
return es.joinToString("")
|
||||
}
|
||||
|
||||
internal fun unescape(str: String, position: Position): String {
|
||||
val result = mutableListOf<Char>()
|
||||
@ -655,9 +668,15 @@ internal fun unescape(str: String, position: Position): String {
|
||||
'n' -> '\n'
|
||||
'r' -> '\r'
|
||||
'"' -> '"'
|
||||
'\'' -> '\''
|
||||
'u' -> {
|
||||
"${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}".toInt(16).toChar()
|
||||
}
|
||||
'x' -> {
|
||||
// special hack 0x8000..0x80ff will be outputted verbatim without encoding
|
||||
val hex = ("" + iter.nextChar() + iter.nextChar()).toInt(16)
|
||||
(0x8000 + hex).toChar()
|
||||
}
|
||||
else -> throw SyntaxError("invalid escape char in string: \\$ec", position)
|
||||
})
|
||||
} else {
|
||||
|
@ -21,35 +21,39 @@ enum class DataType {
|
||||
STRUCT; // pass by reference
|
||||
|
||||
/**
|
||||
* is the type assignable to the given other type?
|
||||
* is the type assignable to the given other type (perhaps via a typecast) without loss of precision?
|
||||
*/
|
||||
infix fun isAssignableTo(targetType: DataType) =
|
||||
// what types are assignable to others without loss of precision?
|
||||
when(this) {
|
||||
UBYTE -> targetType in setOf(UBYTE, WORD, UWORD, FLOAT)
|
||||
BYTE -> targetType in setOf(BYTE, WORD, FLOAT)
|
||||
UWORD -> targetType in setOf(UWORD, FLOAT)
|
||||
WORD -> targetType in setOf(WORD, FLOAT)
|
||||
FLOAT -> targetType == FLOAT
|
||||
STR -> targetType == STR
|
||||
in ArrayDatatypes -> targetType == this
|
||||
STR -> targetType == STR || targetType == UWORD
|
||||
in ArrayDatatypes -> targetType == this || targetType == UWORD
|
||||
else -> false
|
||||
}
|
||||
|
||||
|
||||
infix fun isAssignableTo(targetTypes: Set<DataType>) = targetTypes.any { this isAssignableTo it }
|
||||
infix fun isNotAssignableTo(targetType: DataType) = !this.isAssignableTo(targetType)
|
||||
infix fun isNotAssignableTo(targetTypes: Set<DataType>) = !this.isAssignableTo(targetTypes)
|
||||
|
||||
infix fun largerThan(other: DataType) =
|
||||
when(this) {
|
||||
in ByteDatatypes -> false
|
||||
in WordDatatypes -> other in ByteDatatypes
|
||||
when {
|
||||
this == other -> false
|
||||
this in ByteDatatypes -> false
|
||||
this in WordDatatypes -> other in ByteDatatypes
|
||||
this==STR && other==UWORD || this==UWORD && other==STR -> false
|
||||
else -> true
|
||||
}
|
||||
|
||||
infix fun equalsSize(other: DataType) =
|
||||
when(this) {
|
||||
in ByteDatatypes -> other in ByteDatatypes
|
||||
in WordDatatypes -> other in WordDatatypes
|
||||
when {
|
||||
this == other -> true
|
||||
this in ByteDatatypes -> other in ByteDatatypes
|
||||
this in WordDatatypes -> other in WordDatatypes
|
||||
this==STR && other==UWORD || this==UWORD && other==STR -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
@ -57,14 +61,14 @@ enum class DataType {
|
||||
return when(this) {
|
||||
in ByteDatatypes -> 1
|
||||
in WordDatatypes -> 2
|
||||
FLOAT -> CompilationTarget.machine.FLOAT_MEM_SIZE
|
||||
in PassByReferenceDatatypes -> 2
|
||||
FLOAT -> CompilationTarget.instance.machine.FLOAT_MEM_SIZE
|
||||
in PassByReferenceDatatypes -> CompilationTarget.instance.machine.POINTER_MEM_SIZE
|
||||
else -> -9999999
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class Register {
|
||||
enum class CpuRegister {
|
||||
A,
|
||||
X,
|
||||
Y
|
||||
@ -76,14 +80,25 @@ enum class RegisterOrPair {
|
||||
Y,
|
||||
AX,
|
||||
AY,
|
||||
XY
|
||||
XY,
|
||||
FAC1,
|
||||
FAC2;
|
||||
|
||||
companion object {
|
||||
val names by lazy { values().map { it.toString()} }
|
||||
}
|
||||
|
||||
} // only used in parameter and return value specs in asm subroutines
|
||||
|
||||
enum class Statusflag {
|
||||
Pc,
|
||||
Pz,
|
||||
Pv,
|
||||
Pn
|
||||
Pn;
|
||||
|
||||
companion object {
|
||||
val names by lazy { values().map { it.toString()} }
|
||||
}
|
||||
}
|
||||
|
||||
enum class BranchCondition {
|
||||
@ -150,11 +165,14 @@ object ParentSentinel : Node {
|
||||
override val position = Position("<<sentinel>>", 0, 0, 0)
|
||||
override var parent: Node = this
|
||||
override fun linkParents(parent: Node) {}
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {}
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
replacement.parent = this
|
||||
}
|
||||
}
|
||||
|
||||
data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) {
|
||||
override fun toString(): String = "[$file: line $line col ${startCol+1}-${endCol+1}]"
|
||||
fun toClickableStr(): String = "$file:$line:$startCol:"
|
||||
|
||||
companion object {
|
||||
val DUMMY = Position("<dummy>", 0, 0, 0)
|
||||
|
@ -24,7 +24,7 @@ class ErrorReporter {
|
||||
MessageSeverity.ERROR -> System.err.print("\u001b[91m") // bright red
|
||||
MessageSeverity.WARNING -> System.err.print("\u001b[93m") // bright yellow
|
||||
}
|
||||
val msg = "${it.position} ${it.severity} ${it.message}".trim()
|
||||
val msg = "${it.position.toClickableStr()} ${it.severity} ${it.message}".trim()
|
||||
if(msg !in alreadyReportedMessages) {
|
||||
System.err.println(msg)
|
||||
alreadyReportedMessages.add(msg)
|
||||
|
@ -2,16 +2,16 @@ package prog8.ast.base
|
||||
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
|
||||
class FatalAstException (override var message: String) : Exception(message)
|
||||
open class FatalAstException (override var message: String) : Exception(message)
|
||||
|
||||
open class AstException (override var message: String) : Exception(message)
|
||||
|
||||
open class SyntaxError(override var message: String, val position: Position) : AstException(message) {
|
||||
override fun toString() = "$position Syntax error: $message"
|
||||
override fun toString() = "${position.toClickableStr()} Syntax error: $message"
|
||||
}
|
||||
|
||||
class ExpressionError(message: String, val position: Position) : AstException(message) {
|
||||
override fun toString() = "$position Error: $message"
|
||||
override fun toString() = "${position.toClickableStr()} Error: $message"
|
||||
}
|
||||
|
||||
class UndefinedSymbolError(symbol: IdentifierReference)
|
||||
|
@ -3,9 +3,9 @@ package prog8.ast.base
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.processing.*
|
||||
import prog8.ast.statements.Directive
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.target.AsmVariableAndReturnsPreparer
|
||||
import prog8.optimizer.FlattenAnonymousScopesAndNopRemover
|
||||
import prog8.compiler.BeforeAsmGenerationAstChanger
|
||||
|
||||
|
||||
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: ErrorReporter) {
|
||||
@ -13,19 +13,16 @@ internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: Err
|
||||
checker.visit(this)
|
||||
}
|
||||
|
||||
internal fun Program.prepareAsmVariablesAndReturns(errors: ErrorReporter) {
|
||||
val fixer = AsmVariableAndReturnsPreparer(this, errors)
|
||||
internal fun Program.processAstBeforeAsmGeneration(errors: ErrorReporter) {
|
||||
val fixer = BeforeAsmGenerationAstChanger(this, errors)
|
||||
fixer.visit(this)
|
||||
fixer.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.reorderStatements() {
|
||||
val initvalueCreator = AddressOfInserter(this)
|
||||
initvalueCreator.visit(this)
|
||||
initvalueCreator.applyModifications()
|
||||
|
||||
val checker = StatementReorderer(this)
|
||||
checker.visit(this)
|
||||
internal fun Program.reorderStatements(errors: ErrorReporter) {
|
||||
val reorder = StatementReorderer(this, errors)
|
||||
reorder.visit(this)
|
||||
reorder.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.addTypecasts(errors: ErrorReporter) {
|
||||
@ -34,34 +31,72 @@ internal fun Program.addTypecasts(errors: ErrorReporter) {
|
||||
caster.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.verifyFunctionArgTypes() {
|
||||
val fixer = VerifyFunctionArgTypes(this)
|
||||
fixer.visit(this)
|
||||
}
|
||||
|
||||
internal fun Module.checkImportedValid() {
|
||||
val imr = ImportedModuleDirectiveRemover()
|
||||
imr.visit(this, this.parent)
|
||||
imr.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.checkRecursion(errors: ErrorReporter) {
|
||||
val checker = AstRecursionChecker(namespace, errors)
|
||||
checker.visit(this)
|
||||
checker.processMessages(name)
|
||||
}
|
||||
|
||||
internal fun Program.checkIdentifiers(errors: ErrorReporter) {
|
||||
val checker = AstIdentifiersChecker(this, errors)
|
||||
checker.visit(this)
|
||||
|
||||
val checker2 = AstIdentifiersChecker(this, errors)
|
||||
checker2.visit(this)
|
||||
|
||||
if(errors.isEmpty()) {
|
||||
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.makeForeverLoops() {
|
||||
val checker = ForeverLoopsMaker()
|
||||
checker.visit(this)
|
||||
checker.applyModifications()
|
||||
internal fun Program.variousCleanups() {
|
||||
val process = VariousCleanups()
|
||||
process.visit(this)
|
||||
process.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.removeNopsFlattenAnonScopes() {
|
||||
val flattener = FlattenAnonymousScopesAndNopRemover()
|
||||
flattener.visit(this)
|
||||
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()
|
||||
if(start!=null) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,11 +4,11 @@ import prog8.ast.*
|
||||
import prog8.ast.antlr.escape
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModifyingVisitor
|
||||
import prog8.ast.processing.IAstVisitor
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.functions.BuiltinFunctions
|
||||
import prog8.functions.CannotEvaluateException
|
||||
import prog8.functions.NotConstArgumentException
|
||||
import prog8.functions.builtinFunctionReturnType
|
||||
import java.util.*
|
||||
@ -16,35 +16,52 @@ import kotlin.math.abs
|
||||
|
||||
|
||||
val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "==", "!=")
|
||||
|
||||
val comparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
|
||||
val augmentAssignmentOperators = setOf("+", "-", "/", "*", "**", "&", "|", "^", "<<", ">>", "%", "and", "or", "xor")
|
||||
|
||||
sealed class Expression: Node {
|
||||
abstract fun constValue(program: Program): NumericLiteralValue?
|
||||
abstract fun accept(visitor: IAstModifyingVisitor): Expression
|
||||
abstract fun accept(visitor: IAstVisitor)
|
||||
abstract fun accept(visitor: AstWalker, parent: Node)
|
||||
abstract fun referencesIdentifiers(vararg name: String): Boolean // todo: remove this and add identifier usage tracking into CallGraph instead
|
||||
abstract fun referencesIdentifier(vararg scopedName: String): Boolean
|
||||
abstract fun inferType(program: Program): InferredTypes.InferredType
|
||||
|
||||
infix fun isSameAs(assigntarget: AssignTarget) = assigntarget.isSameAs(this)
|
||||
|
||||
infix fun isSameAs(other: Expression): Boolean {
|
||||
if(this===other)
|
||||
return true
|
||||
when(this) {
|
||||
is RegisterExpr ->
|
||||
return (other is RegisterExpr && other.register==register)
|
||||
return when(this) {
|
||||
is IdentifierReference ->
|
||||
return (other is IdentifierReference && other.nameInSource==nameInSource)
|
||||
(other is IdentifierReference && other.nameInSource==nameInSource)
|
||||
is PrefixExpression ->
|
||||
return (other is PrefixExpression && other.operator==operator && other.expression isSameAs expression)
|
||||
(other is PrefixExpression && other.operator==operator && other.expression isSameAs expression)
|
||||
is BinaryExpression ->
|
||||
return (other is BinaryExpression && other.operator==operator
|
||||
(other is BinaryExpression && other.operator==operator
|
||||
&& other.left isSameAs left
|
||||
&& other.right isSameAs right)
|
||||
is ArrayIndexedExpression -> {
|
||||
return (other is ArrayIndexedExpression && other.identifier.nameInSource == identifier.nameInSource
|
||||
&& other.arrayspec.index isSameAs arrayspec.index)
|
||||
(other is ArrayIndexedExpression && other.arrayvar.nameInSource == arrayvar.nameInSource
|
||||
&& other.indexer isSameAs indexer)
|
||||
}
|
||||
else -> return other==this
|
||||
is DirectMemoryRead -> {
|
||||
(other is DirectMemoryRead && other.addressExpression isSameAs addressExpression)
|
||||
}
|
||||
is TypecastExpression -> {
|
||||
(other is TypecastExpression && other.implicit==implicit && other.type==type && other.expression isSameAs expression)
|
||||
}
|
||||
is AddressOf -> {
|
||||
(other is AddressOf && other.identifier.nameInSource == identifier.nameInSource)
|
||||
}
|
||||
is RangeExpr -> {
|
||||
(other is RangeExpr && other.from==from && other.to==to && other.step==step)
|
||||
}
|
||||
is FunctionCall -> {
|
||||
(other is FunctionCall && other.target.nameInSource == target.nameInSource
|
||||
&& other.args.size == args.size
|
||||
&& other.args.zip(args).all { it.first isSameAs it.second } )
|
||||
}
|
||||
else -> other==this
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -61,14 +78,14 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(node === expression && replacement is Expression)
|
||||
expression = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name)
|
||||
override fun referencesIdentifier(vararg scopedName: String) = expression.referencesIdentifier(*scopedName)
|
||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||
val inferred = expression.inferType(program)
|
||||
return when(operator) {
|
||||
@ -112,6 +129,7 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
|
||||
node===right -> right = replacement
|
||||
else -> throw FatalAstException("invalid replace, no child $node")
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
@ -121,11 +139,10 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
|
||||
// binary expression should actually have been optimized away into a single value, before const value was requested...
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String) = left.referencesIdentifiers(*name) || right.referencesIdentifiers(*name)
|
||||
override fun referencesIdentifier(vararg scopedName: String) = left.referencesIdentifier(*scopedName) || right.referencesIdentifier(*scopedName)
|
||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||
val leftDt = left.inferType(program)
|
||||
val rightDt = right.inferType(program)
|
||||
@ -215,33 +232,32 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
|
||||
}
|
||||
}
|
||||
|
||||
class ArrayIndexedExpression(var identifier: IdentifierReference,
|
||||
val arrayspec: ArrayIndex,
|
||||
class ArrayIndexedExpression(var arrayvar: IdentifierReference,
|
||||
val indexer: ArrayIndex,
|
||||
override val position: Position) : Expression(), IAssignable {
|
||||
override lateinit var parent: Node
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
identifier.linkParents(this)
|
||||
arrayspec.linkParents(this)
|
||||
arrayvar.linkParents(this)
|
||||
indexer.linkParents(this)
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
when {
|
||||
node===identifier -> identifier = replacement as IdentifierReference
|
||||
node===arrayspec.index -> arrayspec.index = replacement as Expression
|
||||
node===arrayvar -> arrayvar = replacement as IdentifierReference
|
||||
else -> throw FatalAstException("invalid replace")
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String) = identifier.referencesIdentifiers(*name)
|
||||
override fun referencesIdentifier(vararg scopedName: String) = arrayvar.referencesIdentifier(*scopedName)
|
||||
|
||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||
val target = identifier.targetStatement(program.namespace)
|
||||
val target = arrayvar.targetStatement(program.namespace)
|
||||
if (target is VarDecl) {
|
||||
return when (target.datatype) {
|
||||
DataType.STR -> InferredTypes.knownFor(DataType.UBYTE)
|
||||
@ -253,7 +269,7 @@ class ArrayIndexedExpression(var identifier: IdentifierReference,
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "ArrayIndexed(ident=$identifier, arraysize=$arrayspec; pos=$position)"
|
||||
return "ArrayIndexed(ident=$arrayvar, arraysize=$indexer; pos=$position)"
|
||||
}
|
||||
}
|
||||
|
||||
@ -268,19 +284,21 @@ class TypecastExpression(var expression: Expression, var type: DataType, val imp
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Expression && node===expression)
|
||||
expression = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name)
|
||||
override fun referencesIdentifier(vararg scopedName: String) = expression.referencesIdentifier(*scopedName)
|
||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
|
||||
override fun constValue(program: Program): NumericLiteralValue? {
|
||||
val cv = expression.constValue(program) ?: return null
|
||||
return cv.cast(type)
|
||||
// val value = RuntimeValue(cv.type, cv.asNumericValue!!).cast(type)
|
||||
// return LiteralValue.fromNumber(value.numericValue(), value.type, position).cast(type)
|
||||
val cast = cv.cast(type)
|
||||
return if(cast.isValid)
|
||||
cast.valueOrZero()
|
||||
else
|
||||
null
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
@ -299,12 +317,12 @@ data class AddressOf(var identifier: IdentifierReference, override val position:
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is IdentifierReference && node===identifier)
|
||||
identifier = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun referencesIdentifiers(vararg name: String) = false
|
||||
override fun referencesIdentifier(vararg scopedName: String) = false
|
||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UWORD)
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
}
|
||||
@ -320,13 +338,13 @@ class DirectMemoryRead(var addressExpression: Expression, override val position:
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Expression && node===addressExpression)
|
||||
addressExpression = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String) = false
|
||||
override fun referencesIdentifier(vararg scopedName: String) = false
|
||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UBYTE)
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
|
||||
@ -379,10 +397,9 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
|
||||
throw FatalAstException("can't replace here")
|
||||
}
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String) = false
|
||||
override fun referencesIdentifier(vararg scopedName: String) = false
|
||||
override fun constValue(program: Program) = this
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
@ -400,88 +417,66 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
|
||||
|
||||
operator fun compareTo(other: NumericLiteralValue): Int = number.toDouble().compareTo(other.number.toDouble())
|
||||
|
||||
fun cast(targettype: DataType): NumericLiteralValue {
|
||||
class CastValue(val isValid: Boolean, private val value: NumericLiteralValue?) {
|
||||
fun valueOrZero() = if(isValid) value!! else NumericLiteralValue(DataType.UBYTE, 0, Position.DUMMY)
|
||||
}
|
||||
|
||||
fun cast(targettype: DataType): CastValue {
|
||||
if(type==targettype)
|
||||
return this
|
||||
return CastValue(true, this)
|
||||
val numval = number.toDouble()
|
||||
when(type) {
|
||||
DataType.UBYTE -> {
|
||||
if(targettype== DataType.BYTE && numval <= 127)
|
||||
return NumericLiteralValue(targettype, number.toShort(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||
if(targettype== DataType.WORD || targettype== DataType.UWORD)
|
||||
return NumericLiteralValue(targettype, number.toInt(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||
if(targettype== DataType.FLOAT)
|
||||
return NumericLiteralValue(targettype, number.toDouble(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
if(targettype== DataType.UBYTE && numval >= 0)
|
||||
return NumericLiteralValue(targettype, number.toShort(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||
if(targettype== DataType.UWORD && numval >= 0)
|
||||
return NumericLiteralValue(targettype, number.toInt(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||
if(targettype== DataType.WORD)
|
||||
return NumericLiteralValue(targettype, number.toInt(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||
if(targettype== DataType.FLOAT)
|
||||
return NumericLiteralValue(targettype, number.toDouble(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
if(targettype== DataType.BYTE && numval <= 127)
|
||||
return NumericLiteralValue(targettype, number.toShort(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||
if(targettype== DataType.UBYTE && numval <= 255)
|
||||
return NumericLiteralValue(targettype, number.toShort(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||
if(targettype== DataType.WORD && numval <= 32767)
|
||||
return NumericLiteralValue(targettype, number.toInt(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||
if(targettype== DataType.FLOAT)
|
||||
return NumericLiteralValue(targettype, number.toDouble(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
|
||||
}
|
||||
DataType.WORD -> {
|
||||
if(targettype== DataType.BYTE && numval >= -128 && numval <=127)
|
||||
return NumericLiteralValue(targettype, number.toShort(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||
if(targettype== DataType.UBYTE && numval >= 0 && numval <= 255)
|
||||
return NumericLiteralValue(targettype, number.toShort(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||
if(targettype== DataType.UWORD && numval >=0)
|
||||
return NumericLiteralValue(targettype, number.toInt(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||
if(targettype== DataType.FLOAT)
|
||||
return NumericLiteralValue(targettype, number.toDouble(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
if (targettype == DataType.BYTE && numval >= -128 && numval <=127)
|
||||
return NumericLiteralValue(targettype, number.toShort(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||
if (targettype == DataType.UBYTE && numval >=0 && numval <= 255)
|
||||
return NumericLiteralValue(targettype, number.toShort(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||
if (targettype == DataType.WORD && numval >= -32768 && numval <= 32767)
|
||||
return NumericLiteralValue(targettype, number.toInt(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||
if (targettype == DataType.UWORD && numval >=0 && numval <= 65535)
|
||||
return NumericLiteralValue(targettype, number.toInt(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
throw ExpressionError("can't cast $type into $targettype", position)
|
||||
}
|
||||
}
|
||||
|
||||
class StructLiteralValue(var values: List<Expression>,
|
||||
override val position: Position): Expression() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent=parent
|
||||
values.forEach { it.linkParents(this) }
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
throw FatalAstException("can't replace here")
|
||||
}
|
||||
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String) = values.any { it.referencesIdentifiers(*name) }
|
||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.STRUCT)
|
||||
|
||||
override fun toString(): String {
|
||||
return "struct{ ${values.joinToString(", ")} }"
|
||||
return CastValue(false, null)
|
||||
}
|
||||
}
|
||||
|
||||
@ -502,9 +497,8 @@ class StringLiteralValue(val value: String,
|
||||
throw FatalAstException("can't replace here")
|
||||
}
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String) = false
|
||||
override fun referencesIdentifier(vararg scopedName: String) = false
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
@ -533,18 +527,18 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Expression)
|
||||
val idx = value.indexOf(node)
|
||||
val idx = value.indexOfFirst { it===node }
|
||||
value[idx] = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String) = value.any { it.referencesIdentifiers(*name) }
|
||||
override fun referencesIdentifier(vararg scopedName: String) = value.any { it.referencesIdentifier(*scopedName) }
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun toString(): String = "$value"
|
||||
override fun inferType(program: Program): InferredTypes.InferredType = if(type.isUnknown) type else guessDatatype(program)
|
||||
override fun inferType(program: Program): InferredTypes.InferredType = if(type.isKnown) type else guessDatatype(program)
|
||||
|
||||
operator fun compareTo(other: ArrayLiteralValue): Int = throw ExpressionError("cannot order compare arrays", position)
|
||||
override fun hashCode(): Int = Objects.hash(value, type)
|
||||
@ -574,10 +568,17 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
|
||||
val dts = datatypesInArray.map { it.typeOrElse(DataType.STRUCT) }
|
||||
return when {
|
||||
DataType.FLOAT in dts -> InferredTypes.InferredType.known(DataType.ARRAY_F)
|
||||
DataType.STR in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UW)
|
||||
DataType.WORD in dts -> InferredTypes.InferredType.known(DataType.ARRAY_W)
|
||||
DataType.UWORD in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UW)
|
||||
DataType.BYTE in dts -> InferredTypes.InferredType.known(DataType.ARRAY_B)
|
||||
DataType.UBYTE in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UB)
|
||||
DataType.ARRAY_UW in dts ||
|
||||
DataType.ARRAY_W in dts ||
|
||||
DataType.ARRAY_UB in dts ||
|
||||
DataType.ARRAY_B in dts ||
|
||||
DataType.ARRAY_F in dts ||
|
||||
DataType.STRUCT in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UW)
|
||||
else -> InferredTypes.InferredType.unknown()
|
||||
}
|
||||
}
|
||||
@ -592,14 +593,14 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
|
||||
if(num==null) {
|
||||
// an array of UWORDs could possibly also contain AddressOfs, other stuff can't be casted
|
||||
if (elementType != DataType.UWORD || it !is AddressOf)
|
||||
return null
|
||||
return null // can't cast a value of the array, abort
|
||||
it
|
||||
} else {
|
||||
try {
|
||||
num.cast(elementType)
|
||||
} catch(x: ExpressionError) {
|
||||
return null
|
||||
}
|
||||
val cast = num.cast(elementType)
|
||||
if(cast.isValid)
|
||||
cast.valueOrZero()
|
||||
else
|
||||
return null // can't cast a value of the array, abort
|
||||
}
|
||||
}.toTypedArray()
|
||||
return ArrayLiteralValue(InferredTypes.InferredType.known(targettype), castArray, position = position)
|
||||
@ -629,14 +630,14 @@ class RangeExpr(var from: Expression,
|
||||
step===node -> step=replacement
|
||||
else -> throw FatalAstException("invalid replacement")
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String): Boolean = from.referencesIdentifiers(*name) || to.referencesIdentifiers(*name)
|
||||
override fun referencesIdentifier(vararg scopedName: String): Boolean = from.referencesIdentifier(*scopedName) || to.referencesIdentifier(*scopedName)
|
||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||
val fromDt=from.inferType(program)
|
||||
val toDt=to.inferType(program)
|
||||
@ -647,7 +648,14 @@ class RangeExpr(var from: Expression,
|
||||
fromDt istype DataType.STR && toDt istype DataType.STR -> InferredTypes.knownFor(DataType.STR)
|
||||
fromDt istype DataType.WORD || toDt istype DataType.WORD -> InferredTypes.knownFor(DataType.ARRAY_W)
|
||||
fromDt istype DataType.BYTE || toDt istype DataType.BYTE -> InferredTypes.knownFor(DataType.ARRAY_B)
|
||||
else -> InferredTypes.knownFor(DataType.ARRAY_UB)
|
||||
else -> {
|
||||
val fdt = fromDt.typeOrElse(DataType.STRUCT)
|
||||
val tdt = toDt.typeOrElse(DataType.STRUCT)
|
||||
if(fdt largerThan tdt)
|
||||
InferredTypes.knownFor(ElementArrayTypes.getValue(fdt))
|
||||
else
|
||||
InferredTypes.knownFor(ElementArrayTypes.getValue(tdt))
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun toString(): String {
|
||||
@ -669,8 +677,8 @@ class RangeExpr(var from: Expression,
|
||||
val toString = to as? StringLiteralValue
|
||||
if(fromString!=null && toString!=null ) {
|
||||
// string range -> int range over character values
|
||||
fromVal = CompilationTarget.encodeString(fromString.value, fromString.altEncoding)[0].toInt()
|
||||
toVal = CompilationTarget.encodeString(toString.value, fromString.altEncoding)[0].toInt()
|
||||
fromVal = CompilationTarget.instance.encodeString(fromString.value, fromString.altEncoding)[0].toInt()
|
||||
toVal = CompilationTarget.instance.encodeString(toString.value, fromString.altEncoding)[0].toInt()
|
||||
} else {
|
||||
val fromLv = from as? NumericLiteralValue
|
||||
val toLv = to as? NumericLiteralValue
|
||||
@ -700,30 +708,6 @@ internal fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
|
||||
}
|
||||
}
|
||||
|
||||
class RegisterExpr(val register: Register, override val position: Position) : Expression(), IAssignable {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
throw FatalAstException("can't replace here")
|
||||
}
|
||||
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String): Boolean = register.name in name
|
||||
override fun toString(): String {
|
||||
return "RegisterExpr(register=$register, pos=$position)"
|
||||
}
|
||||
|
||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UBYTE)
|
||||
}
|
||||
|
||||
data class IdentifierReference(val nameInSource: List<String>, override val position: Position) : Expression(), IAssignable {
|
||||
override lateinit var parent: Node
|
||||
|
||||
@ -736,6 +720,9 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
|
||||
fun targetVarDecl(namespace: INameScope): VarDecl? = targetStatement(namespace) as? VarDecl
|
||||
fun targetSubroutine(namespace: INameScope): Subroutine? = targetStatement(namespace) as? Subroutine
|
||||
|
||||
override fun equals(other: Any?) = other is IdentifierReference && other.nameInSource==nameInSource
|
||||
override fun hashCode() = nameInSource.hashCode()
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
}
|
||||
@ -760,18 +747,17 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
|
||||
return "IdentifierRef($nameInSource)"
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String): Boolean = nameInSource.last() in name
|
||||
override fun referencesIdentifier(vararg scopedName: String): Boolean =
|
||||
nameInSource.size==scopedName.size && nameInSource.toTypedArray().contentEquals(scopedName)
|
||||
|
||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||
val targetStmt = targetStatement(program.namespace)
|
||||
return if(targetStmt is VarDecl) {
|
||||
InferredTypes.knownFor(targetStmt.datatype)
|
||||
} else {
|
||||
InferredTypes.InferredType.unknown()
|
||||
return when (val targetStmt = targetStatement(program.namespace)) {
|
||||
is VarDecl -> InferredTypes.knownFor(targetStmt.datatype)
|
||||
is StructDecl -> InferredTypes.knownFor(DataType.STRUCT)
|
||||
else -> InferredTypes.InferredType.unknown()
|
||||
}
|
||||
}
|
||||
|
||||
@ -787,6 +773,21 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
|
||||
else -> throw FatalAstException("requires a reference value")
|
||||
}
|
||||
}
|
||||
|
||||
fun firstStructVarName(namespace: INameScope): String? {
|
||||
// take the name of the first struct member of the structvariable instead
|
||||
// if it's just a regular variable, return null.
|
||||
val struct = memberOfStruct(namespace) ?: return null
|
||||
val decl = targetVarDecl(namespace)!!
|
||||
if(decl.datatype!=DataType.STRUCT)
|
||||
return null
|
||||
|
||||
val firstStructMember = struct.nameOfFirstMember()
|
||||
// find the flattened var that belongs to this first struct member
|
||||
val firstVarName = listOf(decl.name, firstStructMember)
|
||||
val firstVar = definingScope().lookup(firstVarName, this) as VarDecl
|
||||
return firstVar.name
|
||||
}
|
||||
}
|
||||
|
||||
class FunctionCall(override var target: IdentifierReference,
|
||||
@ -804,9 +805,10 @@ class FunctionCall(override var target: IdentifierReference,
|
||||
if(node===target)
|
||||
target=replacement as IdentifierReference
|
||||
else {
|
||||
val idx = args.indexOf(node)
|
||||
val idx = args.indexOfFirst { it===node }
|
||||
args[idx] = replacement as Expression
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun constValue(program: Program) = constValue(program, true)
|
||||
@ -822,7 +824,7 @@ class FunctionCall(override var target: IdentifierReference,
|
||||
val exprfunc = func.constExpressionFunc
|
||||
if(exprfunc!=null)
|
||||
resultValue = exprfunc(args, position, program)
|
||||
else if(func.returntype==null)
|
||||
else if(func.known_returntype==null)
|
||||
throw ExpressionError("builtin function ${target.nameInSource[0]} can't be used here because it doesn't return a value", position)
|
||||
}
|
||||
|
||||
@ -839,17 +841,20 @@ class FunctionCall(override var target: IdentifierReference,
|
||||
// const-evaluating the builtin function call failed.
|
||||
return null
|
||||
}
|
||||
catch(x: CannotEvaluateException) {
|
||||
// const-evaluating the builtin function call failed.
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "FunctionCall(target=$target, pos=$position)"
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String): Boolean = target.referencesIdentifiers(*name) || args.any{it.referencesIdentifiers(*name)}
|
||||
override fun referencesIdentifier(vararg scopedName: String): Boolean = target.referencesIdentifier(*scopedName) || args.any{it.referencesIdentifier(*scopedName)}
|
||||
|
||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||
val constVal = constValue(program ,false)
|
||||
@ -869,6 +874,12 @@ class FunctionCall(override var target: IdentifierReference,
|
||||
return InferredTypes.void() // no return value
|
||||
if(stmt.returntypes.size==1)
|
||||
return InferredTypes.knownFor(stmt.returntypes[0])
|
||||
|
||||
// multiple return values. Can occur for asmsub routines. If there is exactly one register return value, take that.
|
||||
val numRegisterReturns = stmt.asmReturnvaluesRegisters.count { it.registerOrPair!=null }
|
||||
if(numRegisterReturns==1)
|
||||
return InferredTypes.InferredType.known(DataType.UBYTE)
|
||||
|
||||
return InferredTypes.unknown() // has multiple return types... so not a single resulting datatype possible
|
||||
}
|
||||
else -> return InferredTypes.unknown()
|
||||
|
@ -35,6 +35,13 @@ object InferredTypes {
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = Objects.hash(isVoid, datatype)
|
||||
|
||||
infix fun isAssignableTo(targetDt: InferredType): Boolean =
|
||||
isKnown && targetDt.isKnown && (datatype!! isAssignableTo targetDt.datatype!!)
|
||||
infix fun isAssignableTo(targetDt: DataType): Boolean =
|
||||
isKnown && (datatype!! isAssignableTo targetDt)
|
||||
infix fun isNotAssignableTo(targetDt: InferredType): Boolean = !this.isAssignableTo(targetDt)
|
||||
infix fun isNotAssignableTo(targetDt: DataType): Boolean = !this.isAssignableTo(targetDt)
|
||||
}
|
||||
|
||||
private val unknownInstance = InferredType.unknown()
|
||||
|
@ -1,93 +0,0 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.IterableDatatypes
|
||||
import prog8.ast.base.PassByReferenceDatatypes
|
||||
import prog8.ast.expressions.AddressOf
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.FunctionCall
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
import prog8.ast.statements.Statement
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.compiler.CompilerException
|
||||
import prog8.functions.BuiltinFunctions
|
||||
import prog8.functions.FSignature
|
||||
|
||||
|
||||
internal class AddressOfInserter(val program: Program): AstWalker() {
|
||||
// Insert AddressOf (&) expression where required (string params to a UWORD function param etc).
|
||||
// TODO join this into the StatementReorderer?
|
||||
|
||||
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||
// insert AddressOf (&) expression where required (string params to a UWORD function param etc).
|
||||
var parentStatement: Node = functionCall
|
||||
while(parentStatement !is Statement)
|
||||
parentStatement = parentStatement.parent
|
||||
val targetStatement = functionCall.target.targetSubroutine(program.namespace)
|
||||
if(targetStatement!=null) {
|
||||
return addAddressOfExprIfNeeded(targetStatement, functionCall.args, functionCall)
|
||||
} else {
|
||||
val builtinFunc = BuiltinFunctions[functionCall.target.nameInSource.joinToString (".")]
|
||||
if(builtinFunc!=null)
|
||||
return addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCall.args, functionCall)
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
// insert AddressOf (&) expression where required (string params to a UWORD function param etc).
|
||||
val targetStatement = functionCallStatement.target.targetSubroutine(program.namespace)
|
||||
if(targetStatement!=null) {
|
||||
return addAddressOfExprIfNeeded(targetStatement, functionCallStatement.args, functionCallStatement)
|
||||
} else {
|
||||
val builtinFunc = BuiltinFunctions[functionCallStatement.target.nameInSource.joinToString (".")]
|
||||
if(builtinFunc!=null)
|
||||
return addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCallStatement.args, functionCallStatement)
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
private fun addAddressOfExprIfNeeded(subroutine: Subroutine, args: MutableList<Expression>, parent: IFunctionCall): Iterable<IAstModification> {
|
||||
// functions that accept UWORD and are given an array type, or string, will receive the AddressOf (memory location) of that value instead.
|
||||
val replacements = mutableListOf<IAstModification>()
|
||||
for(argparam in subroutine.parameters.withIndex().zip(args)) {
|
||||
if(argparam.first.value.type==DataType.UWORD || argparam.first.value.type == DataType.STR) {
|
||||
if(argparam.second is AddressOf)
|
||||
continue
|
||||
val idref = argparam.second as? IdentifierReference
|
||||
if(idref!=null) {
|
||||
val variable = idref.targetVarDecl(program.namespace)
|
||||
if(variable!=null && variable.datatype in IterableDatatypes) {
|
||||
replacements += IAstModification.ReplaceNode(
|
||||
args[argparam.first.index],
|
||||
AddressOf(idref, idref.position),
|
||||
parent as Node)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return replacements
|
||||
}
|
||||
|
||||
private fun addAddressOfExprIfNeededForBuiltinFuncs(signature: FSignature, args: MutableList<Expression>, parent: IFunctionCall): Iterable<IAstModification> {
|
||||
// val paramTypesForAddressOf = PassByReferenceDatatypes + DataType.UWORD
|
||||
val replacements = mutableListOf<IAstModification>()
|
||||
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")
|
||||
replacements += IAstModification.ReplaceNode(
|
||||
args[arg.first.index],
|
||||
AddressOf(argvalue, argvalue.position),
|
||||
parent as Node)
|
||||
}
|
||||
}
|
||||
return replacements
|
||||
}
|
||||
}
|
@ -7,7 +7,9 @@ import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8.functions.BuiltinFunctions
|
||||
import java.io.File
|
||||
|
||||
@ -22,10 +24,10 @@ internal class AstChecker(private val program: Program,
|
||||
if(mainBlocks.size>1)
|
||||
errors.err("more than one 'main' block", mainBlocks[0].position)
|
||||
if(mainBlocks.isEmpty())
|
||||
errors.err("there is no 'main' block", program.position)
|
||||
errors.err("there is no 'main' block", program.modules.firstOrNull()?.position ?: program.position)
|
||||
|
||||
for(mainBlock in mainBlocks) {
|
||||
val startSub = mainBlock.subScopes()["start"] as? Subroutine
|
||||
val startSub = mainBlock.subScope("start") as? Subroutine
|
||||
if (startSub == null) {
|
||||
errors.err("missing program entrypoint ('start' subroutine in 'main' block)", mainBlock.position)
|
||||
} else {
|
||||
@ -58,7 +60,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(irqBlocks.size>1)
|
||||
errors.err("more than one 'irq' block", irqBlocks[0].position)
|
||||
for(irqBlock in irqBlocks) {
|
||||
val irqSub = irqBlock.subScopes()["irq"] as? Subroutine
|
||||
val irqSub = irqBlock.subScope("irq") as? Subroutine
|
||||
if (irqSub != null) {
|
||||
if (irqSub.parameters.isNotEmpty() || irqSub.returntypes.isNotEmpty())
|
||||
errors.err("irq entrypoint subroutine can't have parameters and/or return values", irqSub.position)
|
||||
@ -82,7 +84,7 @@ internal class AstChecker(private val program: Program,
|
||||
override fun visit(returnStmt: Return) {
|
||||
val expectedReturnValues = returnStmt.definingSubroutine()?.returntypes ?: emptyList()
|
||||
if(expectedReturnValues.size>1) {
|
||||
throw AstException("cannot use a return with one value in a subroutine that has multiple return values: $returnStmt")
|
||||
throw FatalAstException("cannot use a return with one value in a subroutine that has multiple return values: $returnStmt")
|
||||
}
|
||||
|
||||
if(expectedReturnValues.isEmpty() && returnStmt.value!=null) {
|
||||
@ -110,48 +112,52 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(forLoop: ForLoop) {
|
||||
if(forLoop.body.containsNoCodeNorVars())
|
||||
errors.warn("for loop body is empty", forLoop.position)
|
||||
|
||||
val iterableDt = forLoop.iterable.inferType(program).typeOrElse(DataType.BYTE)
|
||||
if(iterableDt !in IterableDatatypes && forLoop.iterable !is RangeExpr) {
|
||||
errors.err("can only loop over an iterable type", forLoop.position)
|
||||
} else {
|
||||
if (forLoop.loopRegister != null) {
|
||||
// loop register
|
||||
if (iterableDt != DataType.ARRAY_UB && iterableDt != DataType.ARRAY_B && iterableDt != DataType.STR)
|
||||
errors.err("register can only loop over bytes", forLoop.position)
|
||||
if(forLoop.loopRegister!=Register.A)
|
||||
errors.err("it's only possible to use A as a loop register", forLoop.position)
|
||||
val loopvar = forLoop.loopVar.targetVarDecl(program.namespace)
|
||||
if(loopvar==null || loopvar.type== VarDeclType.CONST) {
|
||||
errors.err("for loop requires a variable to loop with", forLoop.position)
|
||||
} else {
|
||||
// loop variable
|
||||
val loopvar = forLoop.loopVar!!.targetVarDecl(program.namespace)
|
||||
if(loopvar==null || loopvar.type== VarDeclType.CONST) {
|
||||
errors.err("for loop requires a variable to loop with", forLoop.position)
|
||||
} else {
|
||||
when (loopvar.datatype) {
|
||||
DataType.UBYTE -> {
|
||||
if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.ARRAY_UB && iterableDt != DataType.STR)
|
||||
errors.err("ubyte loop variable can only loop over unsigned bytes or strings", forLoop.position)
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.UWORD && iterableDt != DataType.STR &&
|
||||
iterableDt != DataType.ARRAY_UB && iterableDt!= DataType.ARRAY_UW)
|
||||
errors.err("uword loop variable can only loop over unsigned bytes, words or strings", forLoop.position)
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
if(iterableDt!= DataType.BYTE && iterableDt!= DataType.ARRAY_B)
|
||||
errors.err("byte loop variable can only loop over bytes", forLoop.position)
|
||||
}
|
||||
DataType.WORD -> {
|
||||
if(iterableDt!= DataType.BYTE && iterableDt!= DataType.WORD &&
|
||||
iterableDt != DataType.ARRAY_B && iterableDt!= DataType.ARRAY_W)
|
||||
errors.err("word loop variable can only loop over bytes or words", forLoop.position)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
errors.err("for loop only supports integers", forLoop.position)
|
||||
}
|
||||
else -> errors.err("loop variable must be numeric type", forLoop.position)
|
||||
when (loopvar.datatype) {
|
||||
DataType.UBYTE -> {
|
||||
if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.ARRAY_UB && iterableDt != DataType.STR)
|
||||
errors.err("ubyte loop variable can only loop over unsigned bytes or strings", forLoop.position)
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.UWORD && iterableDt != DataType.STR &&
|
||||
iterableDt != DataType.ARRAY_UB && iterableDt!= DataType.ARRAY_UW)
|
||||
errors.err("uword loop variable can only loop over unsigned bytes, words or strings", forLoop.position)
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
if(iterableDt!= DataType.BYTE && iterableDt!= DataType.ARRAY_B)
|
||||
errors.err("byte loop variable can only loop over bytes", forLoop.position)
|
||||
}
|
||||
DataType.WORD -> {
|
||||
if(iterableDt!= DataType.BYTE && iterableDt!= DataType.WORD &&
|
||||
iterableDt != DataType.ARRAY_B && iterableDt!= DataType.ARRAY_W)
|
||||
errors.err("word loop variable can only loop over bytes or words", forLoop.position)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
errors.err("for loop only supports integers", forLoop.position)
|
||||
}
|
||||
else -> errors.err("loop variable must be numeric type", forLoop.position)
|
||||
}
|
||||
if(errors.isEmpty()) {
|
||||
// check loop range values
|
||||
val range = forLoop.iterable as? RangeExpr
|
||||
if(range!=null) {
|
||||
val from = range.from as? NumericLiteralValue
|
||||
val to = range.to as? NumericLiteralValue
|
||||
if(from != null)
|
||||
checkValueTypeAndRange(loopvar.datatype, from)
|
||||
else if(!range.from.inferType(program).istype(loopvar.datatype))
|
||||
errors.err("range start value is incompatible with loop variable type", range.position)
|
||||
if(to != null)
|
||||
checkValueTypeAndRange(loopvar.datatype, to)
|
||||
else if(!range.to.inferType(program).istype(loopvar.datatype))
|
||||
errors.err("range end value is incompatible with loop variable type", range.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -244,44 +250,45 @@ internal class AstChecker(private val program: Program,
|
||||
err("parameter '${param.first.name}' should be ubyte")
|
||||
}
|
||||
}
|
||||
for(ret in subroutine.returntypes.withIndex().zip(subroutine.asmReturnvaluesRegisters)) {
|
||||
if(ret.second.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) {
|
||||
if (ret.first.value != DataType.UBYTE && ret.first.value != DataType.BYTE)
|
||||
err("return value #${ret.first.index + 1} should be (u)byte")
|
||||
subroutine.returntypes.zip(subroutine.asmReturnvaluesRegisters).forEachIndexed { index, pair ->
|
||||
if(pair.second.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) {
|
||||
if (pair.first != DataType.UBYTE && pair.first != DataType.BYTE)
|
||||
err("return value #${index + 1} should be (u)byte")
|
||||
}
|
||||
else if(ret.second.registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
|
||||
if (ret.first.value != DataType.UWORD && ret.first.value != DataType.WORD
|
||||
&& ret.first.value != DataType.STR && ret.first.value !in ArrayDatatypes && ret.first.value != DataType.FLOAT)
|
||||
err("return value #${ret.first.index + 1} should be (u)word/address")
|
||||
else if(pair.second.registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
|
||||
if (pair.first != DataType.UWORD && pair.first != DataType.WORD
|
||||
&& pair.first != DataType.STR && pair.first !in ArrayDatatypes && pair.first != DataType.FLOAT)
|
||||
err("return value #${index + 1} should be (u)word/address")
|
||||
}
|
||||
else if(ret.second.statusflag!=null) {
|
||||
if (ret.first.value != DataType.UBYTE)
|
||||
err("return value #${ret.first.index + 1} should be ubyte")
|
||||
else if(pair.second.statusflag!=null) {
|
||||
if (pair.first != DataType.UBYTE)
|
||||
err("return value #${index + 1} should be ubyte")
|
||||
}
|
||||
}
|
||||
|
||||
val regCounts = mutableMapOf<Register, Int>().withDefault { 0 }
|
||||
val regCounts = mutableMapOf<CpuRegister, Int>().withDefault { 0 }
|
||||
val statusflagCounts = mutableMapOf<Statusflag, Int>().withDefault { 0 }
|
||||
fun countRegisters(from: Iterable<RegisterOrStatusflag>) {
|
||||
regCounts.clear()
|
||||
statusflagCounts.clear()
|
||||
for(p in from) {
|
||||
when(p.registerOrPair) {
|
||||
RegisterOrPair.A -> regCounts[Register.A]=regCounts.getValue(Register.A)+1
|
||||
RegisterOrPair.X -> regCounts[Register.X]=regCounts.getValue(Register.X)+1
|
||||
RegisterOrPair.Y -> regCounts[Register.Y]=regCounts.getValue(Register.Y)+1
|
||||
RegisterOrPair.A -> regCounts[CpuRegister.A]=regCounts.getValue(CpuRegister.A)+1
|
||||
RegisterOrPair.X -> regCounts[CpuRegister.X]=regCounts.getValue(CpuRegister.X)+1
|
||||
RegisterOrPair.Y -> regCounts[CpuRegister.Y]=regCounts.getValue(CpuRegister.Y)+1
|
||||
RegisterOrPair.AX -> {
|
||||
regCounts[Register.A]=regCounts.getValue(Register.A)+1
|
||||
regCounts[Register.X]=regCounts.getValue(Register.X)+1
|
||||
regCounts[CpuRegister.A]=regCounts.getValue(CpuRegister.A)+1
|
||||
regCounts[CpuRegister.X]=regCounts.getValue(CpuRegister.X)+1
|
||||
}
|
||||
RegisterOrPair.AY -> {
|
||||
regCounts[Register.A]=regCounts.getValue(Register.A)+1
|
||||
regCounts[Register.Y]=regCounts.getValue(Register.Y)+1
|
||||
regCounts[CpuRegister.A]=regCounts.getValue(CpuRegister.A)+1
|
||||
regCounts[CpuRegister.Y]=regCounts.getValue(CpuRegister.Y)+1
|
||||
}
|
||||
RegisterOrPair.XY -> {
|
||||
regCounts[Register.X]=regCounts.getValue(Register.X)+1
|
||||
regCounts[Register.Y]=regCounts.getValue(Register.Y)+1
|
||||
regCounts[CpuRegister.X]=regCounts.getValue(CpuRegister.X)+1
|
||||
regCounts[CpuRegister.Y]=regCounts.getValue(CpuRegister.Y)+1
|
||||
}
|
||||
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> { /* no sensible way to count this */ }
|
||||
null ->
|
||||
if(p.statusflag!=null)
|
||||
statusflagCounts[p.statusflag] = statusflagCounts.getValue(p.statusflag) + 1
|
||||
@ -316,43 +323,34 @@ internal class AstChecker(private val program: Program,
|
||||
} else {
|
||||
// Pass-by-reference datatypes can not occur as parameters to a subroutine directly
|
||||
// Instead, their reference (address) should be passed (as an UWORD).
|
||||
// The language has no typed pointers at this time.
|
||||
if(subroutine.parameters.any{it.type in PassByReferenceDatatypes }) {
|
||||
err("Pass-by-reference types (str, array) cannot occur as a parameter type directly. Instead, use an uword for their address, or access the variable from the outer scope directly.")
|
||||
err("Pass-by-reference types (str, array) cannot occur as a parameter type directly. Instead, use an uword to receive their address, or access the variable from the outer scope directly.")
|
||||
}
|
||||
}
|
||||
|
||||
visitStatements(subroutine.statements)
|
||||
}
|
||||
|
||||
override fun visit(repeatLoop: RepeatLoop) {
|
||||
if(repeatLoop.untilCondition.referencesIdentifiers("A", "X", "Y"))
|
||||
errors.warn("using a register in the loop condition is risky (it could get clobbered)", repeatLoop.untilCondition.position)
|
||||
if(repeatLoop.untilCondition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
|
||||
errors.err("condition value should be an integer type", repeatLoop.untilCondition.position)
|
||||
super.visit(repeatLoop)
|
||||
override fun visit(untilLoop: UntilLoop) {
|
||||
if(untilLoop.condition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
|
||||
errors.err("condition value should be an integer type", untilLoop.condition.position)
|
||||
super.visit(untilLoop)
|
||||
}
|
||||
|
||||
override fun visit(whileLoop: WhileLoop) {
|
||||
if(whileLoop.condition.referencesIdentifiers("A", "X", "Y"))
|
||||
errors.warn("using a register in the loop condition is risky (it could get clobbered)", whileLoop.condition.position)
|
||||
if(whileLoop.condition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
|
||||
errors.err("condition value should be an integer type", whileLoop.condition.position)
|
||||
super.visit(whileLoop)
|
||||
}
|
||||
|
||||
override fun visit(assignment: Assignment) {
|
||||
// assigning from a functioncall COULD return multiple values (from an asm subroutine)
|
||||
if(assignment.value is FunctionCall) {
|
||||
val stmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace)
|
||||
if (stmt is Subroutine && stmt.isAsmSubroutine) {
|
||||
if(stmt.returntypes.size>1)
|
||||
errors.err("It's not possible to store the multiple results of this asmsub call; you should use a small block of custom inline assembly for this.", assignment.value.position)
|
||||
else {
|
||||
val idt = assignment.target.inferType(program, assignment)
|
||||
if(!idt.isKnown || stmt.returntypes.single()!=idt.typeOrElse(DataType.BYTE)) {
|
||||
errors.err("return type mismatch", assignment.value.position)
|
||||
}
|
||||
if (stmt is Subroutine) {
|
||||
val idt = assignment.target.inferType(program)
|
||||
if(!idt.isKnown) {
|
||||
errors.err("return type mismatch", assignment.value.position)
|
||||
}
|
||||
if(stmt.returntypes.size <= 1 && stmt.returntypes.single() isNotAssignableTo idt.typeOrElse(DataType.BYTE)) {
|
||||
errors.err("return type mismatch", assignment.value.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -361,9 +359,9 @@ internal class AstChecker(private val program: Program,
|
||||
if(targetIdent!=null) {
|
||||
val targetVar = targetIdent.targetVarDecl(program.namespace)
|
||||
if(targetVar?.struct != null) {
|
||||
val sourceStructLv = assignment.value as? StructLiteralValue
|
||||
val sourceStructLv = assignment.value as? ArrayLiteralValue
|
||||
if (sourceStructLv != null) {
|
||||
if (sourceStructLv.values.size != targetVar.struct?.numberOfElements)
|
||||
if (sourceStructLv.value.size != targetVar.struct?.numberOfElements)
|
||||
errors.err("number of elements doesn't match struct definition", sourceStructLv.position)
|
||||
} else {
|
||||
val sourceIdent = assignment.value as? IdentifierReference
|
||||
@ -372,14 +370,28 @@ internal class AstChecker(private val program: Program,
|
||||
if (sourceVar?.struct != null) {
|
||||
if (sourceVar.struct !== targetVar.struct)
|
||||
errors.err("assignment of different struct types", assignment.position)
|
||||
} else if(sourceVar?.isArray==true) {
|
||||
if((sourceVar.value as ArrayLiteralValue).value.size != targetVar.struct?.numberOfElements)
|
||||
errors.err("number of elements doesn't match struct definition", sourceVar.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(assignment.value.inferType(program) != assignment.target.inferType(program, assignment))
|
||||
errors.err("assignment value is of different type as the target", assignment.value.position)
|
||||
val targetDt = assignment.target.inferType(program)
|
||||
val valueDt = assignment.value.inferType(program)
|
||||
if(valueDt.isKnown && !(valueDt isAssignableTo targetDt)) {
|
||||
if(targetDt.typeOrElse(DataType.STRUCT) in IterableDatatypes)
|
||||
errors.err("cannot assign value to string or array", assignment.value.position)
|
||||
else
|
||||
errors.err("value's type doesn't match target", assignment.value.position)
|
||||
}
|
||||
|
||||
if(assignment.value is TypecastExpression) {
|
||||
if(assignment.isAugmentable && targetDt.istype(DataType.FLOAT))
|
||||
errors.err("typecasting a float value in-place makes no sense", assignment.value.position)
|
||||
}
|
||||
|
||||
super.visit(assignment)
|
||||
}
|
||||
@ -397,8 +409,7 @@ internal class AstChecker(private val program: Program,
|
||||
val targetIdentifier = assignTarget.identifier
|
||||
if (targetIdentifier != null) {
|
||||
val targetName = targetIdentifier.nameInSource
|
||||
val targetSymbol = program.namespace.lookup(targetName, assignment)
|
||||
when (targetSymbol) {
|
||||
when (val targetSymbol = program.namespace.lookup(targetName, assignment)) {
|
||||
null -> {
|
||||
errors.err("undefined symbol: ${targetIdentifier.nameInSource.joinToString(".")}", targetIdentifier.position)
|
||||
return
|
||||
@ -423,22 +434,15 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
if (assignment is Assignment) {
|
||||
|
||||
if (assignment.aug_op != null)
|
||||
throw FatalAstException("augmented assignment should have been converted into normal assignment")
|
||||
|
||||
val targetDatatype = assignTarget.inferType(program, assignment)
|
||||
val targetDatatype = assignTarget.inferType(program)
|
||||
if (targetDatatype.isKnown) {
|
||||
val constVal = assignment.value.constValue(program)
|
||||
if (constVal != null) {
|
||||
checkValueTypeAndRange(targetDatatype.typeOrElse(DataType.BYTE), constVal)
|
||||
} else {
|
||||
val sourceDatatype = assignment.value.inferType(program)
|
||||
if (!sourceDatatype.isKnown) {
|
||||
if (assignment.value is FunctionCall) {
|
||||
val targetStmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace)
|
||||
if (targetStmt != null)
|
||||
errors.err("function call doesn't return a suitable value to use in assignment", assignment.value.position)
|
||||
} else
|
||||
if (sourceDatatype.isUnknown) {
|
||||
if (assignment.value !is FunctionCall)
|
||||
errors.err("assignment value is invalid or has no proper datatype", assignment.value.position)
|
||||
} else {
|
||||
checkAssignmentCompatible(targetDatatype.typeOrElse(DataType.BYTE), assignTarget,
|
||||
@ -454,21 +458,21 @@ internal class AstChecker(private val program: Program,
|
||||
if(variable==null)
|
||||
errors.err("pointer-of operand must be the name of a heap variable", addressOf.position)
|
||||
else {
|
||||
if(variable.datatype !in ArrayDatatypes && variable.datatype != DataType.STR && variable.datatype!=DataType.STRUCT)
|
||||
if(variable.datatype !in ArrayDatatypes
|
||||
&& variable.type!=VarDeclType.MEMORY
|
||||
&& variable.struct == null
|
||||
&& variable.datatype != DataType.STR && variable.datatype!=DataType.STRUCT)
|
||||
errors.err("invalid pointer-of operand type", addressOf.position)
|
||||
}
|
||||
super.visit(addressOf)
|
||||
}
|
||||
|
||||
override fun visit(decl: VarDecl) {
|
||||
fun err(msg: String, position: Position?=null) {
|
||||
errors.err(msg, position ?: decl.position)
|
||||
}
|
||||
fun err(msg: String, position: Position?=null) = errors.err(msg, position ?: decl.position)
|
||||
|
||||
// the initializer value can't refer to the variable itself (recursive definition)
|
||||
if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true) {
|
||||
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexVar?.referencesIdentifier(decl.name) == true)
|
||||
err("recursive var declaration")
|
||||
}
|
||||
|
||||
// CONST can only occur on simple types (byte, word, float)
|
||||
if(decl.type== VarDeclType.CONST) {
|
||||
@ -476,10 +480,12 @@ internal class AstChecker(private val program: Program,
|
||||
err("const modifier can only be used on numeric types (byte, word, float)")
|
||||
}
|
||||
|
||||
// FLOATS
|
||||
if(!compilerOptions.floats && decl.datatype in setOf(DataType.FLOAT, DataType.ARRAY_F) && decl.type!= VarDeclType.MEMORY) {
|
||||
// FLOATS enabled?
|
||||
if(!compilerOptions.floats && decl.datatype in setOf(DataType.FLOAT, DataType.ARRAY_F) && decl.type!= VarDeclType.MEMORY)
|
||||
err("floating point used, but that is not enabled via options")
|
||||
}
|
||||
|
||||
if(decl.datatype == DataType.FLOAT && (decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE || decl.zeropage==ZeropageWish.PREFER_ZEROPAGE))
|
||||
errors.warn("floating point values won't be placed in Zeropage due to size constraints", decl.position)
|
||||
|
||||
// ARRAY without size specifier MUST have an iterable initializer value
|
||||
if(decl.isArray && decl.arraysize==null) {
|
||||
@ -512,29 +518,21 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
when(decl.value) {
|
||||
null -> {
|
||||
// a vardecl without an initial value, don't bother with the rest
|
||||
return super.visit(decl)
|
||||
// a vardecl without an initial value, don't bother with it
|
||||
}
|
||||
is RangeExpr -> throw FatalAstException("range expression should have been converted to a true array value")
|
||||
is StringLiteralValue -> {
|
||||
checkValueTypeAndRangeString(decl.datatype, decl.value as StringLiteralValue)
|
||||
}
|
||||
is ArrayLiteralValue -> {
|
||||
val arraySpec = decl.arraysize ?: ArrayIndex.forArray(decl.value as ArrayLiteralValue)
|
||||
checkValueTypeAndRangeArray(decl.datatype, decl.struct, arraySpec, decl.value as ArrayLiteralValue)
|
||||
}
|
||||
is NumericLiteralValue -> {
|
||||
checkValueTypeAndRange(decl.datatype, decl.value as NumericLiteralValue)
|
||||
}
|
||||
is StructLiteralValue -> {
|
||||
if(decl.datatype==DataType.STRUCT) {
|
||||
val struct = decl.struct!!
|
||||
val structLv = decl.value as StructLiteralValue
|
||||
if(struct.numberOfElements != structLv.values.size) {
|
||||
val structLv = decl.value as ArrayLiteralValue
|
||||
if(struct.numberOfElements != structLv.value.size) {
|
||||
errors.err("struct value has incorrect number of elements", structLv.position)
|
||||
return
|
||||
}
|
||||
for(value in structLv.values.zip(struct.statements)) {
|
||||
for(value in structLv.value.zip(struct.statements)) {
|
||||
val memberdecl = value.second as VarDecl
|
||||
val constValue = value.first.constValue(program)
|
||||
if(constValue==null) {
|
||||
@ -548,19 +546,25 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
errors.err("struct literal is wrong type to initialize this variable", decl.value!!.position)
|
||||
val arraySpec = decl.arraysize ?: ArrayIndex.forArray(decl.value as ArrayLiteralValue)
|
||||
checkValueTypeAndRangeArray(decl.datatype, decl.struct, arraySpec, decl.value as ArrayLiteralValue)
|
||||
}
|
||||
}
|
||||
is NumericLiteralValue -> {
|
||||
checkValueTypeAndRange(decl.datatype, decl.value as NumericLiteralValue)
|
||||
}
|
||||
else -> {
|
||||
err("var/const declaration needs a compile-time constant initializer value, or range, instead found: ${decl.value!!.javaClass.simpleName}")
|
||||
super.visit(decl)
|
||||
return
|
||||
if(decl.type==VarDeclType.CONST) {
|
||||
err("const declaration needs a compile-time constant initializer value, or range")
|
||||
super.visit(decl)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
VarDeclType.MEMORY -> {
|
||||
if(decl.arraysize!=null) {
|
||||
val arraySize = decl.arraysize!!.size() ?: 1
|
||||
val arraySize = decl.arraysize!!.constIndex() ?: 1
|
||||
when(decl.datatype) {
|
||||
DataType.ARRAY_B, DataType.ARRAY_UB ->
|
||||
if(arraySize > 256)
|
||||
@ -575,20 +579,58 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
if(decl.value !is NumericLiteralValue) {
|
||||
err("value of memory var decl is not a numeric literal (it is a ${decl.value!!.javaClass.simpleName}).", decl.value?.position)
|
||||
} else {
|
||||
if(decl.value is NumericLiteralValue) {
|
||||
val value = decl.value as NumericLiteralValue
|
||||
if (value.type !in IntegerDatatypes || value.number.toInt() < 0 || value.number.toInt() > 65535) {
|
||||
err("memory address must be valid integer 0..\$ffff", decl.value?.position)
|
||||
}
|
||||
} else {
|
||||
err("value of memory mapped variable can only be a number, perhaps you meant to use an address pointer type instead?", decl.value?.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val declValue = decl.value
|
||||
if(declValue!=null && decl.type==VarDeclType.VAR && !declValue.inferType(program).istype(decl.datatype))
|
||||
throw FatalAstException("initialisation value $declValue is of different type (${declValue.inferType(program)} as the variable (${decl.datatype}) at ${decl.position}")
|
||||
if(declValue!=null && decl.type==VarDeclType.VAR) {
|
||||
if(decl.datatype==DataType.STRUCT) {
|
||||
val valueIdt = declValue.inferType(program)
|
||||
if(!valueIdt.isKnown)
|
||||
throw AstException("unknown dt")
|
||||
val valueDt = valueIdt.typeOrElse(DataType.STRUCT)
|
||||
if(valueDt !in ArrayDatatypes)
|
||||
err("initialisation of struct should be with array value", declValue.position)
|
||||
} else if (!declValue.inferType(program).istype(decl.datatype)) {
|
||||
err("initialisation value has incompatible type (${declValue.inferType(program)}) for the variable (${decl.datatype})", declValue.position)
|
||||
}
|
||||
}
|
||||
|
||||
// array length limits
|
||||
if(decl.isArray) {
|
||||
val length = decl.arraysize!!.constIndex() ?: 1
|
||||
when (decl.datatype) {
|
||||
DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||
if(length==0 || length>256)
|
||||
err("string and byte array length must be 1-256")
|
||||
}
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
if(length==0 || length>128)
|
||||
err("word array length must be 1-128")
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
if(length==0 || length>51)
|
||||
err("float array length must be 1-51")
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
// string assignment is not supported in a vard
|
||||
if(decl.datatype==DataType.STR) {
|
||||
if(decl.value==null)
|
||||
err("string var must be initialized with a string literal")
|
||||
else if (decl.type==VarDeclType.VAR && decl.value !is StringLiteralValue)
|
||||
err("string var can only be initialized with a string literal")
|
||||
}
|
||||
|
||||
super.visit(decl)
|
||||
}
|
||||
@ -670,9 +712,17 @@ internal class AstChecker(private val program: Program,
|
||||
err("this directive may only occur in a block or at module level")
|
||||
if(directive.args.isEmpty())
|
||||
err("missing option directive argument(s)")
|
||||
else if(directive.args.map{it.name in setOf("enable_floats", "force_output")}.any { !it })
|
||||
else if(directive.args.map{it.name in setOf("enable_floats", "force_output", "no_sysinit")}.any { !it })
|
||||
err("invalid option directive argument(s)")
|
||||
}
|
||||
"%target" -> {
|
||||
if(directive.parent !is Block && directive.parent !is Module)
|
||||
err("this directive may only occur in a block or at module level")
|
||||
if(directive.args.size != 1)
|
||||
err("directive requires one argument")
|
||||
if(directive.args.single().name !in setOf(C64Target.name, Cx16Target.name))
|
||||
err("invalid compilation target")
|
||||
}
|
||||
else -> throw SyntaxError("invalid directive ${directive.directive}", directive.position)
|
||||
}
|
||||
super.visit(directive)
|
||||
@ -695,6 +745,22 @@ internal class AstChecker(private val program: Program,
|
||||
checkValueTypeAndRangeArray(array.type.typeOrElse(DataType.STRUCT), null, arrayspec, array)
|
||||
}
|
||||
|
||||
fun isPassByReferenceElement(e: Expression): Boolean {
|
||||
if(e is IdentifierReference) {
|
||||
val decl = e.targetVarDecl(program.namespace)!!
|
||||
return decl.datatype in PassByReferenceDatatypes
|
||||
}
|
||||
return e is StringLiteralValue
|
||||
}
|
||||
|
||||
if(array.parent is VarDecl) {
|
||||
if (!array.value.all { it is NumericLiteralValue || it is AddressOf || isPassByReferenceElement(it) })
|
||||
errors.err("array literal for variable initialization contains invalid types", array.position)
|
||||
} else if(array.parent is ForLoop) {
|
||||
if (!array.value.all { it.constValue(program) != null })
|
||||
errors.err("array literal for iteration must contain constants. Try using a separate array variable instead?", array.position)
|
||||
}
|
||||
|
||||
super.visit(array)
|
||||
}
|
||||
|
||||
@ -704,16 +770,30 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(expr: PrefixExpression) {
|
||||
val idt = expr.inferType(program)
|
||||
if(!idt.isKnown)
|
||||
return // any error should be reported elsewhere
|
||||
|
||||
val dt = idt.typeOrElse(DataType.STRUCT)
|
||||
if(expr.operator=="-") {
|
||||
val dt = expr.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
if (dt != DataType.BYTE && dt != DataType.WORD && dt != DataType.FLOAT) {
|
||||
errors.err("can only take negative of a signed number type", expr.position)
|
||||
}
|
||||
}
|
||||
else if(expr.operator == "not") {
|
||||
if(dt !in IntegerDatatypes)
|
||||
errors.err("can only use boolean not on integer types", expr.position)
|
||||
}
|
||||
else if(expr.operator == "~") {
|
||||
if(dt !in IntegerDatatypes)
|
||||
errors.err("can only use bitwise invert on integer types", expr.position)
|
||||
}
|
||||
super.visit(expr)
|
||||
}
|
||||
|
||||
override fun visit(expr: BinaryExpression) {
|
||||
super.visit(expr)
|
||||
|
||||
val leftIDt = expr.left.inferType(program)
|
||||
val rightIDt = expr.right.inferType(program)
|
||||
if(!leftIDt.isKnown || !rightIDt.isKnown)
|
||||
@ -735,7 +815,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
"**" -> {
|
||||
if(leftDt in IntegerDatatypes)
|
||||
errors.err("power operator requires floating point", expr.position)
|
||||
errors.err("power operator requires floating point operands", expr.position)
|
||||
}
|
||||
"and", "or", "xor" -> {
|
||||
// only integer numeric operands accepted, and if literal constants, only boolean values accepted (0 or 1)
|
||||
@ -751,21 +831,14 @@ internal class AstChecker(private val program: Program,
|
||||
if(leftDt !in IntegerDatatypes || rightDt !in IntegerDatatypes)
|
||||
errors.err("bitwise operator can only be used on integer operands", expr.right.position)
|
||||
}
|
||||
"<<", ">>" -> {
|
||||
// for now, bit-shifts can only shift by a constant number
|
||||
val constRight = expr.right.constValue(program)
|
||||
if(constRight==null)
|
||||
errors.err("bit-shift can only be done by a constant number (for now)", expr.right.position)
|
||||
}
|
||||
}
|
||||
|
||||
if(leftDt !in NumericDatatypes)
|
||||
errors.err("left operand is not numeric", expr.left.position)
|
||||
if(rightDt!in NumericDatatypes)
|
||||
errors.err("right operand is not numeric", expr.right.position)
|
||||
if(leftDt !in NumericDatatypes && leftDt != DataType.STR)
|
||||
errors.err("left operand is not numeric or str", expr.left.position)
|
||||
if(rightDt!in NumericDatatypes && rightDt != DataType.STR)
|
||||
errors.err("right operand is not numeric or str", expr.right.position)
|
||||
if(leftDt!=rightDt)
|
||||
errors.err("left and right operands aren't the same type", expr.left.position)
|
||||
super.visit(expr)
|
||||
}
|
||||
|
||||
override fun visit(typecast: TypecastExpression) {
|
||||
@ -823,6 +896,32 @@ internal class AstChecker(private val program: Program,
|
||||
errors.warn("sgn() of unsigned type is always 0 or 1, this is perhaps not what was intended", functionCall.args.first().position)
|
||||
}
|
||||
|
||||
val error = VerifyFunctionArgTypes.checkTypes(functionCall, functionCall.definingScope(), program)
|
||||
if(error!=null)
|
||||
errors.err(error, functionCall.position)
|
||||
|
||||
// check the functions that return multiple returnvalues.
|
||||
val stmt = functionCall.target.targetStatement(program.namespace)
|
||||
if (stmt is Subroutine) {
|
||||
if (stmt.returntypes.size > 1) {
|
||||
// Currently, it's only possible to handle ONE (or zero) return values from a subroutine.
|
||||
// asmsub routines can have multiple return values, for instance in 2 different registers.
|
||||
// It's not (yet) possible to handle these multiple return values because assignments
|
||||
// are only to a single unique target at the same time.
|
||||
// EXCEPTION:
|
||||
// if the asmsub returns multiple values and one of them is via a status register bit,
|
||||
// it *is* possible to handle them by just actually assigning the register value and
|
||||
// dealing with the status bit as just being that, the status bit after the call.
|
||||
val (returnRegisters, returnStatusflags) = stmt.asmReturnvaluesRegisters.partition { rr -> rr.registerOrPair != null }
|
||||
if (returnRegisters.isEmpty() || returnRegisters.size == 1) {
|
||||
if (returnStatusflags.any())
|
||||
errors.warn("this asmsub also has one or more return 'values' in one of the status flags", functionCall.position)
|
||||
} else {
|
||||
errors.err("It's not possible to store the multiple result values of this asmsub call; you should use a small block of custom inline assembly for this.", functionCall.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.visit(functionCall)
|
||||
}
|
||||
|
||||
@ -845,12 +944,18 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
if(functionCallStatement.target.nameInSource.last() in setOf("lsl", "lsr", "rol", "ror", "rol2", "ror2", "swap", "sort", "reverse")) {
|
||||
if(functionCallStatement.target.nameInSource.last() in setOf("rol", "ror", "rol2", "ror2", "swap", "sort", "reverse")) {
|
||||
// in-place modification, can't be done on literals
|
||||
if(functionCallStatement.args.any { it !is IdentifierReference && it !is RegisterExpr && it !is ArrayIndexedExpression && it !is DirectMemoryRead }) {
|
||||
errors.err("can't use that as argument to a in-place modifying function", functionCallStatement.args.first().position)
|
||||
if(functionCallStatement.args.any { it !is IdentifierReference && it !is ArrayIndexedExpression && it !is DirectMemoryRead }) {
|
||||
errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position)
|
||||
}
|
||||
}
|
||||
|
||||
val error = VerifyFunctionArgTypes.checkTypes(functionCallStatement, functionCallStatement.definingScope(), program)
|
||||
if(error!=null) {
|
||||
errors.err(error, functionCallStatement.args.firstOrNull()?.position ?: functionCallStatement.position)
|
||||
}
|
||||
|
||||
super.visit(functionCallStatement)
|
||||
}
|
||||
|
||||
@ -859,77 +964,35 @@ internal class AstChecker(private val program: Program,
|
||||
errors.err("cannot use arguments when calling a label", position)
|
||||
|
||||
if(target is BuiltinFunctionStatementPlaceholder) {
|
||||
// it's a call to a builtin function.
|
||||
val func = BuiltinFunctions.getValue(target.name)
|
||||
if(args.size!=func.parameters.size)
|
||||
errors.err("invalid number of arguments", position)
|
||||
else {
|
||||
val paramTypesForAddressOf = PassByReferenceDatatypes + DataType.UWORD
|
||||
for (arg in args.withIndex().zip(func.parameters)) {
|
||||
val argDt=arg.first.value.inferType(program)
|
||||
if (argDt.isKnown
|
||||
&& !(argDt.typeOrElse(DataType.STRUCT) isAssignableTo arg.second.possibleDatatypes)
|
||||
&& (argDt.typeOrElse(DataType.STRUCT) != DataType.UWORD || arg.second.possibleDatatypes.intersect(paramTypesForAddressOf).isEmpty())) {
|
||||
errors.err("builtin function '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.possibleDatatypes}", position)
|
||||
}
|
||||
if(target.name=="swap") {
|
||||
// swap() is a bit weird because this one is translated into a operations directly, instead of being a function call
|
||||
val dt1 = args[0].inferType(program)
|
||||
val dt2 = args[1].inferType(program)
|
||||
if (dt1 != dt2)
|
||||
errors.err("swap requires 2 args of identical type", position)
|
||||
else if (args[0].constValue(program) != null || args[1].constValue(program) != null)
|
||||
errors.err("swap requires 2 variables, not constant value(s)", position)
|
||||
else if(args[0] isSameAs args[1])
|
||||
errors.err("swap should have 2 different args", position)
|
||||
else if(dt1.typeOrElse(DataType.STRUCT) !in NumericDatatypes)
|
||||
errors.err("swap requires args of numerical type", position)
|
||||
}
|
||||
else if(target.name=="all" || target.name=="any") {
|
||||
if((args[0] as? AddressOf)?.identifier?.targetVarDecl(program.namespace)?.datatype == DataType.STR) {
|
||||
errors.err("any/all on a string is useless (is always true unless the string is empty)", position)
|
||||
}
|
||||
if(target.name=="swap") {
|
||||
// swap() is a bit weird because this one is translated into a operations directly, instead of being a function call
|
||||
val dt1 = args[0].inferType(program)
|
||||
val dt2 = args[1].inferType(program)
|
||||
if (dt1 != dt2)
|
||||
errors.err("swap requires 2 args of identical type", position)
|
||||
else if (args[0].constValue(program) != null || args[1].constValue(program) != null)
|
||||
errors.err("swap requires 2 variables, not constant value(s)", position)
|
||||
else if(args[0] isSameAs args[1])
|
||||
errors.err("swap should have 2 different args", position)
|
||||
else if(dt1.typeOrElse(DataType.STRUCT) !in NumericDatatypes)
|
||||
errors.err("swap requires args of numerical type", position)
|
||||
}
|
||||
else if(target.name=="all" || target.name=="any") {
|
||||
if((args[0] as? AddressOf)?.identifier?.targetVarDecl(program.namespace)?.datatype == DataType.STR) {
|
||||
errors.err("any/all on a string is useless (is always true unless the string is empty)", position)
|
||||
}
|
||||
if(args[0].inferType(program).typeOrElse(DataType.STR) == DataType.STR) {
|
||||
errors.err("any/all on a string is useless (is always true unless the string is empty)", position)
|
||||
}
|
||||
if(args[0].inferType(program).typeOrElse(DataType.STR) == DataType.STR) {
|
||||
errors.err("any/all on a string is useless (is always true unless the string is empty)", position)
|
||||
}
|
||||
}
|
||||
} else if(target is Subroutine) {
|
||||
if(args.size!=target.parameters.size)
|
||||
errors.err("invalid number of arguments", position)
|
||||
else {
|
||||
for (arg in args.withIndex().zip(target.parameters)) {
|
||||
val argIDt = arg.first.value.inferType(program)
|
||||
if(!argIDt.isKnown) {
|
||||
if(target.regXasResult())
|
||||
errors.warn("subroutine call return value in X register is discarded and replaced by 0", position)
|
||||
if(target.isAsmSubroutine) {
|
||||
for (arg in args.zip(target.parameters)) {
|
||||
val argIDt = arg.first.inferType(program)
|
||||
if (!argIDt.isKnown)
|
||||
return
|
||||
}
|
||||
val argDt=argIDt.typeOrElse(DataType.STRUCT)
|
||||
if(!(argDt isAssignableTo arg.second.type)) {
|
||||
// for asm subroutines having STR param it's okay to provide a UWORD (address value)
|
||||
if(!(target.isAsmSubroutine && arg.second.type == DataType.STR && argDt == DataType.UWORD))
|
||||
errors.err("subroutine '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.type}", position)
|
||||
}
|
||||
|
||||
if(target.isAsmSubroutine) {
|
||||
if (target.asmParameterRegisters[arg.first.index].registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.XY, RegisterOrPair.X)) {
|
||||
if (arg.first.value !is NumericLiteralValue && arg.first.value !is IdentifierReference)
|
||||
errors.warn("calling a subroutine that expects X as a parameter is problematic, more so when providing complex arguments. If you see a compiler error/crash about this later, try to simplify this call", position)
|
||||
}
|
||||
|
||||
// check if the argument types match the register(pairs)
|
||||
val asmParamReg = target.asmParameterRegisters[arg.first.index]
|
||||
if(asmParamReg.statusflag!=null) {
|
||||
if(argDt !in ByteDatatypes)
|
||||
errors.err("subroutine '${target.name}' argument ${arg.first.index + 1} must be byte type for statusflag", position)
|
||||
} else if(asmParamReg.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) {
|
||||
if(argDt !in ByteDatatypes)
|
||||
errors.err("subroutine '${target.name}' argument ${arg.first.index + 1} must be byte type for single register", position)
|
||||
} else if(asmParamReg.registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
|
||||
if(argDt !in WordDatatypes + IterableDatatypes)
|
||||
errors.err("subroutine '${target.name}' argument ${arg.first.index + 1} must be word type for register pair", position)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -950,7 +1013,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
} else if(postIncrDecr.target.arrayindexed != null) {
|
||||
val target = postIncrDecr.target.arrayindexed?.identifier?.targetStatement(program.namespace)
|
||||
val target = postIncrDecr.target.arrayindexed?.arrayvar?.targetStatement(program.namespace)
|
||||
if(target==null) {
|
||||
errors.err("undefined symbol", postIncrDecr.position)
|
||||
}
|
||||
@ -965,32 +1028,38 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
|
||||
val target = arrayIndexedExpression.identifier.targetStatement(program.namespace)
|
||||
val target = arrayIndexedExpression.arrayvar.targetStatement(program.namespace)
|
||||
if(target is VarDecl) {
|
||||
if(target.datatype !in IterableDatatypes)
|
||||
errors.err("indexing requires an iterable variable", arrayIndexedExpression.position)
|
||||
val arraysize = target.arraysize?.size()
|
||||
val arraysize = target.arraysize?.constIndex()
|
||||
if(arraysize!=null) {
|
||||
// check out of bounds
|
||||
val index = (arrayIndexedExpression.arrayspec.index as? NumericLiteralValue)?.number?.toInt()
|
||||
val index = arrayIndexedExpression.indexer.constIndex()
|
||||
if(index!=null && (index<0 || index>=arraysize))
|
||||
errors.err("array index out of bounds", arrayIndexedExpression.arrayspec.position)
|
||||
errors.err("array index out of bounds", arrayIndexedExpression.indexer.position)
|
||||
} else if(target.datatype == DataType.STR) {
|
||||
if(target.value is StringLiteralValue) {
|
||||
// check string lengths for non-memory mapped strings
|
||||
val stringLen = (target.value as StringLiteralValue).value.length
|
||||
val index = (arrayIndexedExpression.arrayspec.index as? NumericLiteralValue)?.number?.toInt()
|
||||
val index = arrayIndexedExpression.indexer.constIndex()
|
||||
if (index != null && (index < 0 || index >= stringLen))
|
||||
errors.err("index out of bounds", arrayIndexedExpression.arrayspec.position)
|
||||
errors.err("index out of bounds", arrayIndexedExpression.indexer.position)
|
||||
}
|
||||
}
|
||||
} else
|
||||
errors.err("indexing requires a variable to act upon", arrayIndexedExpression.position)
|
||||
|
||||
// check index value 0..255
|
||||
val dtx = arrayIndexedExpression.arrayspec.index.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
if(dtx!= DataType.UBYTE && dtx!= DataType.BYTE)
|
||||
val dtxNum = arrayIndexedExpression.indexer.indexNum?.inferType(program)?.typeOrElse(DataType.STRUCT)
|
||||
if(dtxNum!=null && dtxNum != DataType.UBYTE && dtxNum != DataType.BYTE)
|
||||
errors.err("array indexing is limited to byte size 0..255", arrayIndexedExpression.position)
|
||||
val dtxVar = arrayIndexedExpression.indexer.indexVar?.inferType(program)?.typeOrElse(DataType.STRUCT)
|
||||
if(dtxVar!=null && dtxVar != DataType.UBYTE && dtxVar != DataType.BYTE)
|
||||
errors.err("array indexing is limited to byte size 0..255", arrayIndexedExpression.position)
|
||||
|
||||
if(arrayIndexedExpression.indexer.origExpression!=null)
|
||||
throw FatalAstException("array indexer should have been replaced with a temp var @ ${arrayIndexedExpression.indexer.position}")
|
||||
|
||||
super.visit(arrayIndexedExpression)
|
||||
}
|
||||
@ -1050,35 +1119,14 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(scope: AnonymousScope) {
|
||||
visitStatements(scope.statements)
|
||||
}
|
||||
|
||||
private fun visitStatements(statements: List<Statement>) {
|
||||
for((index, stmt) in statements.withIndex()) {
|
||||
if(index < statements.lastIndex && statements[index+1] !is Subroutine) {
|
||||
when {
|
||||
stmt is FunctionCallStatement && stmt.target.nameInSource.last() == "exit" -> {
|
||||
errors.warn("unreachable code, preceding exit call will never return", statements[index + 1].position)
|
||||
}
|
||||
stmt is Return && statements[index + 1] !is Subroutine -> {
|
||||
errors.warn("unreachable code, preceding return statement", statements[index + 1].position)
|
||||
}
|
||||
stmt is Jump && statements[index + 1] !is Subroutine -> {
|
||||
errors.warn("unreachable code, preceding jump statement", statements[index + 1].position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stmt.accept(this)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: Statement): Statement? {
|
||||
val targetStatement = target.targetStatement(program.namespace)
|
||||
if(targetStatement is Label || targetStatement is Subroutine || targetStatement is BuiltinFunctionStatementPlaceholder)
|
||||
return targetStatement
|
||||
errors.err("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position)
|
||||
else if(targetStatement==null)
|
||||
errors.err("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position)
|
||||
else
|
||||
errors.err("cannot call that: ${target.nameInSource.joinToString(".")}", statement.position)
|
||||
return null
|
||||
}
|
||||
|
||||
@ -1102,7 +1150,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
if(value.type.isUnknown)
|
||||
return err("attempt to check values of array with as yet unknown datatype")
|
||||
return false
|
||||
|
||||
when (targetDt) {
|
||||
DataType.STR -> return err("string value expected")
|
||||
@ -1111,15 +1159,12 @@ internal class AstChecker(private val program: Program,
|
||||
if(value.type.istype(targetDt)) {
|
||||
if(!checkArrayValues(value, targetDt))
|
||||
return false
|
||||
val arraySpecSize = arrayspec.size()
|
||||
val arraySpecSize = arrayspec.constIndex()
|
||||
val arraySize = value.value.size
|
||||
if(arraySpecSize!=null && arraySpecSize>0) {
|
||||
if(arraySpecSize<1 || arraySpecSize>256)
|
||||
return err("byte array length must be 1-256")
|
||||
val constX = arrayspec.index.constValue(program)
|
||||
if(constX?.type !in IntegerDatatypes)
|
||||
return err("array size specifier must be constant integer value")
|
||||
val expectedSize = constX!!.number.toInt()
|
||||
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
|
||||
if (arraySize != expectedSize)
|
||||
return err("initializer array size mismatch (expecting $expectedSize, got $arraySize)")
|
||||
return true
|
||||
@ -1133,15 +1178,12 @@ internal class AstChecker(private val program: Program,
|
||||
if(value.type.istype(targetDt)) {
|
||||
if(!checkArrayValues(value, targetDt))
|
||||
return false
|
||||
val arraySpecSize = arrayspec.size()
|
||||
val arraySpecSize = arrayspec.constIndex()
|
||||
val arraySize = value.value.size
|
||||
if(arraySpecSize!=null && arraySpecSize>0) {
|
||||
if(arraySpecSize<1 || arraySpecSize>128)
|
||||
return err("word array length must be 1-128")
|
||||
val constX = arrayspec.index.constValue(program)
|
||||
if(constX?.type !in IntegerDatatypes)
|
||||
return err("array size specifier must be constant integer value")
|
||||
val expectedSize = constX!!.number.toInt()
|
||||
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
|
||||
if (arraySize != expectedSize)
|
||||
return err("initializer array size mismatch (expecting $expectedSize, got $arraySize)")
|
||||
return true
|
||||
@ -1156,14 +1198,11 @@ internal class AstChecker(private val program: Program,
|
||||
if(!checkArrayValues(value, targetDt))
|
||||
return false
|
||||
val arraySize = value.value.size
|
||||
val arraySpecSize = arrayspec.size()
|
||||
val arraySpecSize = arrayspec.constIndex()
|
||||
if(arraySpecSize!=null && arraySpecSize>0) {
|
||||
if(arraySpecSize < 1 || arraySpecSize>51)
|
||||
return err("float array length must be 1-51")
|
||||
val constX = arrayspec.index.constValue(program)
|
||||
if(constX?.type !in IntegerDatatypes)
|
||||
return err("array size specifier must be constant integer value")
|
||||
val expectedSize = constX!!.number.toInt()
|
||||
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
|
||||
if (arraySize != expectedSize)
|
||||
return err("initializer array size mismatch (expecting $expectedSize, got $arraySize)")
|
||||
} else
|
||||
@ -1171,7 +1210,7 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
// check if the floating point values are all within range
|
||||
val doubles = value.value.map {it.constValue(program)?.number!!.toDouble()}.toDoubleArray()
|
||||
if(doubles.any { it < CompilationTarget.machine.FLOAT_MAX_NEGATIVE || it > CompilationTarget.machine.FLOAT_MAX_POSITIVE })
|
||||
if(doubles.any { it < CompilationTarget.instance.machine.FLOAT_MAX_NEGATIVE || it > CompilationTarget.instance.machine.FLOAT_MAX_POSITIVE })
|
||||
return err("floating point value overflow")
|
||||
return true
|
||||
}
|
||||
@ -1184,7 +1223,7 @@ internal class AstChecker(private val program: Program,
|
||||
for(elt in value.value.zip(struct.statements)) {
|
||||
val vardecl = elt.second as VarDecl
|
||||
val valuetype = elt.first.inferType(program)
|
||||
if (!valuetype.isKnown || !(valuetype.typeOrElse(DataType.STRUCT) isAssignableTo vardecl.datatype)) {
|
||||
if (!valuetype.isKnown || valuetype isNotAssignableTo vardecl.datatype) {
|
||||
errors.err("invalid struct member init value type $valuetype, expected ${vardecl.datatype}", elt.first.position)
|
||||
return false
|
||||
}
|
||||
@ -1248,7 +1287,11 @@ internal class AstChecker(private val program: Program,
|
||||
is AddressOf -> it.identifier.heapId(program.namespace)
|
||||
is TypecastExpression -> {
|
||||
val constVal = it.expression.constValue(program)
|
||||
constVal?.cast(it.type)?.number?.toInt() ?: -9999999
|
||||
val cast = constVal?.cast(it.type)
|
||||
if(cast==null || !cast.isValid)
|
||||
-9999999
|
||||
else
|
||||
cast.valueOrZero().number.toInt()
|
||||
}
|
||||
else -> -9999999
|
||||
}
|
||||
@ -1268,7 +1311,7 @@ internal class AstChecker(private val program: Program,
|
||||
correct = array.all { it in -32768..32767 }
|
||||
}
|
||||
DataType.ARRAY_F -> correct = true
|
||||
else -> throw AstException("invalid array type $type")
|
||||
else -> throw FatalAstException("invalid array type $type")
|
||||
}
|
||||
if (!correct)
|
||||
errors.err("array value out of range for type $type", value.position)
|
||||
@ -1293,8 +1336,8 @@ internal class AstChecker(private val program: Program,
|
||||
DataType.STR -> sourceDatatype== DataType.STR
|
||||
DataType.STRUCT -> {
|
||||
if(sourceDatatype==DataType.STRUCT) {
|
||||
val structLv = sourceValue as StructLiteralValue
|
||||
val numValues = structLv.values.size
|
||||
val structLv = sourceValue as ArrayLiteralValue
|
||||
val numValues = structLv.value.size
|
||||
val targetstruct = target.identifier!!.targetVarDecl(program.namespace)!!.struct!!
|
||||
return targetstruct.numberOfElements == numValues
|
||||
}
|
||||
@ -1312,9 +1355,7 @@ internal class AstChecker(private val program: Program,
|
||||
else if(sourceDatatype== DataType.FLOAT && targetDatatype in IntegerDatatypes)
|
||||
errors.err("cannot assign float to ${targetDatatype.name.toLowerCase()}; possible loss of precision. Suggestion: round the value or revert to integer arithmetic", position)
|
||||
else {
|
||||
if(targetDatatype==DataType.UWORD && sourceDatatype in PassByReferenceDatatypes)
|
||||
errors.err("cannot assign ${sourceDatatype.name.toLowerCase()} to ${targetDatatype.name.toLowerCase()}, perhaps forgot '&' ?", position)
|
||||
else
|
||||
if(targetDatatype!=DataType.UWORD && sourceDatatype !in PassByReferenceDatatypes)
|
||||
errors.err("cannot assign ${sourceDatatype.name.toLowerCase()} to ${targetDatatype.name.toLowerCase()}", position)
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
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.*
|
||||
@ -10,99 +8,88 @@ import prog8.ast.statements.*
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.functions.BuiltinFunctions
|
||||
|
||||
|
||||
internal class AstIdentifiersChecker(private val program: Program,
|
||||
private val errors: ErrorReporter) : IAstModifyingVisitor {
|
||||
internal class AstIdentifiersChecker(private val program: Program, private val errors: ErrorReporter) : IAstVisitor {
|
||||
private var blocks = mutableMapOf<String, Block>()
|
||||
private val vardeclsToAdd = mutableMapOf<INameScope, MutableList<VarDecl>>()
|
||||
|
||||
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) {
|
||||
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 {
|
||||
override fun visit(block: Block) {
|
||||
if(block.name in CompilationTarget.instance.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
|
||||
|
||||
return super.visit(block)
|
||||
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.args.single(), DataType.UBYTE, false, functionCall.position)
|
||||
typecast.linkParents(functionCall.parent)
|
||||
return super.visit(typecast)
|
||||
override fun visit(directive: Directive) {
|
||||
if(directive.directive=="%target") {
|
||||
val compatibleTarget = directive.args.single().name
|
||||
if (compatibleTarget != CompilationTarget.instance.name)
|
||||
errors.err("module's compilation target ($compatibleTarget) differs from active target (${CompilationTarget.instance.name})", directive.position)
|
||||
}
|
||||
return super.visit(functionCall)
|
||||
|
||||
super.visit(directive)
|
||||
}
|
||||
|
||||
override fun visit(decl: VarDecl): Statement {
|
||||
// first, check if there are datatype errors on the vardecl
|
||||
override fun visit(decl: VarDecl) {
|
||||
decl.datatypeErrors.forEach { errors.err(it.message, it.position) }
|
||||
|
||||
// now check the identifier
|
||||
if(decl.name in BuiltinFunctions)
|
||||
// the builtin functions can't be redefined
|
||||
errors.err("builtin function cannot be redefined", decl.position)
|
||||
|
||||
if(decl.name in CompilationTarget.machine.opcodeNames)
|
||||
if(decl.name in CompilationTarget.instance.machine.opcodeNames)
|
||||
errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", 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)
|
||||
if (decl.structHasBeenFlattened)
|
||||
return super.visit(decl) // don't do this multiple times
|
||||
|
||||
if(decl.struct==null) {
|
||||
if (decl.struct == null) {
|
||||
errors.err("undefined struct type", decl.position)
|
||||
return super.visit(decl)
|
||||
}
|
||||
|
||||
if(decl.struct!!.statements.any { (it as VarDecl).datatype !in NumericDatatypes})
|
||||
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) {
|
||||
if (decl.value is NumericLiteralValue) {
|
||||
errors.err("you cannot initialize a struct using a single value", decl.position)
|
||||
return super.visit(decl)
|
||||
}
|
||||
|
||||
if(decl.value != null && decl.value !is StructLiteralValue) {
|
||||
errors.err("initializing requires struct literal value", decl.value?.position ?: decl.position)
|
||||
if (decl.value != null && decl.value !is ArrayLiteralValue) {
|
||||
errors.err("initializing a struct requires array literal value", decl.value?.position ?: decl.position)
|
||||
return super.visit(decl)
|
||||
}
|
||||
|
||||
val 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)
|
||||
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): Statement {
|
||||
if(subroutine.name in CompilationTarget.machine.opcodeNames) {
|
||||
override fun visit(subroutine: Subroutine) {
|
||||
if(subroutine.name in CompilationTarget.instance.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
|
||||
@ -116,14 +103,6 @@ internal class AstIdentifiersChecker(private val program: Program,
|
||||
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()
|
||||
@ -138,31 +117,16 @@ internal class AstIdentifiersChecker(private val program: Program,
|
||||
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 = ParameterVarDecl(it.name, it.type, subroutine.position)
|
||||
vardecl.linkParents(subroutine)
|
||||
subroutine.statements.add(0, vardecl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) {
|
||||
errors.err("asmsub can only contain inline assembly (%asm)", subroutine.position)
|
||||
}
|
||||
}
|
||||
return super.visit(subroutine)
|
||||
|
||||
super.visit(subroutine)
|
||||
}
|
||||
|
||||
override fun visit(label: Label): Statement {
|
||||
if(label.name in CompilationTarget.machine.opcodeNames)
|
||||
override fun visit(label: Label) {
|
||||
if(label.name in CompilationTarget.instance.machine.opcodeNames)
|
||||
errors.err("can't use a cpu opcode name as a symbol: '${label.name}'", label.position)
|
||||
|
||||
if(label.name in BuiltinFunctions) {
|
||||
@ -179,163 +143,24 @@ internal class AstIdentifiersChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.visit(label)
|
||||
|
||||
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.loopRegister == Register.X)
|
||||
errors.warn("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 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(string: StringLiteralValue) {
|
||||
if (string.value.length > 255)
|
||||
errors.err("string literal length max is 255", string.position)
|
||||
|
||||
super.visit(string)
|
||||
}
|
||||
|
||||
override fun visit(assignTarget: AssignTarget): AssignTarget {
|
||||
if(assignTarget.register== Register.X)
|
||||
errors.warn("writing to the X register is dangerous, because it's used as an internal pointer", assignTarget.position)
|
||||
return super.visit(assignTarget)
|
||||
}
|
||||
|
||||
override fun visit(arrayLiteral: ArrayLiteralValue): Expression {
|
||||
val array = super.visit(arrayLiteral)
|
||||
if(array is ArrayLiteralValue) {
|
||||
val vardecl = array.parent as? VarDecl
|
||||
// adjust the datatype of the array (to an educated guess)
|
||||
if(vardecl!=null) {
|
||||
val arrayDt = array.type
|
||||
if(!arrayDt.istype(vardecl.datatype)) {
|
||||
val cast = array.cast(vardecl.datatype)
|
||||
if (cast != null) {
|
||||
vardecl.value = cast
|
||||
cast.linkParents(vardecl)
|
||||
return cast
|
||||
}
|
||||
}
|
||||
return array
|
||||
}
|
||||
else {
|
||||
val arrayDt = array.guessDatatype(program)
|
||||
if(arrayDt.isKnown) {
|
||||
// this array literal is part of an expression, turn it into an identifier reference
|
||||
val litval2 = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
|
||||
return if (litval2 != null) {
|
||||
litval2.parent = array.parent
|
||||
makeIdentifierFromRefLv(litval2)
|
||||
} else array
|
||||
}
|
||||
}
|
||||
}
|
||||
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)
|
||||
errors.err("string literal length must be between 1 and 255", string.position)
|
||||
return if (vardecl != null)
|
||||
string
|
||||
else
|
||||
makeIdentifierFromRefLv(string) // replace the literal string by a identifier reference.
|
||||
}
|
||||
return string
|
||||
}
|
||||
|
||||
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.
|
||||
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.
|
||||
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 {
|
||||
override fun visit(structDecl: StructDecl) {
|
||||
for(member in structDecl.statements){
|
||||
val decl = member as? VarDecl
|
||||
if(decl!=null && decl.datatype !in NumericDatatypes)
|
||||
errors.err("structs can only contain numerical types", decl.position)
|
||||
}
|
||||
|
||||
return super.visit(structDecl)
|
||||
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
|
||||
return StringLiteralValue(string.value.repeat(constvalue.number.toInt()), string.altEncoding, expr.position)
|
||||
}
|
||||
}
|
||||
if(expr.operator == "+" && operand is StringLiteralValue) {
|
||||
// concatenate two strings
|
||||
return StringLiteralValue("${string.value}${operand.value}", string.altEncoding, expr.position)
|
||||
}
|
||||
return expr
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,118 +0,0 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.FunctionCall
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
import prog8.ast.statements.Subroutine
|
||||
|
||||
|
||||
internal class AstRecursionChecker(private val namespace: INameScope,
|
||||
private val errors: ErrorReporter) : IAstVisitor {
|
||||
private val callGraph = DirectedGraph<INameScope>()
|
||||
|
||||
fun processMessages(modulename: String) {
|
||||
val cycle = callGraph.checkForCycle()
|
||||
if(cycle.isEmpty())
|
||||
return
|
||||
val chain = cycle.joinToString(" <-- ") { "${it.name} at ${it.position}" }
|
||||
errors.err("Program contains recursive subroutine calls, this is not supported. Recursive chain:\n (a subroutine call in) $chain", Position.DUMMY)
|
||||
}
|
||||
|
||||
override fun visit(functionCallStatement: FunctionCallStatement) {
|
||||
val scope = functionCallStatement.definingScope()
|
||||
val targetStatement = functionCallStatement.target.targetStatement(namespace)
|
||||
if(targetStatement!=null) {
|
||||
val targetScope = when (targetStatement) {
|
||||
is Subroutine -> targetStatement
|
||||
else -> targetStatement.definingScope()
|
||||
}
|
||||
callGraph.add(scope, targetScope)
|
||||
}
|
||||
super.visit(functionCallStatement)
|
||||
}
|
||||
|
||||
override fun visit(functionCall: FunctionCall) {
|
||||
val scope = functionCall.definingScope()
|
||||
val targetStatement = functionCall.target.targetStatement(namespace)
|
||||
if(targetStatement!=null) {
|
||||
val targetScope = when (targetStatement) {
|
||||
is Subroutine -> targetStatement
|
||||
else -> targetStatement.definingScope()
|
||||
}
|
||||
callGraph.add(scope, targetScope)
|
||||
}
|
||||
super.visit(functionCall)
|
||||
}
|
||||
|
||||
private class DirectedGraph<VT> {
|
||||
private val graph = mutableMapOf<VT, MutableSet<VT>>()
|
||||
private var uniqueVertices = mutableSetOf<VT>()
|
||||
val numVertices : Int
|
||||
get() = uniqueVertices.size
|
||||
|
||||
fun add(from: VT, to: VT) {
|
||||
var targets = graph[from]
|
||||
if(targets==null) {
|
||||
targets = mutableSetOf()
|
||||
graph[from] = targets
|
||||
}
|
||||
targets.add(to)
|
||||
uniqueVertices.add(from)
|
||||
uniqueVertices.add(to)
|
||||
}
|
||||
|
||||
fun print() {
|
||||
println("#vertices: $numVertices")
|
||||
graph.forEach { (from, to) ->
|
||||
println("$from CALLS:")
|
||||
to.forEach { println(" $it") }
|
||||
}
|
||||
val cycle = checkForCycle()
|
||||
if(cycle.isNotEmpty()) {
|
||||
println("CYCLIC! $cycle")
|
||||
}
|
||||
}
|
||||
|
||||
fun checkForCycle(): MutableList<VT> {
|
||||
val visited = uniqueVertices.associateWith { false }.toMutableMap()
|
||||
val recStack = uniqueVertices.associateWith { false }.toMutableMap()
|
||||
val cycle = mutableListOf<VT>()
|
||||
for(node in uniqueVertices) {
|
||||
if(isCyclicUntil(node, visited, recStack, cycle))
|
||||
return cycle
|
||||
}
|
||||
return mutableListOf()
|
||||
}
|
||||
|
||||
private fun isCyclicUntil(node: VT,
|
||||
visited: MutableMap<VT, Boolean>,
|
||||
recStack: MutableMap<VT, Boolean>,
|
||||
cycleNodes: MutableList<VT>): Boolean {
|
||||
|
||||
if(recStack[node]==true) return true
|
||||
if(visited[node]==true) return false
|
||||
|
||||
// mark current node as visited and add to recursion stack
|
||||
visited[node] = true
|
||||
recStack[node] = true
|
||||
|
||||
// recurse for all neighbours
|
||||
val neighbors = graph[node]
|
||||
if(neighbors!=null) {
|
||||
for (neighbour in neighbors) {
|
||||
if (isCyclicUntil(neighbour, visited, recStack, cycleNodes)) {
|
||||
cycleNodes.add(node)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pop node from recursion stack
|
||||
recStack[node] = false
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
105
compiler/src/prog8/ast/processing/AstVariousTransforms.kt
Normal file
105
compiler/src/prog8/ast/processing/AstVariousTransforms.kt
Normal file
@ -0,0 +1,105 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
|
||||
|
||||
internal class AstVariousTransforms(private val program: Program) : AstWalker() {
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
// is it a struct variable? then define all its struct members as mangled names,
|
||||
// and include the original decl as well.
|
||||
if(decl.datatype==DataType.STRUCT && !decl.structHasBeenFlattened) {
|
||||
val decls = decl.flattenStructMembers()
|
||||
decls.add(decl)
|
||||
val result = AnonymousScope(decls, decl.position)
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
decl, result, parent
|
||||
))
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
// For non-kernel subroutines and non-asm parameters:
|
||||
// inject subroutine params as local variables (if they're not there yet).
|
||||
val symbolsInSub = subroutine.allDefinedSymbols()
|
||||
val namesInSub = symbolsInSub.map{ it.first }.toSet()
|
||||
if(subroutine.asmAddress==null) {
|
||||
if(subroutine.asmParameterRegisters.isEmpty() && subroutine.parameters.isNotEmpty()) {
|
||||
val vars = subroutine.statements.filterIsInstance<VarDecl>().map { it.name }.toSet()
|
||||
if(!vars.containsAll(subroutine.parameters.map{it.name})) {
|
||||
return subroutine.parameters
|
||||
.filter { it.name !in namesInSub }
|
||||
.map {
|
||||
val vardecl = ParameterVarDecl(it.name, it.type, subroutine.position)
|
||||
IAstModification.InsertFirst(vardecl, subroutine)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,6 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.*
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
@ -12,56 +9,60 @@ import prog8.ast.statements.*
|
||||
interface IAstModification {
|
||||
fun perform()
|
||||
|
||||
class Remove(val node: Node, val parent: Node) : IAstModification {
|
||||
class Remove(val node: Node, val parent: INameScope) : IAstModification {
|
||||
override fun perform() {
|
||||
if(parent is INameScope) {
|
||||
if (!parent.statements.remove(node))
|
||||
throw FatalAstException("attempt to remove non-existing node $node")
|
||||
} else {
|
||||
throw FatalAstException("parent of a remove modification is not an INameScope")
|
||||
}
|
||||
if (!parent.statements.remove(node) && parent !is GlobalNamespace)
|
||||
throw FatalAstException("attempt to remove non-existing node $node")
|
||||
}
|
||||
}
|
||||
|
||||
class SetExpression(val setter: (newExpr: Expression) -> Unit, val newExpr: Expression, val parent: Node) : IAstModification {
|
||||
class SetExpression(private val setter: (newExpr: Expression) -> Unit, private val newExpr: Expression, private val parent: Node) : IAstModification {
|
||||
override fun perform() {
|
||||
setter(newExpr)
|
||||
newExpr.linkParents(parent)
|
||||
}
|
||||
}
|
||||
|
||||
class InsertFirst(val stmt: Statement, val parent: Node) : IAstModification {
|
||||
class InsertFirst(private val stmt: Statement, private val parent: INameScope) : IAstModification {
|
||||
override fun perform() {
|
||||
if(parent is INameScope) {
|
||||
parent.statements.add(0, stmt)
|
||||
stmt.linkParents(parent)
|
||||
} else {
|
||||
throw FatalAstException("parent of an insert modification is not an INameScope")
|
||||
}
|
||||
parent.statements.add(0, stmt)
|
||||
stmt.linkParents(parent as Node)
|
||||
}
|
||||
}
|
||||
|
||||
class InsertAfter(val after: Statement, val stmt: Statement, val parent: Node) : IAstModification {
|
||||
class InsertLast(private val stmt: Statement, private val parent: INameScope) : IAstModification {
|
||||
override fun perform() {
|
||||
if(parent is INameScope) {
|
||||
val idx = parent.statements.indexOf(after)+1
|
||||
parent.statements.add(idx, stmt)
|
||||
stmt.linkParents(parent)
|
||||
} else {
|
||||
throw FatalAstException("parent of an insert modification is not an INameScope")
|
||||
}
|
||||
parent.statements.add(stmt)
|
||||
stmt.linkParents(parent as Node)
|
||||
}
|
||||
}
|
||||
|
||||
class ReplaceNode(val node: Node, val replacement: Node, val parent: Node) : IAstModification {
|
||||
class InsertAfter(private val after: Statement, private val stmt: Statement, private val parent: INameScope) : IAstModification {
|
||||
override fun perform() {
|
||||
val idx = parent.statements.indexOfFirst { it===after } + 1
|
||||
parent.statements.add(idx, stmt)
|
||||
stmt.linkParents(parent as Node)
|
||||
}
|
||||
}
|
||||
|
||||
class InsertBefore(private val before: Statement, private val stmt: Statement, private val parent: INameScope) : IAstModification {
|
||||
override fun perform() {
|
||||
val idx = parent.statements.indexOfFirst { it===before }
|
||||
parent.statements.add(idx, stmt)
|
||||
stmt.linkParents(parent as Node)
|
||||
}
|
||||
}
|
||||
|
||||
class ReplaceNode(private val node: Node, private val replacement: Node, private val parent: Node) : IAstModification {
|
||||
override fun perform() {
|
||||
parent.replaceChildNode(node, replacement)
|
||||
replacement.parent = parent
|
||||
replacement.linkParents(parent)
|
||||
}
|
||||
}
|
||||
|
||||
class SwapOperands(val expr: BinaryExpression): IAstModification {
|
||||
class SwapOperands(private val expr: BinaryExpression): IAstModification {
|
||||
override fun perform() {
|
||||
require(expr.operator in associativeOperators)
|
||||
val tmp = expr.left
|
||||
expr.left = expr.right
|
||||
expr.right = tmp
|
||||
@ -80,13 +81,12 @@ abstract class AstWalker {
|
||||
open fun before(branchStatement: BranchStatement, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(contStmt: Continue, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(directive: Directive, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(expr: PrefixExpression, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(forLoop: ForLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(foreverLoop: ForeverLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
@ -102,13 +102,11 @@ abstract class AstWalker {
|
||||
open fun before(postIncrDecr: PostIncrDecr, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(program: Program, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(range: RangeExpr, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(registerExpr: RegisterExpr, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(string: StringLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(structDecl: StructDecl, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(structLv: StructLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
@ -124,13 +122,12 @@ abstract class AstWalker {
|
||||
open fun after(branchStatement: BranchStatement, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(breakStmt: Break, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(contStmt: Continue, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(directive: Directive, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(foreverLoop: ForeverLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
@ -146,13 +143,11 @@ abstract class AstWalker {
|
||||
open fun after(postIncrDecr: PostIncrDecr, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(program: Program, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(range: RangeExpr, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(registerExpr: RegisterExpr, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(structDecl: StructDecl, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(structLv: StructLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
@ -214,6 +209,7 @@ abstract class AstWalker {
|
||||
track(before(decl, parent), decl, parent)
|
||||
decl.value?.accept(this, decl)
|
||||
decl.arraysize?.accept(this, decl)
|
||||
decl.struct?.accept(this, decl)
|
||||
track(after(decl, parent), decl, parent)
|
||||
}
|
||||
|
||||
@ -305,11 +301,6 @@ abstract class AstWalker {
|
||||
track(after(postIncrDecr, parent), postIncrDecr, parent)
|
||||
}
|
||||
|
||||
fun visit(contStmt: Continue, parent: Node) {
|
||||
track(before(contStmt, parent), contStmt, parent)
|
||||
track(after(contStmt, parent), contStmt, parent)
|
||||
}
|
||||
|
||||
fun visit(breakStmt: Break, parent: Node) {
|
||||
track(before(breakStmt, parent), breakStmt, parent)
|
||||
track(after(breakStmt, parent), breakStmt, parent)
|
||||
@ -317,7 +308,7 @@ abstract class AstWalker {
|
||||
|
||||
fun visit(forLoop: ForLoop, parent: Node) {
|
||||
track(before(forLoop, parent), forLoop, parent)
|
||||
forLoop.loopVar?.accept(this, forLoop)
|
||||
forLoop.loopVar.accept(this, forLoop)
|
||||
forLoop.iterable.accept(this, forLoop)
|
||||
forLoop.body.accept(this, forLoop)
|
||||
track(after(forLoop, parent), forLoop, parent)
|
||||
@ -330,19 +321,20 @@ abstract class AstWalker {
|
||||
track(after(whileLoop, parent), whileLoop, parent)
|
||||
}
|
||||
|
||||
fun visit(foreverLoop: ForeverLoop, parent: Node) {
|
||||
track(before(foreverLoop, parent), foreverLoop, parent)
|
||||
foreverLoop.body.accept(this, foreverLoop)
|
||||
track(after(foreverLoop, parent), foreverLoop, parent)
|
||||
}
|
||||
|
||||
fun visit(repeatLoop: RepeatLoop, parent: Node) {
|
||||
track(before(repeatLoop, parent), repeatLoop, parent)
|
||||
repeatLoop.untilCondition.accept(this, repeatLoop)
|
||||
repeatLoop.iterations?.accept(this, repeatLoop)
|
||||
repeatLoop.body.accept(this, repeatLoop)
|
||||
track(after(repeatLoop, parent), repeatLoop, parent)
|
||||
}
|
||||
|
||||
fun visit(untilLoop: UntilLoop, parent: Node) {
|
||||
track(before(untilLoop, parent), untilLoop, parent)
|
||||
untilLoop.condition.accept(this, untilLoop)
|
||||
untilLoop.body.accept(this, untilLoop)
|
||||
track(after(untilLoop, parent), untilLoop, parent)
|
||||
}
|
||||
|
||||
fun visit(returnStmt: Return, parent: Node) {
|
||||
track(before(returnStmt, parent), returnStmt, parent)
|
||||
returnStmt.value?.accept(this, returnStmt)
|
||||
@ -351,8 +343,8 @@ abstract class AstWalker {
|
||||
|
||||
fun visit(arrayIndexedExpression: ArrayIndexedExpression, parent: Node) {
|
||||
track(before(arrayIndexedExpression, parent), arrayIndexedExpression, parent)
|
||||
arrayIndexedExpression.identifier.accept(this, arrayIndexedExpression)
|
||||
arrayIndexedExpression.arrayspec.accept(this, arrayIndexedExpression)
|
||||
arrayIndexedExpression.arrayvar.accept(this, arrayIndexedExpression)
|
||||
arrayIndexedExpression.indexer.accept(this, arrayIndexedExpression)
|
||||
track(after(arrayIndexedExpression, parent), arrayIndexedExpression, parent)
|
||||
}
|
||||
|
||||
@ -399,11 +391,6 @@ abstract class AstWalker {
|
||||
track(after(inlineAssembly, parent), inlineAssembly, parent)
|
||||
}
|
||||
|
||||
fun visit(registerExpr: RegisterExpr, parent: Node) {
|
||||
track(before(registerExpr, parent), registerExpr, parent)
|
||||
track(after(registerExpr, parent), registerExpr, parent)
|
||||
}
|
||||
|
||||
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node) {
|
||||
track(before(builtinFunctionStatementPlaceholder, parent), builtinFunctionStatementPlaceholder, parent)
|
||||
track(after(builtinFunctionStatementPlaceholder, parent), builtinFunctionStatementPlaceholder, parent)
|
||||
@ -433,11 +420,5 @@ abstract class AstWalker {
|
||||
structDecl.statements.forEach { it.accept(this, structDecl) }
|
||||
track(after(structDecl, parent), structDecl, parent)
|
||||
}
|
||||
|
||||
fun visit(structLv: StructLiteralValue, parent: Node) {
|
||||
track(before(structLv, parent), structLv, parent)
|
||||
structLv.values.forEach { it.accept(this, structLv) }
|
||||
track(after(structLv, parent), structLv, parent)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,28 +0,0 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.statements.ForeverLoop
|
||||
import prog8.ast.statements.RepeatLoop
|
||||
import prog8.ast.statements.WhileLoop
|
||||
|
||||
|
||||
internal class ForeverLoopsMaker: AstWalker() {
|
||||
override fun before(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> {
|
||||
val numeric = repeatLoop.untilCondition as? NumericLiteralValue
|
||||
if(numeric!=null && numeric.number.toInt() == 0) {
|
||||
val forever = ForeverLoop(repeatLoop.body, repeatLoop.position)
|
||||
return listOf(IAstModification.ReplaceNode(repeatLoop, forever, parent))
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun before(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
|
||||
val numeric = whileLoop.condition as? NumericLiteralValue
|
||||
if(numeric!=null && numeric.number.toInt() != 0) {
|
||||
val forever = ForeverLoop(whileLoop.body, whileLoop.position)
|
||||
return listOf(IAstModification.ReplaceNode(whileLoop, forever, parent))
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
}
|
@ -1,267 +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 { it.accept(this) }
|
||||
}
|
||||
|
||||
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.args = functionCall.args.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.args = functionCallStatement.args.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 {
|
||||
when(val newloopvar = forLoop.loopVar?.accept(this)) {
|
||||
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(foreverLoop: ForeverLoop): Statement {
|
||||
foreverLoop.body = foreverLoop.body.accept(this) as AnonymousScope
|
||||
return foreverLoop
|
||||
}
|
||||
|
||||
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 {
|
||||
when (val ident = assignTarget.identifier?.accept(this)) {
|
||||
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
|
||||
}
|
||||
}
|
@ -33,6 +33,7 @@ interface IAstVisitor {
|
||||
fun visit(decl: VarDecl) {
|
||||
decl.value?.accept(this)
|
||||
decl.arraysize?.accept(this)
|
||||
decl.struct?.accept(this)
|
||||
}
|
||||
|
||||
fun visit(subroutine: Subroutine) {
|
||||
@ -95,14 +96,11 @@ interface IAstVisitor {
|
||||
postIncrDecr.target.accept(this)
|
||||
}
|
||||
|
||||
fun visit(contStmt: Continue) {
|
||||
}
|
||||
|
||||
fun visit(breakStmt: Break) {
|
||||
}
|
||||
|
||||
fun visit(forLoop: ForLoop) {
|
||||
forLoop.loopVar?.accept(this)
|
||||
forLoop.loopVar.accept(this)
|
||||
forLoop.iterable.accept(this)
|
||||
forLoop.body.accept(this)
|
||||
}
|
||||
@ -112,13 +110,14 @@ interface IAstVisitor {
|
||||
whileLoop.body.accept(this)
|
||||
}
|
||||
|
||||
fun visit(foreverLoop: ForeverLoop) {
|
||||
foreverLoop.body.accept(this)
|
||||
fun visit(repeatLoop: RepeatLoop) {
|
||||
repeatLoop.iterations?.accept(this)
|
||||
repeatLoop.body.accept(this)
|
||||
}
|
||||
|
||||
fun visit(repeatLoop: RepeatLoop) {
|
||||
repeatLoop.untilCondition.accept(this)
|
||||
repeatLoop.body.accept(this)
|
||||
fun visit(untilLoop: UntilLoop) {
|
||||
untilLoop.condition.accept(this)
|
||||
untilLoop.body.accept(this)
|
||||
}
|
||||
|
||||
fun visit(returnStmt: Return) {
|
||||
@ -126,8 +125,8 @@ interface IAstVisitor {
|
||||
}
|
||||
|
||||
fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
|
||||
arrayIndexedExpression.identifier.accept(this)
|
||||
arrayIndexedExpression.arrayspec.accept(this)
|
||||
arrayIndexedExpression.arrayvar.accept(this)
|
||||
arrayIndexedExpression.indexer.accept(this)
|
||||
}
|
||||
|
||||
fun visit(assignTarget: AssignTarget) {
|
||||
@ -159,9 +158,6 @@ interface IAstVisitor {
|
||||
fun visit(inlineAssembly: InlineAssembly) {
|
||||
}
|
||||
|
||||
fun visit(registerExpr: RegisterExpr) {
|
||||
}
|
||||
|
||||
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder) {
|
||||
}
|
||||
|
||||
@ -181,8 +177,4 @@ interface IAstVisitor {
|
||||
fun visit(structDecl: StructDecl) {
|
||||
structDecl.statements.forEach { it.accept(this) }
|
||||
}
|
||||
|
||||
fun visit(structLv: StructLiteralValue) {
|
||||
structLv.values.forEach { it.accept(this) }
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.statements.Directive
|
||||
|
||||
@ -10,11 +11,12 @@ internal class ImportedModuleDirectiveRemover: AstWalker() {
|
||||
*/
|
||||
|
||||
private val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address")
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun before(directive: Directive, parent: Node): Iterable<IAstModification> {
|
||||
if(directive.directive in moduleLevelDirectives) {
|
||||
return listOf(IAstModification.Remove(directive, parent))
|
||||
return listOf(IAstModification.Remove(directive, parent as INameScope))
|
||||
}
|
||||
return emptyList()
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
53
compiler/src/prog8/ast/processing/LiteralsToAutoVars.kt
Normal file
53
compiler/src/prog8/ast/processing/LiteralsToAutoVars.kt
Normal file
@ -0,0 +1,53 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
|
||||
|
||||
internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
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 a new local vardecl
|
||||
val vardecl = VarDecl.createAuto(string)
|
||||
val identifier = IdentifierReference(listOf(vardecl.name), vardecl.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(string, identifier, parent),
|
||||
IAstModification.InsertFirst(vardecl, string.definingScope())
|
||||
)
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||
val vardecl = array.parent as? VarDecl
|
||||
if(vardecl!=null) {
|
||||
// adjust the datatype of the array (to an educated guess)
|
||||
val arrayDt = array.type
|
||||
if(!arrayDt.istype(vardecl.datatype)) {
|
||||
val cast = array.cast(vardecl.datatype)
|
||||
if (cast != null && cast !== array)
|
||||
return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl))
|
||||
}
|
||||
} else {
|
||||
val arrayDt = array.guessDatatype(program)
|
||||
if(arrayDt.isKnown) {
|
||||
// this array literal is part of an expression, turn it into an identifier reference
|
||||
val litval2 = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
|
||||
if(litval2!=null) {
|
||||
val vardecl2 = VarDecl.createAuto(litval2)
|
||||
val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(array, identifier, parent),
|
||||
IAstModification.InsertFirst(vardecl2, array.definingScope())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
}
|
@ -1,219 +1,309 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.base.NumericDatatypes
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
|
||||
|
||||
internal class StatementReorderer(private val program: Program): IAstModifyingVisitor {
|
||||
internal class StatementReorderer(val program: Program, val errors: ErrorReporter) : AstWalker() {
|
||||
// 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.
|
||||
// - 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.
|
||||
// - (syntax desugaring) struct value assignment is expanded into several struct member assignments.
|
||||
// - in-place assignments are reordered a bit so that they are mostly of the form A = A <operator> <rest>
|
||||
// - sorts the choices in when statement.
|
||||
// - a vardecl with a non-const initializer value is split into a regular vardecl and an assignment statement.
|
||||
// - insert AddressOf (&) expression where required (string params to a UWORD function param etc).
|
||||
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option")
|
||||
|
||||
private val addVardecls = mutableMapOf<INameScope, MutableList<VarDecl>>()
|
||||
|
||||
override fun visit(module: Module) {
|
||||
addVardecls.clear()
|
||||
super.visit(module)
|
||||
|
||||
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()
|
||||
|
||||
// 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)
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
for((where, decls) in addVardecls) {
|
||||
where.statements.addAll(0, decls)
|
||||
decls.forEach { it.linkParents(where as Node) }
|
||||
}
|
||||
reorderVardeclsAndDirectives(module.statements)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun visit(block: Block): Statement {
|
||||
private fun reorderVardeclsAndDirectives(statements: MutableList<Statement>) {
|
||||
val varDecls = statements.filterIsInstance<VarDecl>()
|
||||
statements.removeAll(varDecls)
|
||||
statements.addAll(0, varDecls)
|
||||
|
||||
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++
|
||||
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)
|
||||
)
|
||||
}
|
||||
// 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--
|
||||
|
||||
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 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)
|
||||
|
||||
return super.visit(block)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
return subroutine
|
||||
}
|
||||
|
||||
|
||||
private fun addVarDecl(scope: INameScope, variable: VarDecl): VarDecl {
|
||||
if(scope !in addVardecls)
|
||||
addVardecls[scope] = mutableListOf()
|
||||
val declList = addVardecls.getValue(scope)
|
||||
val existing = declList.singleOrNull { it.name==variable.name }
|
||||
return if(existing!=null) {
|
||||
existing
|
||||
} else {
|
||||
declList.add(variable)
|
||||
variable
|
||||
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||
when (val expr2 = arrayIndexedExpression.indexer.origExpression) {
|
||||
is NumericLiteralValue -> {
|
||||
arrayIndexedExpression.indexer.indexNum = expr2
|
||||
arrayIndexedExpression.indexer.origExpression = null
|
||||
return noModifications
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
arrayIndexedExpression.indexer.indexVar = expr2
|
||||
arrayIndexedExpression.indexer.origExpression = null
|
||||
return noModifications
|
||||
}
|
||||
is Expression -> {
|
||||
// replace complex indexing with a temp variable
|
||||
return getAutoIndexerVarFor(arrayIndexedExpression)
|
||||
}
|
||||
else -> return noModifications
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(decl: VarDecl): Statement {
|
||||
private fun getAutoIndexerVarFor(expr: ArrayIndexedExpression): MutableList<IAstModification> {
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
val subroutine = expr.definingSubroutine()!!
|
||||
val statement = expr.containingStatement()
|
||||
val indexerVarPrefix = "prog8_autovar_index_"
|
||||
val repo = subroutine.asmGenInfo.usedAutoArrayIndexerForStatements
|
||||
|
||||
// TODO make this even smarter so that an indexerVar can be reused for a different following statement... requires updating the partOfStatement?
|
||||
var indexerVar = repo.firstOrNull { it.replaces isSameAs expr.indexer }
|
||||
if(indexerVar==null) {
|
||||
// add another loop index var to be used for this expression
|
||||
val indexerVarName = "$indexerVarPrefix${expr.indexer.hashCode()}"
|
||||
indexerVar = AsmGenInfo.ArrayIndexerInfo(indexerVarName, expr.indexer, statement)
|
||||
repo.add(indexerVar)
|
||||
// create the indexer var at block level scope
|
||||
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.PREFER_ZEROPAGE,
|
||||
null, indexerVarName, null, null, isArray = false, autogeneratedDontRemove = true, position = expr.position)
|
||||
modifications.add(IAstModification.InsertFirst(vardecl, subroutine))
|
||||
}
|
||||
indexerVar.used++ // keep track of how many times it it used, to avoid assigning it multiple times
|
||||
|
||||
// replace the indexer with just the variable
|
||||
// assign the indexing expression to the helper variable, but only if that hasn't been done already
|
||||
val indexerExpression = expr.indexer.origExpression!!
|
||||
val target = AssignTarget(IdentifierReference(listOf(indexerVar.name), indexerExpression.position), null, null, indexerExpression.position)
|
||||
if(indexerVar.used==1) {
|
||||
val assign = Assignment(target, indexerExpression, indexerExpression.position)
|
||||
modifications.add(IAstModification.InsertBefore(statement, assign, statement.definingScope()))
|
||||
}
|
||||
modifications.add(IAstModification.SetExpression( {
|
||||
expr.indexer.indexVar = it as IdentifierReference
|
||||
expr.indexer.indexNum = null
|
||||
expr.indexer.origExpression = null
|
||||
}, target.identifier!!.copy(), expr.indexer))
|
||||
|
||||
return modifications
|
||||
}
|
||||
|
||||
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 after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
val declValue = decl.value
|
||||
if(declValue!=null && decl.type== VarDeclType.VAR && decl.datatype in NumericDatatypes) {
|
||||
val declConstValue = declValue.constValue(program)
|
||||
if(declConstValue==null) {
|
||||
// move the vardecl (without value) to the scope and replace this with a regular assignment
|
||||
val target = AssignTarget(null, IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
|
||||
val assign = Assignment(target, null, declValue, decl.position)
|
||||
assign.linkParents(decl.parent)
|
||||
decl.value = null
|
||||
addVarDecl(decl.definingScope(), decl)
|
||||
return assign
|
||||
// Unless we're dealing with a floating point variable because that will actually make things less efficient at the moment (because floats are mostly calcualated via the stack)
|
||||
if(decl.datatype!=DataType.FLOAT) {
|
||||
decl.value = null
|
||||
decl.allowInitializeWithZero = false
|
||||
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
|
||||
val assign = Assignment(target, declValue, decl.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(decl, assign, parent),
|
||||
IAstModification.InsertFirst(decl, decl.definingScope())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.visit(decl)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun visit(assignment: Assignment): Statement {
|
||||
val assg = super.visit(assignment)
|
||||
if(assg !is Assignment)
|
||||
return assg
|
||||
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
val valueType = assignment.value.inferType(program)
|
||||
val targetType = assignment.target.inferType(program)
|
||||
var assignments = emptyList<Assignment>()
|
||||
|
||||
// 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) {
|
||||
val assignments = if (assg.value is StructLiteralValue) {
|
||||
flattenStructAssignmentFromStructLiteral(assg, program) // 'structvar = { ..... } '
|
||||
} else {
|
||||
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(targetType.istype(DataType.STRUCT) && (valueType.istype(DataType.STRUCT) || valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes )) {
|
||||
assignments = if (assignment.value is ArrayLiteralValue) {
|
||||
flattenStructAssignmentFromStructLiteral(assignment) // 'structvar = [ ..... ] '
|
||||
} else {
|
||||
flattenStructAssignmentFromIdentifier(assignment) // 'structvar1 = structvar2'
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
if(targetType.typeOrElse(DataType.STRUCT) in ArrayDatatypes && valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes ) {
|
||||
assignments = if (assignment.value is ArrayLiteralValue) {
|
||||
flattenArrayAssignmentFromArrayLiteral(assignment) // 'arrayvar = [ ..... ] '
|
||||
} else {
|
||||
flattenArrayAssignmentFromIdentifier(assignment) // 'arrayvar1 = arrayvar2'
|
||||
}
|
||||
}
|
||||
|
||||
if(assignments.isNotEmpty()) {
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
val scope = assignment.definingScope()
|
||||
assignments.reversed().mapTo(modifications) { IAstModification.InsertAfter(assignment, it, scope) }
|
||||
modifications.add(IAstModification.Remove(assignment, scope))
|
||||
return modifications
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
// rewrite in-place assignment expressions a bit so that the assignment target usually is the leftmost operand
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
if(binExpr!=null) {
|
||||
if(binExpr.left isSameAs assignment.target) {
|
||||
// A = A <operator> 5, unchanged
|
||||
return noModifications
|
||||
}
|
||||
|
||||
if(binExpr.operator in associativeOperators) {
|
||||
if (binExpr.right isSameAs assignment.target) {
|
||||
// A = v <associative-operator> A ==> A = A <associative-operator> v
|
||||
return listOf(IAstModification.SwapOperands(binExpr))
|
||||
}
|
||||
|
||||
val leftBinExpr = binExpr.left as? BinaryExpression
|
||||
if(leftBinExpr?.operator == binExpr.operator) {
|
||||
return if(leftBinExpr.left isSameAs assignment.target) {
|
||||
// A = (A <associative-operator> x) <same-operator> y ==> A = A <associative-operator> (x <same-operator> y)
|
||||
val newRight = BinaryExpression(leftBinExpr.right, binExpr.operator, binExpr.right, binExpr.position)
|
||||
val newValue = BinaryExpression(leftBinExpr.left, binExpr.operator, newRight, binExpr.position)
|
||||
listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment))
|
||||
} else {
|
||||
// A = (x <associative-operator> A) <same-operator> y ==> A = A <associative-operator> (x <same-operator> y)
|
||||
val newRight = BinaryExpression(leftBinExpr.left, binExpr.operator, binExpr.right, binExpr.position)
|
||||
val newValue = BinaryExpression(leftBinExpr.right, binExpr.operator, newRight, binExpr.position)
|
||||
listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment))
|
||||
}
|
||||
|
||||
val 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)
|
||||
}
|
||||
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 assg
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment, program: Program): List<Assignment> {
|
||||
private fun flattenArrayAssignmentFromArrayLiteral(assign: Assignment): List<Assignment> {
|
||||
val identifier = assign.target.identifier!!
|
||||
val targetVar = identifier.targetVarDecl(program.namespace)!!
|
||||
val alv = assign.value as? ArrayLiteralValue
|
||||
return flattenArrayAssign(targetVar, alv, identifier, assign.position)
|
||||
}
|
||||
|
||||
private fun flattenArrayAssignmentFromIdentifier(assign: Assignment): List<Assignment> {
|
||||
val identifier = assign.target.identifier!!
|
||||
val targetVar = identifier.targetVarDecl(program.namespace)!!
|
||||
val sourceIdent = assign.value as IdentifierReference
|
||||
val sourceVar = sourceIdent.targetVarDecl(program.namespace)!!
|
||||
if(!sourceVar.isArray) {
|
||||
errors.err("value must be an array", sourceIdent.position)
|
||||
return emptyList()
|
||||
}
|
||||
val alv = sourceVar.value as? ArrayLiteralValue
|
||||
return flattenArrayAssign(targetVar, alv, identifier, assign.position)
|
||||
}
|
||||
|
||||
private fun flattenArrayAssign(targetVar: VarDecl, alv: ArrayLiteralValue?, identifier: IdentifierReference, position: Position): List<Assignment> {
|
||||
if(targetVar.arraysize==null) {
|
||||
errors.err("array has no defined size", identifier.position)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
if(alv==null || alv.value.size != targetVar.arraysize!!.constIndex()) {
|
||||
errors.err("element count mismatch", position)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
// TODO use a pointer loop instead of individual assignments
|
||||
return alv.value.mapIndexed { index, value ->
|
||||
val idx = ArrayIndexedExpression(identifier, ArrayIndex(NumericLiteralValue(DataType.UBYTE, index, position), position), position)
|
||||
Assignment(AssignTarget(null, idx, null, position), value, value.position)
|
||||
}
|
||||
}
|
||||
|
||||
private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment): List<Assignment> {
|
||||
val identifier = structAssignment.target.identifier!!
|
||||
val identifierName = identifier.nameInSource.single()
|
||||
val targetVar = identifier.targetVarDecl(program.namespace)!!
|
||||
val struct = targetVar.struct!!
|
||||
|
||||
val slv = structAssignment.value as? StructLiteralValue
|
||||
if(slv==null || slv.values.size != struct.numberOfElements)
|
||||
throw FatalAstException("element count mismatch")
|
||||
val slv = structAssignment.value as? ArrayLiteralValue
|
||||
if(slv==null || slv.value.size != struct.numberOfElements) {
|
||||
errors.err("element count mismatch", structAssignment.position)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
return struct.statements.zip(slv.values).map { (targetDecl, sourceValue) ->
|
||||
return struct.statements.zip(slv.value).map { (targetDecl, sourceValue) ->
|
||||
targetDecl as VarDecl
|
||||
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
|
||||
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
|
||||
val assign = Assignment(AssignTarget(null, idref, null, null, structAssignment.position),
|
||||
null, sourceValue, sourceValue.position)
|
||||
val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position),
|
||||
sourceValue, sourceValue.position)
|
||||
assign.linkParents(structAssignment)
|
||||
assign
|
||||
}
|
||||
}
|
||||
|
||||
private fun flattenStructAssignmentFromIdentifier(structAssignment: Assignment, program: Program): List<Assignment> {
|
||||
private fun flattenStructAssignmentFromIdentifier(structAssignment: Assignment): List<Assignment> {
|
||||
// TODO use memcopy beyond a certain number of elements
|
||||
val identifier = structAssignment.target.identifier!!
|
||||
val identifierName = identifier.nameInSource.single()
|
||||
val targetVar = identifier.targetVarDecl(program.namespace)!!
|
||||
@ -221,30 +311,50 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
|
||||
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
|
||||
when {
|
||||
sourceVar.struct!=null -> {
|
||||
// struct memberwise copy
|
||||
val sourceStruct = sourceVar.struct!!
|
||||
if(sourceStruct!==targetVar.struct) {
|
||||
// structs are not the same in assignment
|
||||
return listOf() // error will be printed elsewhere
|
||||
}
|
||||
if(struct.statements.size!=sourceStruct.statements.size)
|
||||
return listOf() // error will be printed elsewhere
|
||||
return struct.statements.zip(sourceStruct.statements).map { member ->
|
||||
val targetDecl = member.first as VarDecl
|
||||
val sourceDecl = member.second as VarDecl
|
||||
if(targetDecl.name != sourceDecl.name)
|
||||
throw FatalAstException("struct member mismatch")
|
||||
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
|
||||
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
|
||||
val sourcemangled = mangledStructMemberName(sourceVar.name, sourceDecl.name)
|
||||
val sourceIdref = IdentifierReference(listOf(sourcemangled), structAssignment.position)
|
||||
val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position), sourceIdref, member.second.position)
|
||||
assign.linkParents(structAssignment)
|
||||
assign
|
||||
}
|
||||
}
|
||||
sourceVar.isArray -> {
|
||||
val array = (sourceVar.value as ArrayLiteralValue).value
|
||||
if(struct.statements.size!=array.size)
|
||||
return listOf() // error will be printed elsewhere
|
||||
return struct.statements.zip(array).map {
|
||||
val decl = it.first as VarDecl
|
||||
val mangled = mangledStructMemberName(identifierName, decl.name)
|
||||
val targetName = IdentifierReference(listOf(mangled), structAssignment.position)
|
||||
val target = AssignTarget(targetName, null, null, structAssignment.position)
|
||||
val assign = Assignment(target, it.second, structAssignment.position)
|
||||
assign.linkParents(structAssignment)
|
||||
assign
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
throw FatalAstException("can only assign arrays or structs to structs")
|
||||
}
|
||||
}
|
||||
}
|
||||
is StructLiteralValue -> {
|
||||
is ArrayLiteralValue -> {
|
||||
throw IllegalArgumentException("not going to flatten a structLv assignment here")
|
||||
}
|
||||
else -> throw FatalAstException("strange struct value")
|
||||
|
@ -4,9 +4,7 @@ import prog8.ast.IFunctionCall
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.functions.BuiltinFunctions
|
||||
@ -18,6 +16,28 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
* (this includes function call arguments)
|
||||
*/
|
||||
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
val declValue = decl.value
|
||||
if(decl.type==VarDeclType.VAR && declValue!=null && decl.struct==null) {
|
||||
val valueDt = declValue.inferType(program)
|
||||
if(!valueDt.istype(decl.datatype)) {
|
||||
|
||||
// don't add a typecast on an array initializer value
|
||||
if(valueDt.typeOrElse(DataType.STRUCT) in IntegerDatatypes && decl.datatype in ArrayDatatypes)
|
||||
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)
|
||||
@ -34,24 +54,55 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
}
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
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, assignment)
|
||||
val targetItype = assignment.target.inferType(program)
|
||||
if(targetItype.isKnown && valueItype.isKnown) {
|
||||
val targettype = targetItype.typeOrElse(DataType.STRUCT)
|
||||
val valuetype = valueItype.typeOrElse(DataType.STRUCT)
|
||||
if (valuetype != targettype) {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
assignment.value,
|
||||
TypecastExpression(assignment.value, targettype, true, assignment.value.position),
|
||||
assignment))
|
||||
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 emptyList()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
@ -64,126 +115,95 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
|
||||
private fun afterFunctionCallArgs(call: IFunctionCall, scope: INameScope): Iterable<IAstModification> {
|
||||
// see if a typecast is needed to convert the arguments into the required parameter's type
|
||||
return when(val sub = call.target.targetStatement(scope)) {
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
|
||||
when(val sub = call.target.targetStatement(scope)) {
|
||||
is Subroutine -> {
|
||||
for(arg in sub.parameters.zip(call.args.withIndex())) {
|
||||
val argItype = arg.second.value.inferType(program)
|
||||
sub.parameters.zip(call.args).forEachIndexed { index, pair ->
|
||||
val argItype = pair.second.inferType(program)
|
||||
if(argItype.isKnown) {
|
||||
val argtype = argItype.typeOrElse(DataType.STRUCT)
|
||||
val requiredType = arg.first.type
|
||||
val requiredType = pair.first.type
|
||||
if (requiredType != argtype) {
|
||||
if (argtype isAssignableTo requiredType) {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
call.args[arg.second.index],
|
||||
TypecastExpression(arg.second.value, requiredType, true, arg.second.value.position),
|
||||
call as Node))
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
emptyList()
|
||||
}
|
||||
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.args.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) {
|
||||
func.parameters.zip(call.args).forEachIndexed { index, pair ->
|
||||
val argItype = pair.second.inferType(program)
|
||||
if (argItype.isKnown) {
|
||||
val argtype = argItype.typeOrElse(DataType.STRUCT)
|
||||
if (pair.first.possibleDatatypes.all { argtype != it }) {
|
||||
for (possibleType in pair.first.possibleDatatypes) {
|
||||
if (argtype isAssignableTo possibleType) {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
call.args[arg.second.index],
|
||||
TypecastExpression(arg.second.value, possibleType, true, arg.second.value.position),
|
||||
call as Node))
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[index],
|
||||
TypecastExpression(pair.second, possibleType, true, pair.second.position),
|
||||
call as Node)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
emptyList()
|
||||
}
|
||||
null -> emptyList()
|
||||
else -> throw FatalAstException("call to something weird $sub ${call.target}")
|
||||
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("byte or word value implicitly converted to float. Suggestion: use explicit cast as float, a float number, or revert to integer arithmetic", typecast.position)
|
||||
errors.warn("integer implicitly converted to float. Suggestion: use float literals, add an explicit cast, or revert to integer arithmetic", typecast.position)
|
||||
}
|
||||
return emptyList()
|
||||
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)
|
||||
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 emptyList()
|
||||
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)
|
||||
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 emptyList()
|
||||
}
|
||||
|
||||
override fun after(structLv: StructLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||
// assignment of a struct literal value, some member values may need proper typecast
|
||||
|
||||
fun addTypecastsIfNeeded(struct: StructDecl): Iterable<IAstModification> {
|
||||
val newValues = struct.statements.zip(structLv.values).map { (structMemberDecl, memberValue) ->
|
||||
val memberDt = (structMemberDecl as VarDecl).datatype
|
||||
val valueDt = memberValue.inferType(program)
|
||||
if (valueDt.typeOrElse(memberDt) != memberDt)
|
||||
TypecastExpression(memberValue, memberDt, true, memberValue.position)
|
||||
else
|
||||
memberValue
|
||||
}
|
||||
|
||||
class StructLvValueReplacer(val targetStructLv: StructLiteralValue, val typecastValues: List<Expression>) : IAstModification {
|
||||
override fun perform() {
|
||||
targetStructLv.values = typecastValues
|
||||
typecastValues.forEach { it.linkParents(targetStructLv) }
|
||||
}
|
||||
}
|
||||
|
||||
return if(structLv.values.zip(newValues).any { (v1, v2) -> v1 !== v2})
|
||||
listOf(StructLvValueReplacer(structLv, newValues))
|
||||
else
|
||||
emptyList()
|
||||
}
|
||||
|
||||
val decl = structLv.parent as? VarDecl
|
||||
if(decl != null) {
|
||||
val struct = decl.struct
|
||||
if(struct != null)
|
||||
return addTypecastsIfNeeded(struct)
|
||||
} else {
|
||||
val assign = structLv.parent as? Assignment
|
||||
if (assign != null) {
|
||||
val decl2 = assign.target.identifier?.targetVarDecl(program.namespace)
|
||||
if(decl2 != null) {
|
||||
val struct = decl2.struct
|
||||
if(struct != null)
|
||||
return addTypecastsIfNeeded(struct)
|
||||
}
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
|
||||
@ -194,9 +214,11 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
if(subroutine.returntypes.size==1) {
|
||||
val subReturnType = subroutine.returntypes.first()
|
||||
if (returnValue.inferType(program).istype(subReturnType))
|
||||
return emptyList()
|
||||
return noModifications
|
||||
if (returnValue is NumericLiteralValue) {
|
||||
returnStmt.value = returnValue.cast(subroutine.returntypes.single())
|
||||
val cast = returnValue.cast(subroutine.returntypes.single())
|
||||
if(cast.isValid)
|
||||
returnStmt.value = cast.valueOrZero()
|
||||
} else {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
returnValue,
|
||||
@ -205,6 +227,6 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
}
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
44
compiler/src/prog8/ast/processing/VariousCleanups.kt
Normal file
44
compiler/src/prog8/ast/processing/VariousCleanups.kt
Normal file
@ -0,0 +1,44 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.TypecastExpression
|
||||
import prog8.ast.statements.AnonymousScope
|
||||
import prog8.ast.statements.NopStatement
|
||||
|
||||
|
||||
internal class VariousCleanups: AstWalker() {
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
|
||||
return listOf(IAstModification.Remove(nopStatement, parent 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)
|
||||
into.statements.remove(scope)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(typecast.expression is NumericLiteralValue) {
|
||||
val value = (typecast.expression as NumericLiteralValue).cast(typecast.type)
|
||||
if(value.isValid)
|
||||
return listOf(IAstModification.ReplaceNode(typecast, value.valueOrZero(), parent))
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
}
|
87
compiler/src/prog8/ast/processing/VerifyFunctionArgTypes.kt
Normal file
87
compiler/src/prog8/ast/processing/VerifyFunctionArgTypes.kt
Normal file
@ -0,0 +1,87 @@
|
||||
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.expressions.Expression
|
||||
import prog8.ast.expressions.FunctionCall
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.CompilerException
|
||||
import prog8.functions.BuiltinFunctions
|
||||
|
||||
class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
||||
|
||||
override fun visit(functionCall: FunctionCall) {
|
||||
val error = checkTypes(functionCall as IFunctionCall, functionCall.definingScope(), program)
|
||||
if(error!=null)
|
||||
throw CompilerException(error)
|
||||
}
|
||||
|
||||
override fun visit(functionCallStatement: FunctionCallStatement) {
|
||||
val error = checkTypes(functionCallStatement as IFunctionCall, functionCallStatement.definingScope(), program)
|
||||
if (error!=null)
|
||||
throw CompilerException(error)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private fun argTypeCompatible(argDt: DataType, paramDt: DataType): Boolean {
|
||||
if(argDt==paramDt)
|
||||
return true
|
||||
|
||||
// there are some exceptions that are considered compatible, such as STR <> UWORD
|
||||
if(argDt==DataType.STR && paramDt==DataType.UWORD ||
|
||||
argDt==DataType.UWORD && paramDt==DataType.STR)
|
||||
return true
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
fun checkTypes(call: IFunctionCall, scope: INameScope, program: Program): String? {
|
||||
val argITypes = call.args.map { it.inferType(program) }
|
||||
if(argITypes.any { !it.isKnown })
|
||||
throw FatalAstException("unknown dt")
|
||||
val argtypes = argITypes.map { it.typeOrElse(DataType.STRUCT) }
|
||||
val target = call.target.targetStatement(scope)
|
||||
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 && parent !is Assignment && parent !is VarDecl) {
|
||||
return "can't use subroutine call that returns multiple return values here (try moving it into a separate assignment)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
217
compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt
Normal file
217
compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt
Normal file
@ -0,0 +1,217 @@
|
||||
package prog8.compiler
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModification
|
||||
import prog8.ast.statements.*
|
||||
|
||||
|
||||
internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: ErrorReporter) : AstWalker() {
|
||||
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
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
|
||||
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
|
||||
&& assignment.target.isInRegularRAM(program.namespace)) {
|
||||
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>>()
|
||||
|
||||
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
subroutineVariables.clear()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
val decls = scope.statements.filterIsInstance<VarDecl>()
|
||||
subroutineVariables.addAll(decls.map { it.name to it })
|
||||
|
||||
val sub = scope.definingSubroutine()
|
||||
if (sub != null) {
|
||||
// move vardecls of the scope into the upper scope. Make sure the position remains the same!
|
||||
val numericVarsWithValue = decls.filter { it.value != null && it.datatype in NumericDatatypes }
|
||||
val replaceVardecls =numericVarsWithValue.map {
|
||||
val initValue = it.value!! // assume here that value has always been set by now
|
||||
it.value = null // make sure no value init assignment for this vardecl will be created later (would be superfluous)
|
||||
val target = AssignTarget(IdentifierReference(listOf(it.name), it.position), null, null, it.position)
|
||||
val assign = Assignment(target, initValue, it.position)
|
||||
initValue.parent = assign
|
||||
IAstModification.ReplaceNode(it, assign, scope)
|
||||
}
|
||||
val moveVardeclsUp = decls.map { IAstModification.InsertFirst(it, sub) }
|
||||
return replaceVardecls + moveVardeclsUp
|
||||
}
|
||||
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 kernel 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.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.STRUCT)
|
||||
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(typecast.type in WordDatatypes) {
|
||||
val fcall = typecast.parent as? IFunctionCall
|
||||
if (fcall != null) {
|
||||
val sub = fcall.target.targetStatement(program.namespace) as? Subroutine
|
||||
if (sub != null && sub.isAsmSubroutine) {
|
||||
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(sourceDt in PassByReferenceDatatypes) {
|
||||
if(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))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
@ -27,7 +27,10 @@ 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) {
|
||||
var slowCodegenWarnings = false
|
||||
}
|
||||
|
||||
|
||||
class CompilerException(message: String?) : Exception(message)
|
||||
|
@ -4,7 +4,11 @@ import prog8.ast.AstToSourceCode
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.statements.Directive
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8.optimizer.*
|
||||
import prog8.optimizer.UnusedCodeRemover
|
||||
import prog8.optimizer.constantFold
|
||||
import prog8.optimizer.optimizeStatements
|
||||
import prog8.optimizer.simplifyExpressions
|
||||
@ -12,6 +16,7 @@ import prog8.parser.ModuleImporter
|
||||
import prog8.parser.ParsingFailedError
|
||||
import prog8.parser.moduleName
|
||||
import java.nio.file.Path
|
||||
import kotlin.system.exitProcess
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
|
||||
@ -24,16 +29,28 @@ class CompilationResult(val success: Boolean,
|
||||
fun compileProgram(filepath: Path,
|
||||
optimize: Boolean,
|
||||
writeAssembly: Boolean,
|
||||
slowCodegenWarnings: Boolean,
|
||||
compilationTarget: String,
|
||||
outputDir: Path): CompilationResult {
|
||||
var programName = ""
|
||||
lateinit var programAst: Program
|
||||
lateinit var importedFiles: List<Path>
|
||||
val errors = ErrorReporter()
|
||||
|
||||
when(compilationTarget) {
|
||||
C64Target.name -> CompilationTarget.instance = C64Target
|
||||
Cx16Target.name -> CompilationTarget.instance = Cx16Target
|
||||
else -> {
|
||||
System.err.println("invalid compilation target")
|
||||
exitProcess(1)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
val totalTime = measureTimeMillis {
|
||||
// import main module and everything it needs
|
||||
val (ast, compilationOptions, imported) = parseImports(filepath, errors)
|
||||
compilationOptions.slowCodegenWarnings = slowCodegenWarnings
|
||||
programAst = ast
|
||||
importedFiles = imported
|
||||
processAst(programAst, errors, compilationOptions)
|
||||
@ -46,6 +63,8 @@ fun compileProgram(filepath: Path,
|
||||
if(writeAssembly)
|
||||
programName = writeAssembly(programAst, errors, outputDir, optimize, compilationOptions)
|
||||
}
|
||||
System.out.flush()
|
||||
System.err.flush()
|
||||
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
|
||||
return CompilationResult(true, programAst, programName, importedFiles)
|
||||
|
||||
@ -75,8 +94,8 @@ fun compileProgram(filepath: Path,
|
||||
}
|
||||
|
||||
private fun parseImports(filepath: Path, errors: ErrorReporter): Triple<Program, CompilationOptions, List<Path>> {
|
||||
println("Parsing...")
|
||||
val importer = ModuleImporter(errors)
|
||||
println("Compiler target: ${CompilationTarget.instance.name}. Parsing...")
|
||||
val importer = ModuleImporter()
|
||||
val programAst = Program(moduleName(filepath.fileName), mutableListOf())
|
||||
importer.importModule(programAst, filepath)
|
||||
errors.handle()
|
||||
@ -87,15 +106,12 @@ private fun parseImports(filepath: Path, errors: ErrorReporter): Triple<Program,
|
||||
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
|
||||
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
|
||||
|
||||
// if we're producing a PRG or BASIC program, include the c64utils and c64lib libraries
|
||||
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG) {
|
||||
importer.importLibraryModule(programAst, "c64lib")
|
||||
importer.importLibraryModule(programAst, "c64utils")
|
||||
}
|
||||
// depending on the machine and compiler options we may have to include some libraries
|
||||
CompilationTarget.instance.machine.importLibs(compilerOptions, importer, programAst)
|
||||
|
||||
// always import prog8lib and math
|
||||
// always import prog8_lib and math
|
||||
importer.importLibraryModule(programAst, "math")
|
||||
importer.importLibraryModule(programAst, "prog8lib")
|
||||
importer.importLibraryModule(programAst, "prog8_lib")
|
||||
errors.handle()
|
||||
return Triple(programAst, compilerOptions, importedFiles)
|
||||
}
|
||||
@ -106,13 +122,12 @@ private fun determineCompilationOptions(program: Program): CompilationOptions {
|
||||
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 =
|
||||
val noSysInit = allOptions.any { it.name == "no_sysinit" }
|
||||
var zpType: ZeropageType =
|
||||
if (zpoption == null)
|
||||
if(floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE
|
||||
else
|
||||
@ -122,6 +137,12 @@ private fun determineCompilationOptions(program: Program): CompilationOptions {
|
||||
ZeropageType.KERNALSAFE
|
||||
// error will be printed by the astchecker
|
||||
}
|
||||
|
||||
if (zpType==ZeropageType.FLOATSAFE && CompilationTarget.instance.name == Cx16Target.name) {
|
||||
System.err.println("Warning: Cx16 target must use zp option basicsafe instead of floatsafe")
|
||||
zpType = ZeropageType.BASICSAFE
|
||||
}
|
||||
|
||||
val zpReserved = mainModule.statements
|
||||
.asSequence()
|
||||
.filter { it is Directive && it.directive == "%zpreserved" }
|
||||
@ -129,25 +150,34 @@ private fun determineCompilationOptions(program: Program): CompilationOptions {
|
||||
.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
|
||||
zpType, zpReserved, floatsEnabled, noSysInit
|
||||
)
|
||||
}
|
||||
|
||||
private fun processAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) {
|
||||
// perform initial syntax checks and processings
|
||||
println("Processing...")
|
||||
println("Processing for target ${CompilationTarget.instance.name}...")
|
||||
programAst.checkIdentifiers(errors)
|
||||
errors.handle()
|
||||
programAst.makeForeverLoops()
|
||||
programAst.constantFold(errors)
|
||||
errors.handle()
|
||||
programAst.removeNopsFlattenAnonScopes()
|
||||
programAst.reorderStatements()
|
||||
programAst.reorderStatements(errors)
|
||||
errors.handle()
|
||||
programAst.addTypecasts(errors)
|
||||
errors.handle()
|
||||
programAst.variousCleanups()
|
||||
programAst.checkValid(compilerOptions, errors)
|
||||
errors.handle()
|
||||
programAst.checkIdentifiers(errors)
|
||||
@ -160,35 +190,50 @@ private fun optimizeAst(programAst: Program, errors: ErrorReporter) {
|
||||
while (true) {
|
||||
// keep optimizing expressions and statements until no more steps remain
|
||||
val optsDone1 = programAst.simplifyExpressions()
|
||||
val optsDone2 = programAst.optimizeStatements(errors)
|
||||
val optsDone2 = programAst.splitBinaryExpressions()
|
||||
val optsDone3 = programAst.optimizeStatements(errors)
|
||||
programAst.constantFold(errors) // because simplified statements and expressions can result in more constants that can be folded away
|
||||
errors.handle()
|
||||
if (optsDone1 + optsDone2 == 0)
|
||||
if (optsDone1 + optsDone2 + optsDone3 == 0)
|
||||
break
|
||||
}
|
||||
|
||||
val remover = UnusedCodeRemover(programAst, errors)
|
||||
remover.visit(programAst)
|
||||
remover.applyModifications()
|
||||
errors.handle()
|
||||
}
|
||||
|
||||
private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) {
|
||||
programAst.addTypecasts(errors)
|
||||
errors.handle()
|
||||
programAst.removeNopsFlattenAnonScopes()
|
||||
programAst.variousCleanups()
|
||||
programAst.checkValid(compilerOptions, errors) // check if final tree is still valid
|
||||
errors.handle()
|
||||
programAst.checkRecursion(errors) // check if there are recursive subroutine calls
|
||||
val callGraph = CallGraph(programAst)
|
||||
callGraph.checkRecursiveCalls(errors)
|
||||
errors.handle()
|
||||
programAst.verifyFunctionArgTypes()
|
||||
programAst.moveMainAndStartToFirst()
|
||||
}
|
||||
|
||||
private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir: Path,
|
||||
optimize: Boolean, compilerOptions: CompilationOptions): String {
|
||||
// asm generation directly from the Ast,
|
||||
val zeropage = CompilationTarget.machine.getZeropage(compilerOptions)
|
||||
programAst.prepareAsmVariablesAndReturns(errors)
|
||||
programAst.processAstBeforeAsmGeneration(errors)
|
||||
errors.handle()
|
||||
val assembly = CompilationTarget.asmGenerator(
|
||||
|
||||
// printAst(programAst)
|
||||
|
||||
CompilationTarget.instance.machine.initializeZeropage(compilerOptions)
|
||||
val assembly = CompilationTarget.instance.asmGenerator(
|
||||
programAst,
|
||||
zeropage,
|
||||
errors,
|
||||
CompilationTarget.instance.machine.zeropage,
|
||||
compilerOptions,
|
||||
outputDir).compileToAssembly(optimize)
|
||||
assembly.assemble(compilerOptions)
|
||||
errors.handle()
|
||||
return assembly.name
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,12 @@ 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.
|
||||
|
||||
@ -16,7 +22,7 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
||||
fun available() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size
|
||||
|
||||
fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: ErrorReporter): Int {
|
||||
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"isSameAs scopedname can't be allocated twice"}
|
||||
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"scopedname can't be allocated twice"}
|
||||
|
||||
if(options.zeropage==ZeropageType.DONTUSE)
|
||||
throw CompilerException("zero page usage has been disabled")
|
||||
@ -39,13 +45,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 +64,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
|
||||
|
||||
}
|
||||
|
@ -1,80 +0,0 @@
|
||||
package prog8.compiler.target
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.ast.base.NumericDatatypes
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModification
|
||||
import prog8.ast.statements.*
|
||||
|
||||
|
||||
class AsmVariableAndReturnsPreparer(val program: Program, val errors: ErrorReporter): AstWalker() {
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
if(decl.value==null && decl.type==VarDeclType.VAR && decl.datatype in NumericDatatypes) {
|
||||
// a numeric vardecl without an initial value is initialized with zero.
|
||||
decl.value = decl.zeroElementValue()
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
val decls = scope.statements.filterIsInstance<VarDecl>()
|
||||
val sub = scope.definingSubroutine()
|
||||
if(sub!=null) {
|
||||
val existingVariables = sub.statements.filterIsInstance<VarDecl>().associateBy { it.name }
|
||||
var conflicts = false
|
||||
decls.forEach {
|
||||
val existing = existingVariables[it.name]
|
||||
if (existing!=null) {
|
||||
errors.err("variable ${it.name} already defined in subroutine ${sub.name} at ${existing.position}", it.position)
|
||||
conflicts = true
|
||||
}
|
||||
}
|
||||
if(!conflicts) {
|
||||
val numericVarsWithValue = decls.filter { it.value!=null && it.datatype in NumericDatatypes }
|
||||
return numericVarsWithValue.map {
|
||||
val initValue = it.value!! // assume here that value has always been set by now
|
||||
it.value = null // make sure no value init assignment for this vardecl will be created later (would be superfluous)
|
||||
val target = AssignTarget(null, IdentifierReference(listOf(it.name), it.position), null, null, it.position)
|
||||
val assign = Assignment(target, null, initValue, it.position)
|
||||
IAstModification.InsertFirst(assign, scope)
|
||||
} +
|
||||
decls.map { IAstModification.ReplaceNode(it, NopStatement(it.position), scope) } +
|
||||
decls.map { IAstModification.InsertFirst(it, sub) } // move it up to the subroutine
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernel routine.
|
||||
// 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.statements.isNotEmpty()
|
||||
&& subroutine.amountOfRtsInAsm()==0
|
||||
&& subroutine.statements.lastOrNull {it !is VarDecl } !is Return
|
||||
&& subroutine.statements.last() !is Subroutine) {
|
||||
mods += IAstModification.InsertAfter(subroutine.statements.last(), 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 as Node)
|
||||
}
|
||||
|
||||
return mods
|
||||
}
|
||||
}
|
@ -1,17 +1,47 @@
|
||||
package prog8.compiler.target
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.Zeropage
|
||||
import prog8.compiler.target.c64.C64MachineDefinition
|
||||
import prog8.compiler.target.c64.Petscii
|
||||
import prog8.compiler.target.c64.codegen.AsmGen
|
||||
import prog8.compiler.target.cx16.CX16MachineDefinition
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
internal interface CompilationTarget {
|
||||
val name: String
|
||||
val machine: IMachineDefinition
|
||||
fun encodeString(str: String, altEncoding: Boolean): List<Short>
|
||||
fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
|
||||
fun asmGenerator(program: Program, errors: ErrorReporter, zp: Zeropage, options: CompilationOptions, path: Path): IAssemblyGenerator
|
||||
|
||||
companion object {
|
||||
lateinit var name: String
|
||||
lateinit var machine: IMachineDefinition
|
||||
lateinit var encodeString: (str: String, altEncoding: Boolean) -> List<Short>
|
||||
lateinit var decodeString: (bytes: List<Short>, altEncoding: Boolean) -> String
|
||||
lateinit var asmGenerator: (Program, Zeropage, CompilationOptions, Path) -> IAssemblyGenerator
|
||||
lateinit var instance: CompilationTarget
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal object C64Target: CompilationTarget {
|
||||
override val name = "c64"
|
||||
override val machine = C64MachineDefinition
|
||||
override fun encodeString(str: String, altEncoding: Boolean) =
|
||||
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
|
||||
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||
override fun asmGenerator(program: Program, errors: ErrorReporter, zp: Zeropage, options: CompilationOptions, path: Path) =
|
||||
AsmGen(program, errors, zp, options, path)
|
||||
}
|
||||
|
||||
internal object Cx16Target: CompilationTarget {
|
||||
override val name = "cx16"
|
||||
override val machine = CX16MachineDefinition
|
||||
override fun encodeString(str: String, altEncoding: Boolean) =
|
||||
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
|
||||
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||
override fun asmGenerator(program: Program, errors: ErrorReporter, zp: Zeropage, options: CompilationOptions, path: Path) =
|
||||
AsmGen(program, errors, zp, options, path)
|
||||
}
|
||||
|
@ -7,6 +7,8 @@ internal interface IAssemblyGenerator {
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -1,15 +1,39 @@
|
||||
package prog8.compiler.target
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.Zeropage
|
||||
import prog8.parser.ModuleImporter
|
||||
|
||||
|
||||
interface IMachineDefinition {
|
||||
internal interface IMachineFloat {
|
||||
fun toDouble(): Double
|
||||
fun makeFloatFillAsm(): String
|
||||
}
|
||||
|
||||
internal enum class CpuType {
|
||||
CPU6502,
|
||||
CPU65c02
|
||||
}
|
||||
|
||||
internal interface IMachineDefinition {
|
||||
val FLOAT_MAX_NEGATIVE: Double
|
||||
val FLOAT_MAX_POSITIVE: Double
|
||||
val FLOAT_MEM_SIZE: Int
|
||||
val POINTER_MEM_SIZE: Int
|
||||
val ESTACK_LO: Int
|
||||
val ESTACK_HI: Int
|
||||
val BASIC_LOAD_ADDRESS : Int
|
||||
val RAW_LOAD_ADDRESS : Int
|
||||
|
||||
val opcodeNames: Set<String>
|
||||
var zeropage: Zeropage
|
||||
val cpu: CpuType
|
||||
|
||||
fun getZeropage(compilerOptions: CompilationOptions): Zeropage
|
||||
fun initializeZeropage(compilerOptions: CompilationOptions)
|
||||
fun getFloat(num: Number): IMachineFloat
|
||||
fun getFloatRomConst(number: Double): String?
|
||||
fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program)
|
||||
fun launchEmulator(programName: String)
|
||||
fun isRegularRAMaddress(address: Int): Boolean
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package prog8.compiler.target.c64
|
||||
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.OutputType
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.compiler.target.IAssemblyProgram
|
||||
import prog8.compiler.target.generatedLabelPrefix
|
||||
import java.nio.file.Path
|
||||
@ -14,20 +15,20 @@ class AssemblyProgram(override val name: String, outputDir: Path) : IAssemblyPro
|
||||
private val viceMonListFile = outputDir.resolve("$name.vice-mon-list")
|
||||
|
||||
override fun assemble(options: CompilationOptions) {
|
||||
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps
|
||||
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps (default = do this silently)
|
||||
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
|
||||
"-Wall", "-Wno-strict-bool", "-Wno-shadow", "-Werror", "-Wno-error=long-branch",
|
||||
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
|
||||
"--dump-labels", "--vice-labels", "-l", viceMonListFile.toString(), "--no-monitor")
|
||||
|
||||
val outFile = when (options.output) {
|
||||
OutputType.PRG -> {
|
||||
command.add("--cbm-prg")
|
||||
println("\nCreating C-64 prg.")
|
||||
println("\nCreating prg for target ${CompilationTarget.instance.name}.")
|
||||
prgFile
|
||||
}
|
||||
OutputType.RAW -> {
|
||||
command.add("--nostart")
|
||||
println("\nCreating raw binary.")
|
||||
println("\nCreating raw binary for target ${CompilationTarget.instance.name}.")
|
||||
binFile
|
||||
}
|
||||
}
|
||||
|
@ -1,37 +1,99 @@
|
||||
package prog8.compiler.target.c64
|
||||
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.CompilerException
|
||||
import prog8.compiler.Zeropage
|
||||
import prog8.compiler.ZeropageType
|
||||
import prog8.ast.Program
|
||||
import prog8.compiler.*
|
||||
import prog8.compiler.target.CpuType
|
||||
import prog8.compiler.target.IMachineDefinition
|
||||
import java.awt.Color
|
||||
import java.awt.image.BufferedImage
|
||||
import javax.imageio.ImageIO
|
||||
import prog8.compiler.target.IMachineFloat
|
||||
import prog8.parser.ModuleImporter
|
||||
import java.io.IOException
|
||||
import java.math.RoundingMode
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.pow
|
||||
|
||||
object C64MachineDefinition: IMachineDefinition {
|
||||
internal object C64MachineDefinition: IMachineDefinition {
|
||||
|
||||
override val cpu = CpuType.CPU6502
|
||||
|
||||
// 5-byte cbm MFLPT format limitations:
|
||||
override val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255
|
||||
override val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
|
||||
override val FLOAT_MEM_SIZE = 5
|
||||
const val BASIC_LOAD_ADDRESS = 0x0801
|
||||
const val RAW_LOAD_ADDRESS = 0xc000
|
||||
override val POINTER_MEM_SIZE = 2
|
||||
override val BASIC_LOAD_ADDRESS = 0x0801
|
||||
override val RAW_LOAD_ADDRESS = 0xc000
|
||||
|
||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
||||
// and some heavily used string constants derived from the two values above
|
||||
const val ESTACK_LO_VALUE = 0xce00 // $ce00-$ceff inclusive
|
||||
const val ESTACK_HI_VALUE = 0xcf00 // $cf00-$cfff inclusive
|
||||
const val ESTACK_LO_HEX = "\$ce00"
|
||||
const val ESTACK_LO_PLUS1_HEX = "\$ce01"
|
||||
const val ESTACK_LO_PLUS2_HEX = "\$ce02"
|
||||
const val ESTACK_HI_HEX = "\$cf00"
|
||||
const val ESTACK_HI_PLUS1_HEX = "\$cf01"
|
||||
const val ESTACK_HI_PLUS2_HEX = "\$cf02"
|
||||
override val ESTACK_LO = 0xce00 // $ce00-$ceff inclusive
|
||||
override val ESTACK_HI = 0xcf00 // $ce00-$ceff inclusive
|
||||
|
||||
override fun getZeropage(compilerOptions: CompilationOptions) = C64Zeropage(compilerOptions)
|
||||
override lateinit var zeropage: Zeropage
|
||||
|
||||
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
|
||||
|
||||
override fun getFloatRomConst(number: Double): String? {
|
||||
// try to match the ROM float constants to save memory
|
||||
val mflpt5 = Mflpt5.fromNumber(number)
|
||||
val floatbytes = shortArrayOf(mflpt5.b0, mflpt5.b1, mflpt5.b2, mflpt5.b3, mflpt5.b4)
|
||||
when {
|
||||
floatbytes.contentEquals(shortArrayOf(0x00, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_ZERO_const" // not a ROM const
|
||||
floatbytes.contentEquals(shortArrayOf(0x81, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_ONE_const" // not a ROM const
|
||||
floatbytes.contentEquals(shortArrayOf(0x82, 0x49, 0x0f, 0xda, 0xa1)) -> return "floats.FL_PIVAL"
|
||||
floatbytes.contentEquals(shortArrayOf(0x90, 0x80, 0x00, 0x00, 0x00)) -> return "floats.FL_N32768"
|
||||
floatbytes.contentEquals(shortArrayOf(0x81, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_FONE"
|
||||
floatbytes.contentEquals(shortArrayOf(0x80, 0x35, 0x04, 0xf3, 0x34)) -> return "floats.FL_SQRHLF"
|
||||
floatbytes.contentEquals(shortArrayOf(0x81, 0x35, 0x04, 0xf3, 0x34)) -> return "floats.FL_SQRTWO"
|
||||
floatbytes.contentEquals(shortArrayOf(0x80, 0x80, 0x00, 0x00, 0x00)) -> return "floats.FL_NEGHLF"
|
||||
floatbytes.contentEquals(shortArrayOf(0x80, 0x31, 0x72, 0x17, 0xf8)) -> return "floats.FL_LOG2"
|
||||
floatbytes.contentEquals(shortArrayOf(0x84, 0x20, 0x00, 0x00, 0x00)) -> return "floats.FL_TENC"
|
||||
floatbytes.contentEquals(shortArrayOf(0x9e, 0x6e, 0x6b, 0x28, 0x00)) -> return "floats.FL_NZMIL"
|
||||
floatbytes.contentEquals(shortArrayOf(0x80, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_FHALF"
|
||||
floatbytes.contentEquals(shortArrayOf(0x81, 0x38, 0xaa, 0x3b, 0x29)) -> return "floats.FL_LOGEB2"
|
||||
floatbytes.contentEquals(shortArrayOf(0x81, 0x49, 0x0f, 0xda, 0xa2)) -> return "floats.FL_PIHALF"
|
||||
floatbytes.contentEquals(shortArrayOf(0x83, 0x49, 0x0f, 0xda, 0xa2)) -> return "floats.FL_TWOPI"
|
||||
floatbytes.contentEquals(shortArrayOf(0x7f, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_FR4"
|
||||
else -> {
|
||||
// attempt to correct for a few rounding issues
|
||||
when (number.toBigDecimal().setScale(10, RoundingMode.HALF_DOWN).toDouble()) {
|
||||
3.1415926536 -> return "floats.FL_PIVAL"
|
||||
1.4142135624 -> return "floats.FL_SQRTWO"
|
||||
0.7071067812 -> return "floats.FL_SQRHLF"
|
||||
0.6931471806 -> return "floats.FL_LOG2"
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program) {
|
||||
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||
importer.importLibraryModule(program, "syslib")
|
||||
}
|
||||
|
||||
override fun launchEmulator(programName: String) {
|
||||
for(emulator in listOf("x64sc", "x64")) {
|
||||
println("\nStarting C-64 emulator $emulator...")
|
||||
val cmdline = listOf(emulator, "-silent", "-moncommands", "$programName.vice-mon-list",
|
||||
"-autostartprgmode", "1", "-autostart-warp", "-autostart", programName + ".prg")
|
||||
val processb = ProcessBuilder(cmdline).inheritIO()
|
||||
val process: Process
|
||||
try {
|
||||
process=processb.start()
|
||||
} catch(x: IOException) {
|
||||
continue // try the next emulator executable
|
||||
}
|
||||
process.waitFor()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
override fun isRegularRAMaddress(address: Int): Boolean = address<0xa000 || address in 0xc000..0xcfff
|
||||
|
||||
override fun initializeZeropage(compilerOptions: CompilationOptions) {
|
||||
zeropage = C64Zeropage(compilerOptions)
|
||||
}
|
||||
|
||||
// 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names
|
||||
override val opcodeNames = setOf("adc", "ahx", "alr", "anc", "and", "ane", "arr", "asl", "asr", "axs", "bcc", "bcs",
|
||||
@ -45,20 +107,12 @@ object C64MachineDefinition: IMachineDefinition {
|
||||
"sta", "stx", "sty", "tas", "tax", "tay", "tsx", "txa", "txs", "tya", "xaa")
|
||||
|
||||
|
||||
class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
internal class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
|
||||
companion object {
|
||||
const val SCRATCH_B1 = 0x02
|
||||
const val SCRATCH_REG = 0x03 // temp storage for a register
|
||||
const val SCRATCH_REG_X = 0xfa // temp storage for register X (the evaluation stack pointer)
|
||||
const val SCRATCH_W1 = 0xfb // $fb+$fc
|
||||
const val SCRATCH_W2 = 0xfd // $fd+$fe
|
||||
}
|
||||
|
||||
override val exitProgramStrategy: ExitProgramStrategy = when (options.zeropage) {
|
||||
ZeropageType.BASICSAFE, ZeropageType.DONTUSE -> ExitProgramStrategy.CLEAN_EXIT
|
||||
ZeropageType.FLOATSAFE, ZeropageType.KERNALSAFE, ZeropageType.FULL -> ExitProgramStrategy.SYSTEM_RESET
|
||||
}
|
||||
override val SCRATCH_B1 = 0x02 // temp storage for a single byte
|
||||
override val SCRATCH_REG = 0x03 // temp storage for a register, must be B1+1
|
||||
override val SCRATCH_W1 = 0xfb // temp storage 1 for a word $fb+$fc
|
||||
override val SCRATCH_W2 = 0xfd // temp storage 2 for a word $fb+$fc
|
||||
|
||||
|
||||
init {
|
||||
@ -68,12 +122,14 @@ object C64MachineDefinition: IMachineDefinition {
|
||||
if (options.zeropage == ZeropageType.FULL) {
|
||||
free.addAll(0x04..0xf9)
|
||||
free.add(0xff)
|
||||
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_REG_X, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
|
||||
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
|
||||
free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ
|
||||
} else {
|
||||
if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
|
||||
free.addAll(listOf(0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
|
||||
0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
|
||||
free.addAll(listOf(
|
||||
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
|
||||
0x16, 0x17, 0x18, 0x19, 0x1a,
|
||||
0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
|
||||
0x22, 0x23, 0x24, 0x25,
|
||||
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
|
||||
0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53,
|
||||
@ -90,38 +146,36 @@ object C64MachineDefinition: IMachineDefinition {
|
||||
if (options.zeropage == ZeropageType.FLOATSAFE) {
|
||||
// remove the zero page locations used for floating point operations from the free list
|
||||
free.removeAll(listOf(
|
||||
0x12, 0x26, 0x27, 0x28, 0x29, 0x2a,
|
||||
0x10, 0x11, 0x12, 0x26, 0x27, 0x28, 0x29, 0x2a,
|
||||
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
|
||||
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
||||
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
|
||||
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xf
|
||||
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
|
||||
))
|
||||
}
|
||||
|
||||
if(options.zeropage!=ZeropageType.DONTUSE) {
|
||||
// add the other free Zp addresses,
|
||||
// these are valid for the C-64 (when no RS232 I/O is performed) but to keep BASIC running fully:
|
||||
// add the free Zp addresses
|
||||
// these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O*
|
||||
free.addAll(listOf(0x04, 0x05, 0x06, 0x0a, 0x0e,
|
||||
0x94, 0x95, 0xa7, 0xa8, 0xa9, 0xaa,
|
||||
0xb5, 0xb6, 0xf7, 0xf8, 0xf9))
|
||||
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa5, 0xa6,
|
||||
0xb0, 0xb1, 0xbe, 0xbf, 0xf9))
|
||||
} else {
|
||||
// don't use the zeropage at all
|
||||
free.clear()
|
||||
}
|
||||
}
|
||||
assert(SCRATCH_B1 !in free)
|
||||
assert(SCRATCH_REG !in free)
|
||||
assert(SCRATCH_REG_X !in free)
|
||||
assert(SCRATCH_W1 !in free)
|
||||
assert(SCRATCH_W2 !in free)
|
||||
require(SCRATCH_B1 !in free)
|
||||
require(SCRATCH_REG !in free)
|
||||
require(SCRATCH_W1 !in free)
|
||||
require(SCRATCH_W2 !in free)
|
||||
|
||||
for (reserved in options.zpReserved)
|
||||
reserve(reserved)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short) {
|
||||
internal data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short): IMachineFloat {
|
||||
|
||||
companion object {
|
||||
val zero = Mflpt5(0, 0, 0, 0, 0)
|
||||
@ -168,7 +222,7 @@ object C64MachineDefinition: IMachineDefinition {
|
||||
}
|
||||
}
|
||||
|
||||
fun toDouble(): Double {
|
||||
override fun toDouble(): Double {
|
||||
if (this == zero) return 0.0
|
||||
val exp = b0 - 128
|
||||
val sign = (b1.toInt() and 0x80) > 0
|
||||
@ -176,91 +230,14 @@ object C64MachineDefinition: IMachineDefinition {
|
||||
val result = number.toDouble() * (2.0).pow(exp) / 0x100000000
|
||||
return if (sign) -result else result
|
||||
}
|
||||
}
|
||||
|
||||
object Charset {
|
||||
private val normalImg = ImageIO.read(javaClass.getResource("/charset/c64/charset-normal.png"))
|
||||
private val shiftedImg = ImageIO.read(javaClass.getResource("/charset/c64/charset-shifted.png"))
|
||||
|
||||
private fun scanChars(img: BufferedImage): Array<BufferedImage> {
|
||||
|
||||
val transparent = BufferedImage(img.width, img.height, BufferedImage.TYPE_INT_ARGB)
|
||||
transparent.createGraphics().drawImage(img, 0, 0, null)
|
||||
|
||||
val black = Color(0, 0, 0).rgb
|
||||
val nopixel = Color(0, 0, 0, 0).rgb
|
||||
for (y in 0 until transparent.height) {
|
||||
for (x in 0 until transparent.width) {
|
||||
val col = transparent.getRGB(x, y)
|
||||
if (col == black)
|
||||
transparent.setRGB(x, y, nopixel)
|
||||
}
|
||||
}
|
||||
|
||||
val numColumns = transparent.width / 8
|
||||
val charImages = (0..255).map {
|
||||
val charX = it % numColumns
|
||||
val charY = it / numColumns
|
||||
transparent.getSubimage(charX * 8, charY * 8, 8, 8)
|
||||
}
|
||||
return charImages.toTypedArray()
|
||||
override fun makeFloatFillAsm(): String {
|
||||
val b0 = "$" + b0.toString(16).padStart(2, '0')
|
||||
val b1 = "$" + b1.toString(16).padStart(2, '0')
|
||||
val b2 = "$" + b2.toString(16).padStart(2, '0')
|
||||
val b3 = "$" + b3.toString(16).padStart(2, '0')
|
||||
val b4 = "$" + b4.toString(16).padStart(2, '0')
|
||||
return "$b0, $b1, $b2, $b3, $b4"
|
||||
}
|
||||
|
||||
val normalChars = scanChars(normalImg)
|
||||
val shiftedChars = scanChars(shiftedImg)
|
||||
|
||||
private val coloredNormalChars = mutableMapOf<Short, Array<BufferedImage>>()
|
||||
|
||||
fun getColoredChar(screenCode: Short, color: Short): BufferedImage {
|
||||
val colorIdx = (color % colorPalette.size).toShort()
|
||||
val chars = coloredNormalChars[colorIdx]
|
||||
if (chars != null)
|
||||
return chars[screenCode.toInt()]
|
||||
|
||||
val coloredChars = mutableListOf<BufferedImage>()
|
||||
val transparent = Color(0, 0, 0, 0).rgb
|
||||
val rgb = colorPalette[colorIdx.toInt()].rgb
|
||||
for (c in normalChars) {
|
||||
val colored = c.copy()
|
||||
for (y in 0 until colored.height)
|
||||
for (x in 0 until colored.width) {
|
||||
if (colored.getRGB(x, y) != transparent) {
|
||||
colored.setRGB(x, y, rgb)
|
||||
}
|
||||
}
|
||||
coloredChars.add(colored)
|
||||
}
|
||||
coloredNormalChars[colorIdx] = coloredChars.toTypedArray()
|
||||
return coloredNormalChars.getValue(colorIdx)[screenCode.toInt()]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun BufferedImage.copy(): BufferedImage {
|
||||
val bcopy = BufferedImage(this.width, this.height, this.type)
|
||||
val g = bcopy.graphics
|
||||
g.drawImage(this, 0, 0, null)
|
||||
g.dispose()
|
||||
return bcopy
|
||||
}
|
||||
|
||||
val colorPalette = listOf( // this is Pepto's Commodore-64 palette http://www.pepto.de/projects/colorvic/
|
||||
Color(0x000000), // 0 = black
|
||||
Color(0xFFFFFF), // 1 = white
|
||||
Color(0x813338), // 2 = red
|
||||
Color(0x75cec8), // 3 = cyan
|
||||
Color(0x8e3c97), // 4 = purple
|
||||
Color(0x56ac4d), // 5 = green
|
||||
Color(0x2e2c9b), // 6 = blue
|
||||
Color(0xedf171), // 7 = yellow
|
||||
Color(0x8e5029), // 8 = orange
|
||||
Color(0x553800), // 9 = brown
|
||||
Color(0xc46c71), // 10 = light red
|
||||
Color(0x4a4a4a), // 11 = dark grey
|
||||
Color(0x7b7b7b), // 12 = medium grey
|
||||
Color(0xa9ff9f), // 13 = light green
|
||||
Color(0x706deb), // 14 = light blue
|
||||
Color(0xb2b2b2) // 15 = light grey
|
||||
)
|
||||
|
||||
}
|
||||
|
@ -1054,11 +1054,16 @@ object Petscii {
|
||||
val lookup = if(lowercase) encodingPetsciiLowercase else encodingPetsciiUppercase
|
||||
return text.map {
|
||||
val petscii = lookup[it]
|
||||
petscii?.toShort() ?: if(it=='\u0000')
|
||||
0.toShort()
|
||||
else {
|
||||
val case = if (lowercase) "lower" else "upper"
|
||||
throw CharConversionException("no ${case}case Petscii character for '$it' (${it.toShort()})")
|
||||
petscii?.toShort() ?: when (it) {
|
||||
'\u0000' -> 0.toShort()
|
||||
in '\u8000'..'\u80ff' -> {
|
||||
// special case: take the lower 8 bit hex value directly
|
||||
(it.toInt() - 0x8000).toShort()
|
||||
}
|
||||
else -> {
|
||||
val case = if (lowercase) "lower" else "upper"
|
||||
throw CharConversionException("no ${case}case Petscii character for '$it' (${it.toShort()})")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1072,11 +1077,16 @@ object Petscii {
|
||||
val lookup = if(lowercase) encodingScreencodeLowercase else encodingScreencodeUppercase
|
||||
return text.map{
|
||||
val screencode = lookup[it]
|
||||
screencode?.toShort() ?: if(it=='\u0000')
|
||||
0.toShort()
|
||||
else {
|
||||
val case = if (lowercase) "lower" else "upper"
|
||||
throw CharConversionException("no ${case}Screencode character for '$it' (${it.toShort()})")
|
||||
screencode?.toShort() ?: when (it) {
|
||||
'\u0000' -> 0.toShort()
|
||||
in '\u8000'..'\u80ff' -> {
|
||||
// special case: take the lower 8 bit hex value directly
|
||||
(it.toInt() - 0x8000).toShort()
|
||||
}
|
||||
else -> {
|
||||
val case = if (lowercase) "lower" else "upper"
|
||||
throw CharConversionException("no ${case}Screencode character for '$it' (${it.toShort()})")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,8 +1,5 @@
|
||||
package prog8.compiler.target.c64.codegen
|
||||
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX
|
||||
|
||||
|
||||
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
|
||||
|
||||
@ -87,10 +84,10 @@ private fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>):
|
||||
// the repeated lda can be removed
|
||||
val mods = mutableListOf<Modification>()
|
||||
for(lines in linesByFour) {
|
||||
if(lines[0].value.trim()=="lda $ESTACK_LO_PLUS1_HEX,x" &&
|
||||
if(lines[0].value.trim()=="lda P8ESTACK_LO+1,x" &&
|
||||
lines[1].value.trim().startsWith("cmp ") &&
|
||||
lines[2].value.trim().startsWith("beq ") &&
|
||||
lines[3].value.trim()=="lda $ESTACK_LO_PLUS1_HEX,x") {
|
||||
lines[3].value.trim()=="lda P8ESTACK_LO+1,x") {
|
||||
mods.add(Modification(lines[3].index, true, null)) // remove the second lda
|
||||
}
|
||||
}
|
||||
@ -102,10 +99,10 @@ private fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<S
|
||||
// this is a lot harder for word values because the instruction sequence varies.
|
||||
val mods = mutableListOf<Modification>()
|
||||
for(lines in linesByFour) {
|
||||
if(lines[0].value.trim()=="sta $ESTACK_LO_HEX,x" &&
|
||||
if(lines[0].value.trim()=="sta P8ESTACK_LO,x" &&
|
||||
lines[1].value.trim()=="dex" &&
|
||||
lines[2].value.trim()=="inx" &&
|
||||
lines[3].value.trim()=="lda $ESTACK_LO_HEX,x") {
|
||||
lines[3].value.trim()=="lda P8ESTACK_LO,x") {
|
||||
mods.add(Modification(lines[1].index, true, null))
|
||||
mods.add(Modification(lines[2].index, true, null))
|
||||
mods.add(Modification(lines[3].index, true, null))
|
||||
@ -154,7 +151,8 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
||||
}
|
||||
|
||||
if(first.startsWith("lda") && second.startsWith("ldy") && third.startsWith("sta") && fourth.startsWith("sty") &&
|
||||
fifth.startsWith("lda") && sixth.startsWith("ldy") && seventh.startsWith("jsr c64flt.copy_float")) {
|
||||
fifth.startsWith("lda") && sixth.startsWith("ldy") &&
|
||||
(seventh.startsWith("jsr floats.copy_float") || seventh.startsWith("jsr cx16flt.copy_float"))) {
|
||||
|
||||
val nineth = pair[8].value.trimStart()
|
||||
val tenth = pair[9].value.trimStart()
|
||||
@ -164,7 +162,8 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
||||
val fourteenth = pair[13].value.trimStart()
|
||||
|
||||
if(eighth.startsWith("lda") && nineth.startsWith("ldy") && tenth.startsWith("sta") && eleventh.startsWith("sty") &&
|
||||
twelveth.startsWith("lda") && thirteenth.startsWith("ldy") && fourteenth.startsWith("jsr c64flt.copy_float")) {
|
||||
twelveth.startsWith("lda") && thirteenth.startsWith("ldy") &&
|
||||
(fourteenth.startsWith("jsr floats.copy_float") || fourteenth.startsWith("jsr cx16flt.copy_float"))) {
|
||||
|
||||
if(first.substring(4) == eighth.substring(4) && second.substring(4)==nineth.substring(4)) {
|
||||
// identical float init
|
||||
@ -181,6 +180,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
||||
|
||||
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can be eliminated
|
||||
// TODO this is not true if X is not a regular RAM memory address (but instead mapped I/O or ROM)
|
||||
val mods = mutableListOf<Modification>()
|
||||
for (pair in linesByFour) {
|
||||
val first = pair[0].value.trimStart()
|
||||
@ -196,8 +196,8 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>)
|
||||
(first.startsWith("sty ") && second.startsWith("ldy ")) ||
|
||||
(first.startsWith("stx ") && second.startsWith("ldx "))
|
||||
) {
|
||||
val firstLoc = first.substring(4)
|
||||
val secondLoc = second.substring(4)
|
||||
val firstLoc = first.substring(4).trimStart()
|
||||
val secondLoc = second.substring(4).trimStart()
|
||||
if (firstLoc == secondLoc) {
|
||||
mods.add(Modification(pair[1].index, true, null))
|
||||
}
|
||||
|
@ -1,744 +0,0 @@
|
||||
package prog8.compiler.target.c64.codegen
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.DirectMemoryWrite
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.c64.C64MachineDefinition
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
|
||||
import prog8.compiler.toHex
|
||||
|
||||
|
||||
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||
|
||||
internal fun translate(assign: Assignment) {
|
||||
if(assign.aug_op!=null)
|
||||
throw AssemblyError("aug-op assignments should have been transformed to normal ones")
|
||||
|
||||
when(assign.value) {
|
||||
is NumericLiteralValue -> {
|
||||
val numVal = assign.value as NumericLiteralValue
|
||||
when(numVal.type) {
|
||||
DataType.UBYTE, DataType.BYTE -> assignFromByteConstant(assign.target, numVal.number.toShort())
|
||||
DataType.UWORD, DataType.WORD -> assignFromWordConstant(assign.target, numVal.number.toInt())
|
||||
DataType.FLOAT -> assignFromFloatConstant(assign.target, numVal.number.toDouble())
|
||||
else -> throw AssemblyError("weird numval type")
|
||||
}
|
||||
}
|
||||
is RegisterExpr -> {
|
||||
assignFromRegister(assign.target, (assign.value as RegisterExpr).register)
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val type = assign.target.inferType(program, assign).typeOrElse(DataType.STRUCT)
|
||||
when(type) {
|
||||
DataType.UBYTE, DataType.BYTE -> assignFromByteVariable(assign.target, assign.value as IdentifierReference)
|
||||
DataType.UWORD, DataType.WORD -> assignFromWordVariable(assign.target, assign.value as IdentifierReference)
|
||||
DataType.FLOAT -> assignFromFloatVariable(assign.target, assign.value as IdentifierReference)
|
||||
else -> throw AssemblyError("unsupported assignment target type $type")
|
||||
}
|
||||
}
|
||||
is AddressOf -> {
|
||||
val identifier = (assign.value as AddressOf).identifier
|
||||
assignFromAddressOf(assign.target, identifier)
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
val read = (assign.value as DirectMemoryRead)
|
||||
when(read.addressExpression) {
|
||||
is NumericLiteralValue -> {
|
||||
val address = (read.addressExpression as NumericLiteralValue).number.toInt()
|
||||
assignFromMemoryByte(assign.target, address, null)
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
assignFromMemoryByte(assign.target, null, read.addressExpression as IdentifierReference)
|
||||
}
|
||||
else -> {
|
||||
throw AssemblyError("missing asm gen for memread assignment into ${assign.target}")
|
||||
}
|
||||
}
|
||||
}
|
||||
is PrefixExpression -> {
|
||||
// TODO optimize common cases
|
||||
asmgen.translateExpression(assign.value as PrefixExpression)
|
||||
assignFromEvalResult(assign.target)
|
||||
}
|
||||
is BinaryExpression -> {
|
||||
// TODO optimize common cases
|
||||
asmgen.translateExpression(assign.value as BinaryExpression)
|
||||
assignFromEvalResult(assign.target)
|
||||
}
|
||||
is ArrayIndexedExpression -> {
|
||||
// TODO optimize common cases
|
||||
val arrayExpr = assign.value as ArrayIndexedExpression
|
||||
val arrayDt = arrayExpr.identifier.targetVarDecl(program.namespace)!!.datatype
|
||||
val index = arrayExpr.arrayspec.index
|
||||
if(index is NumericLiteralValue) {
|
||||
// constant array index value
|
||||
val arrayVarName = asmgen.asmIdentifierName(arrayExpr.identifier)
|
||||
val indexValue = index.number.toInt() * ArrayElementTypes.getValue(arrayDt).memorySize()
|
||||
when (arrayDt) {
|
||||
DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B ->
|
||||
asmgen.out(" lda $arrayVarName+$indexValue | sta $ESTACK_LO_HEX,x | dex")
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W ->
|
||||
asmgen.out(" lda $arrayVarName+$indexValue | sta $ESTACK_LO_HEX,x | lda $arrayVarName+$indexValue+1 | sta $ESTACK_HI_HEX,x | dex")
|
||||
DataType.ARRAY_F ->
|
||||
asmgen.out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue | jsr c64flt.push_float")
|
||||
else ->
|
||||
throw AssemblyError("weird array type")
|
||||
}
|
||||
} else {
|
||||
asmgen.translateArrayIndexIntoA(arrayExpr)
|
||||
asmgen.readAndPushArrayvalueWithIndexA(arrayDt, arrayExpr.identifier)
|
||||
}
|
||||
assignFromEvalResult(assign.target)
|
||||
}
|
||||
is TypecastExpression -> {
|
||||
val cast = assign.value as TypecastExpression
|
||||
val sourceType = cast.expression.inferType(program)
|
||||
val targetType = assign.target.inferType(program, assign)
|
||||
if(sourceType.isKnown && targetType.isKnown &&
|
||||
(sourceType.typeOrElse(DataType.STRUCT) in ByteDatatypes && targetType.typeOrElse(DataType.STRUCT) in ByteDatatypes) ||
|
||||
(sourceType.typeOrElse(DataType.STRUCT) in WordDatatypes && targetType.typeOrElse(DataType.STRUCT) in WordDatatypes)) {
|
||||
// no need for a type cast
|
||||
assign.value = cast.expression
|
||||
translate(assign)
|
||||
} else {
|
||||
asmgen.translateExpression(assign.value as TypecastExpression)
|
||||
assignFromEvalResult(assign.target)
|
||||
}
|
||||
}
|
||||
is FunctionCall -> {
|
||||
asmgen.translateExpression(assign.value as FunctionCall)
|
||||
assignFromEvalResult(assign.target)
|
||||
}
|
||||
is ArrayLiteralValue, is StringLiteralValue -> throw AssemblyError("no asm gen for string/array assignment $assign")
|
||||
is StructLiteralValue -> throw AssemblyError("struct literal value assignment should have been flattened ${assign.value.position}")
|
||||
is RangeExpr -> throw AssemblyError("range expression should have been changed into array values ${assign.value.position}")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignFromEvalResult(target: AssignTarget) {
|
||||
val targetIdent = target.identifier
|
||||
when {
|
||||
target.register!=null -> {
|
||||
if(target.register== Register.X)
|
||||
throw AssemblyError("can't pop into X register - use variable instead")
|
||||
asmgen.out(" inx | ld${target.register.name.toLowerCase()} $ESTACK_LO_HEX,x ")
|
||||
}
|
||||
targetIdent!=null -> {
|
||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||
val targetDt = targetIdent.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
when(targetDt) {
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
asmgen.out(" inx | lda $ESTACK_LO_HEX,x | sta $targetName")
|
||||
}
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda $ESTACK_LO_HEX,x
|
||||
sta $targetName
|
||||
lda $ESTACK_HI_HEX,x
|
||||
sta $targetName+1
|
||||
""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out("""
|
||||
lda #<$targetName
|
||||
ldy #>$targetName
|
||||
jsr c64flt.pop_float
|
||||
""")
|
||||
}
|
||||
else -> throw AssemblyError("weird target variable type $targetDt")
|
||||
}
|
||||
}
|
||||
target.memoryAddress!=null -> {
|
||||
asmgen.out(" inx | ldy $ESTACK_LO_HEX,x")
|
||||
storeRegisterInMemoryAddress(Register.Y, target.memoryAddress)
|
||||
}
|
||||
target.arrayindexed!=null -> {
|
||||
val arrayDt = target.arrayindexed!!.identifier.targetVarDecl(program.namespace)!!.datatype
|
||||
val arrayVarName = asmgen.asmIdentifierName(target.arrayindexed!!.identifier)
|
||||
asmgen.translateExpression(target.arrayindexed!!.arrayspec.index)
|
||||
asmgen.out(" inx | lda $ESTACK_LO_HEX,x")
|
||||
popAndWriteArrayvalueWithIndexA(arrayDt, arrayVarName)
|
||||
}
|
||||
else -> throw AssemblyError("weird assignment target $target")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignFromAddressOf(target: AssignTarget, name: IdentifierReference) {
|
||||
val targetIdent = target.identifier
|
||||
val targetArrayIdx = target.arrayindexed
|
||||
val struct = name.memberOfStruct(program.namespace)
|
||||
val sourceName = if(struct!=null) {
|
||||
// take the address of the first struct member instead
|
||||
val decl = name.targetVarDecl(program.namespace)!!
|
||||
val firstStructMember = struct.nameOfFirstMember()
|
||||
// find the flattened var that belongs to this first struct member
|
||||
val firstVarName = listOf(decl.name, firstStructMember)
|
||||
val firstVar = name.definingScope().lookup(firstVarName, name) as VarDecl
|
||||
firstVar.name
|
||||
} else {
|
||||
asmgen.fixNameSymbols(name.nameInSource.joinToString ("."))
|
||||
}
|
||||
|
||||
when {
|
||||
targetIdent!=null -> {
|
||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||
asmgen.out("""
|
||||
lda #<$sourceName
|
||||
ldy #>$sourceName
|
||||
sta $targetName
|
||||
sty $targetName+1
|
||||
""")
|
||||
}
|
||||
target.memoryAddress!=null -> {
|
||||
throw AssemblyError("no asm gen for assign address $sourceName to memory word $target")
|
||||
}
|
||||
targetArrayIdx!=null -> {
|
||||
val index = targetArrayIdx.arrayspec.index
|
||||
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||
throw AssemblyError("no asm gen for assign address $sourceName to array $targetName [ $index ]")
|
||||
}
|
||||
else -> throw AssemblyError("no asm gen for assign address $sourceName to $target")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignFromWordVariable(target: AssignTarget, variable: IdentifierReference) {
|
||||
val sourceName = asmgen.asmIdentifierName(variable)
|
||||
val targetIdent = target.identifier
|
||||
val targetArrayIdx = target.arrayindexed
|
||||
when {
|
||||
targetIdent!=null -> {
|
||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||
asmgen.out("""
|
||||
lda $sourceName
|
||||
ldy $sourceName+1
|
||||
sta $targetName
|
||||
sty $targetName+1
|
||||
""")
|
||||
}
|
||||
target.memoryAddress!=null -> {
|
||||
throw AssemblyError("no asm gen for assign wordvar $sourceName to memory ${target.memoryAddress}")
|
||||
}
|
||||
targetArrayIdx!=null -> {
|
||||
val index = targetArrayIdx.arrayspec.index
|
||||
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||
asmgen.out(" lda $sourceName | sta $ESTACK_LO_HEX,x | lda $sourceName+1 | sta $ESTACK_HI_HEX,x | dex")
|
||||
asmgen.translateExpression(index)
|
||||
asmgen.out(" inx | lda $ESTACK_LO_HEX,x")
|
||||
val arrayDt = targetArrayIdx.identifier.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
popAndWriteArrayvalueWithIndexA(arrayDt, targetName)
|
||||
}
|
||||
else -> throw AssemblyError("no asm gen for assign wordvar to $target")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignFromFloatVariable(target: AssignTarget, variable: IdentifierReference) {
|
||||
val sourceName = asmgen.asmIdentifierName(variable)
|
||||
val targetIdent = target.identifier
|
||||
val targetArrayIdx = target.arrayindexed
|
||||
when {
|
||||
targetIdent!=null -> {
|
||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||
asmgen.out("""
|
||||
lda $sourceName
|
||||
sta $targetName
|
||||
lda $sourceName+1
|
||||
sta $targetName+1
|
||||
lda $sourceName+2
|
||||
sta $targetName+2
|
||||
lda $sourceName+3
|
||||
sta $targetName+3
|
||||
lda $sourceName+4
|
||||
sta $targetName+4
|
||||
""")
|
||||
}
|
||||
targetArrayIdx!=null -> {
|
||||
val index = targetArrayIdx.arrayspec.index
|
||||
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||
asmgen.out(" lda #<$sourceName | ldy #>$sourceName | jsr c64flt.push_float")
|
||||
asmgen.translateExpression(index)
|
||||
asmgen.out(" lda #<$targetName | ldy #>$targetName | jsr c64flt.pop_float_to_indexed_var")
|
||||
}
|
||||
else -> throw AssemblyError("no asm gen for assign floatvar to $target")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignFromByteVariable(target: AssignTarget, variable: IdentifierReference) {
|
||||
val sourceName = asmgen.asmIdentifierName(variable)
|
||||
val targetIdent = target.identifier
|
||||
val targetArrayIdx = target.arrayindexed
|
||||
when {
|
||||
target.register!=null -> {
|
||||
asmgen.out(" ld${target.register.name.toLowerCase()} $sourceName")
|
||||
}
|
||||
targetIdent!=null -> {
|
||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||
asmgen.out("""
|
||||
lda $sourceName
|
||||
sta $targetName
|
||||
""")
|
||||
}
|
||||
targetArrayIdx!=null -> {
|
||||
val index = targetArrayIdx.arrayspec.index
|
||||
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||
val arrayDt = targetArrayIdx.identifier.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
asmgen.out(" lda $sourceName | sta $ESTACK_LO_HEX,x | dex")
|
||||
asmgen.translateExpression(index)
|
||||
asmgen.out(" inx | lda $ESTACK_LO_HEX,x")
|
||||
popAndWriteArrayvalueWithIndexA(arrayDt, targetName)
|
||||
}
|
||||
target.memoryAddress != null -> {
|
||||
val addressExpr = target.memoryAddress.addressExpression
|
||||
val addressLv = addressExpr as? NumericLiteralValue
|
||||
when {
|
||||
addressLv != null -> asmgen.out(" lda $sourceName | sta ${addressLv.number.toHex()}")
|
||||
addressExpr is IdentifierReference -> {
|
||||
val targetName = asmgen.asmIdentifierName(addressExpr)
|
||||
asmgen.out(" lda $sourceName | sta $targetName")
|
||||
}
|
||||
else -> {
|
||||
asmgen.translateExpression(addressExpr)
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda $ESTACK_LO_HEX,x
|
||||
ldy $ESTACK_HI_HEX,x
|
||||
sta (+) +1
|
||||
sty (+) +2
|
||||
lda $sourceName
|
||||
+ sta ${'$'}ffff ; modified
|
||||
""")
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("no asm gen for assign bytevar to $target")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignFromRegister(target: AssignTarget, register: Register) {
|
||||
val targetIdent = target.identifier
|
||||
val targetArrayIdx = target.arrayindexed
|
||||
when {
|
||||
targetIdent!=null -> {
|
||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||
asmgen.out(" st${register.name.toLowerCase()} $targetName")
|
||||
}
|
||||
target.register!=null -> {
|
||||
when(register) {
|
||||
Register.A -> when(target.register) {
|
||||
Register.A -> {}
|
||||
Register.X -> asmgen.out(" tax")
|
||||
Register.Y -> asmgen.out(" tay")
|
||||
}
|
||||
Register.X -> when(target.register) {
|
||||
Register.A -> asmgen.out(" txa")
|
||||
Register.X -> {}
|
||||
Register.Y -> asmgen.out(" txy")
|
||||
}
|
||||
Register.Y -> when(target.register) {
|
||||
Register.A -> asmgen.out(" tya")
|
||||
Register.X -> asmgen.out(" tyx")
|
||||
Register.Y -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
target.memoryAddress!=null -> {
|
||||
storeRegisterInMemoryAddress(register, target.memoryAddress)
|
||||
}
|
||||
targetArrayIdx!=null -> {
|
||||
val index = targetArrayIdx.arrayspec.index
|
||||
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||
when (index) {
|
||||
is NumericLiteralValue -> {
|
||||
val memindex = index.number.toInt()
|
||||
when(register) {
|
||||
Register.A -> asmgen.out(" sta $targetName+$memindex")
|
||||
Register.X -> asmgen.out(" stx $targetName+$memindex")
|
||||
Register.Y -> asmgen.out(" sty $targetName+$memindex")
|
||||
}
|
||||
}
|
||||
is RegisterExpr -> {
|
||||
when(register) {
|
||||
Register.A -> asmgen.out(" sta ${C64Zeropage.SCRATCH_B1}")
|
||||
Register.X -> asmgen.out(" stx ${C64Zeropage.SCRATCH_B1}")
|
||||
Register.Y -> asmgen.out(" sty ${C64Zeropage.SCRATCH_B1}")
|
||||
}
|
||||
when(index.register) {
|
||||
Register.A -> {}
|
||||
Register.X -> asmgen.out(" txa")
|
||||
Register.Y -> asmgen.out(" tya")
|
||||
}
|
||||
asmgen.out("""
|
||||
tay
|
||||
lda ${C64Zeropage.SCRATCH_B1}
|
||||
sta $targetName,y
|
||||
""")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
when(register) {
|
||||
Register.A -> asmgen.out(" sta ${C64Zeropage.SCRATCH_B1}")
|
||||
Register.X -> asmgen.out(" stx ${C64Zeropage.SCRATCH_B1}")
|
||||
Register.Y -> asmgen.out(" sty ${C64Zeropage.SCRATCH_B1}")
|
||||
}
|
||||
asmgen.out("""
|
||||
lda ${asmgen.asmIdentifierName(index)}
|
||||
tay
|
||||
lda ${C64Zeropage.SCRATCH_B1}
|
||||
sta $targetName,y
|
||||
""")
|
||||
}
|
||||
else -> {
|
||||
asmgen.saveRegister(register)
|
||||
asmgen.translateExpression(index)
|
||||
asmgen.restoreRegister(register)
|
||||
when(register) {
|
||||
Register.A -> asmgen.out(" sta ${C64Zeropage.SCRATCH_B1}")
|
||||
Register.X -> asmgen.out(" stx ${C64Zeropage.SCRATCH_B1}")
|
||||
Register.Y -> asmgen.out(" sty ${C64Zeropage.SCRATCH_B1}")
|
||||
}
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda $ESTACK_LO_HEX,x
|
||||
tay
|
||||
lda ${C64Zeropage.SCRATCH_B1}
|
||||
sta $targetName,y
|
||||
""")
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("no asm gen for assign register $register to $target")
|
||||
}
|
||||
}
|
||||
|
||||
private fun storeRegisterInMemoryAddress(register: Register, memoryAddress: DirectMemoryWrite) {
|
||||
val addressExpr = memoryAddress.addressExpression
|
||||
val addressLv = addressExpr as? NumericLiteralValue
|
||||
val registerName = register.name.toLowerCase()
|
||||
when {
|
||||
addressLv != null -> asmgen.out(" st$registerName ${addressLv.number.toHex()}")
|
||||
addressExpr is IdentifierReference -> {
|
||||
val targetName = asmgen.asmIdentifierName(addressExpr)
|
||||
when(register) {
|
||||
Register.A -> asmgen.out("""
|
||||
ldy $targetName
|
||||
sty (+) +1
|
||||
ldy $targetName+1
|
||||
sty (+) +2
|
||||
+ sta ${'$'}ffff ; modified""")
|
||||
Register.X -> asmgen.out("""
|
||||
ldy $targetName
|
||||
sty (+) +1
|
||||
ldy $targetName+1
|
||||
sty (+) +2
|
||||
+ stx ${'$'}ffff ; modified""")
|
||||
Register.Y -> asmgen.out("""
|
||||
lda $targetName
|
||||
sta (+) +1
|
||||
lda $targetName+1
|
||||
sta (+) +2
|
||||
+ sty ${'$'}ffff ; modified""")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
asmgen.saveRegister(register)
|
||||
asmgen.translateExpression(addressExpr)
|
||||
asmgen.restoreRegister(register)
|
||||
when (register) {
|
||||
Register.A -> asmgen.out(" tay")
|
||||
Register.X -> throw AssemblyError("can't use X register here")
|
||||
Register.Y -> {}
|
||||
}
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda $ESTACK_LO_HEX,x
|
||||
sta (+) +1
|
||||
lda $ESTACK_HI_HEX,x
|
||||
sta (+) +2
|
||||
+ sty ${'$'}ffff ; modified
|
||||
""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignFromWordConstant(target: AssignTarget, word: Int) {
|
||||
val targetIdent = target.identifier
|
||||
val targetArrayIdx = target.arrayindexed
|
||||
when {
|
||||
targetIdent!=null -> {
|
||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||
if(word ushr 8 == word and 255) {
|
||||
// lsb=msb
|
||||
asmgen.out("""
|
||||
lda #${(word and 255).toHex()}
|
||||
sta $targetName
|
||||
sta $targetName+1
|
||||
""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda #<${word.toHex()}
|
||||
ldy #>${word.toHex()}
|
||||
sta $targetName
|
||||
sty $targetName+1
|
||||
""")
|
||||
}
|
||||
}
|
||||
target.memoryAddress!=null -> {
|
||||
throw AssemblyError("no asm gen for assign word $word to memory ${target.memoryAddress}")
|
||||
}
|
||||
targetArrayIdx!=null -> {
|
||||
val index = targetArrayIdx.arrayspec.index
|
||||
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||
// TODO optimize common cases
|
||||
asmgen.translateExpression(index)
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda $ESTACK_LO_HEX,x
|
||||
asl a
|
||||
tay
|
||||
lda #<${word.toHex()}
|
||||
sta $targetName,y
|
||||
lda #>${word.toHex()}
|
||||
sta $targetName+1,y
|
||||
""")
|
||||
}
|
||||
else -> throw AssemblyError("no asm gen for assign word $word to $target")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignFromByteConstant(target: AssignTarget, byte: Short) {
|
||||
val targetIdent = target.identifier
|
||||
val targetArrayIdx = target.arrayindexed
|
||||
when {
|
||||
target.register!=null -> {
|
||||
asmgen.out(" ld${target.register.name.toLowerCase()} #${byte.toHex()}")
|
||||
}
|
||||
targetIdent!=null -> {
|
||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||
asmgen.out(" lda #${byte.toHex()} | sta $targetName ")
|
||||
}
|
||||
target.memoryAddress!=null -> {
|
||||
asmgen.out(" ldy #${byte.toHex()}")
|
||||
storeRegisterInMemoryAddress(Register.Y, target.memoryAddress)
|
||||
}
|
||||
targetArrayIdx!=null -> {
|
||||
val index = targetArrayIdx.arrayspec.index
|
||||
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||
// TODO optimize common cases
|
||||
asmgen.translateExpression(index)
|
||||
asmgen.out("""
|
||||
inx
|
||||
ldy $ESTACK_LO_HEX,x
|
||||
lda #${byte.toHex()}
|
||||
sta $targetName,y
|
||||
""")
|
||||
}
|
||||
else -> throw AssemblyError("no asm gen for assign byte $byte to $target")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignFromFloatConstant(target: AssignTarget, float: Double) {
|
||||
val targetIdent = target.identifier
|
||||
val targetArrayIdx = target.arrayindexed
|
||||
if(float==0.0) {
|
||||
// optimized case for float zero
|
||||
when {
|
||||
targetIdent != null -> {
|
||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||
asmgen.out("""
|
||||
lda #0
|
||||
sta $targetName
|
||||
sta $targetName+1
|
||||
sta $targetName+2
|
||||
sta $targetName+3
|
||||
sta $targetName+4
|
||||
""")
|
||||
}
|
||||
targetArrayIdx!=null -> {
|
||||
val index = targetArrayIdx.arrayspec.index
|
||||
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||
if(index is NumericLiteralValue) {
|
||||
val indexValue = index.number.toInt() * C64MachineDefinition.FLOAT_MEM_SIZE
|
||||
asmgen.out("""
|
||||
lda #0
|
||||
sta $targetName+$indexValue
|
||||
sta $targetName+$indexValue+1
|
||||
sta $targetName+$indexValue+2
|
||||
sta $targetName+$indexValue+3
|
||||
sta $targetName+$indexValue+4
|
||||
""")
|
||||
} else {
|
||||
asmgen.translateExpression(index)
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda $ESTACK_LO_HEX,x
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc $ESTACK_LO_HEX,x
|
||||
tay
|
||||
lda #0
|
||||
sta $targetName,y
|
||||
sta $targetName+1,y
|
||||
sta $targetName+2,y
|
||||
sta $targetName+3,y
|
||||
sta $targetName+4,y
|
||||
""") // TODO use a subroutine for this
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("no asm gen for assign float 0.0 to $target")
|
||||
}
|
||||
} else {
|
||||
// non-zero value
|
||||
val constFloat = asmgen.getFloatConst(float)
|
||||
when {
|
||||
targetIdent != null -> {
|
||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||
asmgen.out("""
|
||||
lda $constFloat
|
||||
sta $targetName
|
||||
lda $constFloat+1
|
||||
sta $targetName+1
|
||||
lda $constFloat+2
|
||||
sta $targetName+2
|
||||
lda $constFloat+3
|
||||
sta $targetName+3
|
||||
lda $constFloat+4
|
||||
sta $targetName+4
|
||||
""")
|
||||
}
|
||||
targetArrayIdx!=null -> {
|
||||
val index = targetArrayIdx.arrayspec.index
|
||||
val arrayVarName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||
if(index is NumericLiteralValue) {
|
||||
val indexValue = index.number.toInt() * C64MachineDefinition.FLOAT_MEM_SIZE
|
||||
asmgen.out("""
|
||||
lda $constFloat
|
||||
sta $arrayVarName+$indexValue
|
||||
lda $constFloat+1
|
||||
sta $arrayVarName+$indexValue+1
|
||||
lda $constFloat+2
|
||||
sta $arrayVarName+$indexValue+2
|
||||
lda $constFloat+3
|
||||
sta $arrayVarName+$indexValue+3
|
||||
lda $constFloat+4
|
||||
sta $arrayVarName+$indexValue+4
|
||||
""")
|
||||
} else {
|
||||
asmgen.translateArrayIndexIntoA(targetArrayIdx)
|
||||
asmgen.out("""
|
||||
sta ${C64Zeropage.SCRATCH_REG}
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc ${C64Zeropage.SCRATCH_REG}
|
||||
tay
|
||||
lda $constFloat
|
||||
sta $arrayVarName,y
|
||||
lda $constFloat+1
|
||||
sta $arrayVarName+1,y
|
||||
lda $constFloat+2
|
||||
sta $arrayVarName+2,y
|
||||
lda $constFloat+3
|
||||
sta $arrayVarName+3,y
|
||||
lda $constFloat+4
|
||||
sta $arrayVarName+4,y
|
||||
""") // TODO use a subroutine for this
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("no asm gen for assign float $float to $target")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignFromMemoryByte(target: AssignTarget, address: Int?, identifier: IdentifierReference?) {
|
||||
val targetIdent = target.identifier
|
||||
val targetArrayIdx = target.arrayindexed
|
||||
if(address!=null) {
|
||||
when {
|
||||
target.register!=null -> {
|
||||
asmgen.out(" ld${target.register.name.toLowerCase()} ${address.toHex()}")
|
||||
}
|
||||
targetIdent!=null -> {
|
||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||
asmgen.out("""
|
||||
lda ${address.toHex()}
|
||||
sta $targetName
|
||||
""")
|
||||
}
|
||||
target.memoryAddress!=null -> {
|
||||
asmgen.out(" ldy ${address.toHex()}")
|
||||
storeRegisterInMemoryAddress(Register.Y, target.memoryAddress)
|
||||
}
|
||||
targetArrayIdx!=null -> {
|
||||
val index = targetArrayIdx.arrayspec.index
|
||||
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||
throw AssemblyError("no asm gen for assign memory byte at $address to array $targetName [ $index ]")
|
||||
}
|
||||
else -> throw AssemblyError("no asm gen for assign memory byte $target")
|
||||
}
|
||||
}
|
||||
else if(identifier!=null) {
|
||||
val sourceName = asmgen.asmIdentifierName(identifier)
|
||||
when {
|
||||
target.register!=null -> {
|
||||
asmgen.out("""
|
||||
lda $sourceName
|
||||
sta (+) + 1
|
||||
lda $sourceName+1
|
||||
sta (+) + 2""")
|
||||
when(target.register){
|
||||
Register.A -> asmgen.out("+ lda ${'$'}ffff\t; modified")
|
||||
Register.X -> asmgen.out("+ ldx ${'$'}ffff\t; modified")
|
||||
Register.Y -> asmgen.out("+ ldy ${'$'}ffff\t; modified")
|
||||
}
|
||||
}
|
||||
targetIdent!=null -> {
|
||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||
asmgen.out("""
|
||||
lda $sourceName
|
||||
sta (+) + 1
|
||||
lda $sourceName+1
|
||||
sta (+) + 2
|
||||
+ lda ${'$'}ffff\t; modified
|
||||
sta $targetName""")
|
||||
}
|
||||
target.memoryAddress!=null -> {
|
||||
asmgen.out(" ldy $sourceName")
|
||||
storeRegisterInMemoryAddress(Register.Y, target.memoryAddress)
|
||||
}
|
||||
targetArrayIdx!=null -> {
|
||||
val index = targetArrayIdx.arrayspec.index
|
||||
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||
throw AssemblyError("no asm gen for assign memory byte $sourceName to array $targetName [ $index ]")
|
||||
}
|
||||
else -> throw AssemblyError("no asm gen for assign memory byte $target")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun popAndWriteArrayvalueWithIndexA(arrayDt: DataType, variablename: String) {
|
||||
when (arrayDt) {
|
||||
DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B ->
|
||||
asmgen.out(" tay | inx | lda $ESTACK_LO_HEX,x | sta $variablename,y")
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W ->
|
||||
asmgen.out(" asl a | tay | inx | lda $ESTACK_LO_HEX,x | sta $variablename,y | lda $ESTACK_HI_HEX,x | sta $variablename+1,y")
|
||||
DataType.ARRAY_F ->
|
||||
// index * 5 is done in the subroutine that's called
|
||||
asmgen.out("""
|
||||
sta $ESTACK_LO_HEX,x
|
||||
dex
|
||||
lda #<$variablename
|
||||
ldy #>$variablename
|
||||
jsr c64flt.pop_float_to_indexed_var
|
||||
""")
|
||||
else ->
|
||||
throw AssemblyError("weird array type")
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,230 +1,262 @@
|
||||
package prog8.compiler.target.c64.codegen
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.RegisterOrStatusflag
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.statements.SubroutineParameter
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
|
||||
import prog8.compiler.toHex
|
||||
import prog8.compiler.target.c64.codegen.assignment.*
|
||||
|
||||
|
||||
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||
|
||||
internal fun translateFunctionCall(stmt: IFunctionCall) {
|
||||
internal fun translateFunctionCallStatement(stmt: IFunctionCall) {
|
||||
val sub = stmt.target.targetSubroutine(program.namespace)!!
|
||||
val preserveStatusRegisterAfterCall = sub.asmReturnvaluesRegisters.any {it.statusflag!=null}
|
||||
translateFunctionCall(stmt, preserveStatusRegisterAfterCall)
|
||||
// functioncalls no longer return results on the stack, so simply ignore the results in the registers
|
||||
if(preserveStatusRegisterAfterCall)
|
||||
asmgen.out(" plp\t; restore status flags from call")
|
||||
}
|
||||
|
||||
|
||||
internal fun translateFunctionCall(stmt: IFunctionCall, preserveStatusRegisterAfterCall: Boolean) {
|
||||
// output the code to setup the parameters and perform the actual call
|
||||
// does NOT output the code to deal with the result values!
|
||||
val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
||||
if(Register.X in sub.asmClobbers)
|
||||
asmgen.out(" stx c64.SCRATCH_ZPREGX") // we only save X for now (required! is the eval stack pointer), screw A and Y...
|
||||
val saveX = CpuRegister.X in sub.asmClobbers || sub.regXasResult() || sub.regXasParam()
|
||||
if(saveX)
|
||||
asmgen.saveRegister(CpuRegister.X, preserveStatusRegisterAfterCall, (stmt as Node).definingSubroutine()!!)
|
||||
|
||||
val subName = asmgen.asmIdentifierName(stmt.target)
|
||||
val subName = asmgen.asmSymbolName(stmt.target)
|
||||
if(stmt.args.isNotEmpty()) {
|
||||
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
|
||||
translateFuncArguments(arg.first, arg.second, sub)
|
||||
if(sub.asmParameterRegisters.isEmpty()) {
|
||||
// via variables
|
||||
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
|
||||
argumentViaVariable(sub, arg.first, arg.second)
|
||||
}
|
||||
} else {
|
||||
// via registers
|
||||
if(sub.parameters.size==1) {
|
||||
// just a single parameter, no risk of clobbering registers
|
||||
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single()), stmt.args[0])
|
||||
} else {
|
||||
// multiple register arguments, risk of register clobbering.
|
||||
// evaluate arguments onto the stack, and load the registers from the evaluated values on the stack.
|
||||
when {
|
||||
stmt.args.all {it is AddressOf ||
|
||||
it is NumericLiteralValue ||
|
||||
it is StringLiteralValue ||
|
||||
it is ArrayLiteralValue ||
|
||||
it is IdentifierReference} -> {
|
||||
// no risk of clobbering for these simple argument types. Optimize the register loading.
|
||||
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
|
||||
argumentViaRegister(sub, arg.first, arg.second)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// Risk of clobbering due to complex expression args. Work via the stack.
|
||||
registerArgsViaStackEvaluation(stmt, sub)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
asmgen.out(" jsr $subName")
|
||||
|
||||
if(Register.X in sub.asmClobbers)
|
||||
asmgen.out(" ldx c64.SCRATCH_ZPREGX") // restore X again
|
||||
if(preserveStatusRegisterAfterCall) {
|
||||
asmgen.out(" php\t; save status flags from call")
|
||||
// note: the containing statement (such as the FunctionCallStatement or the Assignment or the Expression)
|
||||
// must take care of popping this value again at the end!
|
||||
}
|
||||
|
||||
if(saveX)
|
||||
asmgen.restoreRegister(CpuRegister.X, preserveStatusRegisterAfterCall)
|
||||
}
|
||||
|
||||
private fun translateFuncArguments(parameter: IndexedValue<SubroutineParameter>, value: Expression, sub: Subroutine) {
|
||||
val sourceIDt = value.inferType(program)
|
||||
if(!sourceIDt.isKnown)
|
||||
throw AssemblyError("arg type unknown")
|
||||
val sourceDt = sourceIDt.typeOrElse(DataType.STRUCT)
|
||||
if(!argumentTypeCompatible(sourceDt, parameter.value.type))
|
||||
private fun registerArgsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) {
|
||||
// this is called when one or more of the arguments are 'complex' and
|
||||
// cannot be assigned to a register easily or risk clobbering other registers.
|
||||
|
||||
if(sub.parameters.isEmpty())
|
||||
return
|
||||
|
||||
// 1. load all arguments reversed onto the stack: first arg goes last (is on top).
|
||||
for (arg in stmt.args.reversed())
|
||||
asmgen.translateExpression(arg)
|
||||
|
||||
var argForCarry: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
|
||||
var argForXregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
|
||||
var argForAregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
|
||||
|
||||
asmgen.out(" inx") // align estack pointer
|
||||
|
||||
for(argi in stmt.args.zip(sub.asmParameterRegisters).withIndex()) {
|
||||
when {
|
||||
argi.value.second.statusflag == Statusflag.Pc -> {
|
||||
require(argForCarry == null)
|
||||
argForCarry = argi
|
||||
}
|
||||
argi.value.second.statusflag != null -> throw AssemblyError("can only use Carry as status flag parameter")
|
||||
argi.value.second.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) -> {
|
||||
require(argForXregister==null)
|
||||
argForXregister = argi
|
||||
}
|
||||
argi.value.second.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.AY) -> {
|
||||
require(argForAregister == null)
|
||||
argForAregister = argi
|
||||
}
|
||||
argi.value.second.registerOrPair == RegisterOrPair.Y -> {
|
||||
asmgen.out(" ldy P8ESTACK_LO+${argi.index},x")
|
||||
}
|
||||
else -> throw AssemblyError("weird argument")
|
||||
}
|
||||
}
|
||||
|
||||
if(argForCarry!=null) {
|
||||
asmgen.out("""
|
||||
lda P8ESTACK_LO+${argForCarry.index},x
|
||||
beq +
|
||||
sec
|
||||
bcs ++
|
||||
+ clc
|
||||
+ php""") // push the status flags
|
||||
}
|
||||
|
||||
if(argForAregister!=null) {
|
||||
when(argForAregister.value.second.registerOrPair) {
|
||||
RegisterOrPair.A -> asmgen.out(" lda P8ESTACK_LO+${argForAregister.index},x")
|
||||
RegisterOrPair.AY -> asmgen.out(" lda P8ESTACK_LO+${argForAregister.index},x | ldy P8ESTACK_HI+${argForAregister.index},x")
|
||||
else -> throw AssemblyError("weird arg")
|
||||
}
|
||||
}
|
||||
|
||||
if(argForXregister!=null) {
|
||||
|
||||
if(argForAregister!=null)
|
||||
asmgen.out(" pha")
|
||||
when(argForXregister.value.second.registerOrPair) {
|
||||
RegisterOrPair.X -> asmgen.out(" lda P8ESTACK_LO+${argForXregister.index},x | tax")
|
||||
RegisterOrPair.AX -> asmgen.out(" ldy P8ESTACK_LO+${argForXregister.index},x | lda P8ESTACK_HI+${argForXregister.index},x | tax | tya")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldy P8ESTACK_HI+${argForXregister.index},x | lda P8ESTACK_LO+${argForXregister.index},x | tax")
|
||||
else -> throw AssemblyError("weird arg")
|
||||
}
|
||||
if(argForAregister!=null)
|
||||
asmgen.out(" pla")
|
||||
} else {
|
||||
repeat(sub.parameters.size - 1) { asmgen.out(" inx") } // unwind stack
|
||||
}
|
||||
|
||||
if(argForCarry!=null)
|
||||
asmgen.out(" plp") // set the carry flag back to correct value
|
||||
}
|
||||
|
||||
private fun argumentViaVariable(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
|
||||
// pass parameter via a regular variable (not via registers)
|
||||
val valueIDt = value.inferType(program)
|
||||
if(!valueIDt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
|
||||
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
|
||||
throw AssemblyError("argument type incompatible")
|
||||
if(sub.asmParameterRegisters.isEmpty()) {
|
||||
// pass parameter via a variable
|
||||
val paramVar = parameter.value
|
||||
val scopedParamVar = (sub.scopedname+"."+paramVar.name).split(".")
|
||||
val target = AssignTarget(null, IdentifierReference(scopedParamVar, sub.position), null, null, sub.position)
|
||||
target.linkParents(value.parent)
|
||||
when (value) {
|
||||
is NumericLiteralValue -> {
|
||||
// optimize when the argument is a constant literal
|
||||
when(parameter.value.type) {
|
||||
in ByteDatatypes -> asmgen.assignFromByteConstant(target, value.number.toShort())
|
||||
in WordDatatypes -> asmgen.assignFromWordConstant(target, value.number.toInt())
|
||||
DataType.FLOAT -> asmgen.assignFromFloatConstant(target, value.number.toDouble())
|
||||
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as arguments?")
|
||||
else -> throw AssemblyError("weird parameter datatype")
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
// optimize when the argument is a variable
|
||||
when (parameter.value.type) {
|
||||
in ByteDatatypes -> asmgen.assignFromByteVariable(target, value)
|
||||
in WordDatatypes -> asmgen.assignFromWordVariable(target, value)
|
||||
DataType.FLOAT -> asmgen.assignFromFloatVariable(target, value)
|
||||
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as arguments?")
|
||||
else -> throw AssemblyError("weird parameter datatype")
|
||||
}
|
||||
}
|
||||
is RegisterExpr -> {
|
||||
asmgen.assignFromRegister(target, value.register)
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
when(value.addressExpression) {
|
||||
|
||||
val varName = asmgen.asmVariableName(sub.scopedname+"."+parameter.value.name)
|
||||
asmgen.assignExpressionToVariable(value, varName, parameter.value.type, sub)
|
||||
}
|
||||
|
||||
private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
|
||||
// pass argument via a register parameter
|
||||
val valueIDt = value.inferType(program)
|
||||
if(!valueIDt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
|
||||
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
|
||||
throw AssemblyError("argument type incompatible")
|
||||
|
||||
val paramRegister = sub.asmParameterRegisters[parameter.index]
|
||||
val statusflag = paramRegister.statusflag
|
||||
val register = paramRegister.registerOrPair
|
||||
val requiredDt = parameter.value.type
|
||||
if(requiredDt!=valueDt) {
|
||||
if(valueDt largerThan requiredDt)
|
||||
throw AssemblyError("can only convert byte values to word param types")
|
||||
}
|
||||
when {
|
||||
statusflag!=null -> {
|
||||
if(requiredDt!=valueDt)
|
||||
throw AssemblyError("for statusflag, byte value is required")
|
||||
if (statusflag == Statusflag.Pc) {
|
||||
// this param needs to be set last, right before the jsr
|
||||
// for now, this is already enforced on the subroutine definition by the Ast Checker
|
||||
when(value) {
|
||||
is NumericLiteralValue -> {
|
||||
val address = (value.addressExpression as NumericLiteralValue).number.toInt()
|
||||
asmgen.assignFromMemoryByte(target, address, null)
|
||||
val carrySet = value.number.toInt() != 0
|
||||
asmgen.out(if(carrySet) " sec" else " clc")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
asmgen.assignFromMemoryByte(target, null, value.addressExpression as IdentifierReference)
|
||||
}
|
||||
else -> {
|
||||
asmgen.translateExpression(value.addressExpression)
|
||||
asmgen.out(" jsr prog8_lib.read_byte_from_address | inx")
|
||||
asmgen.assignFromRegister(target, Register.A)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
asmgen.translateExpression(value)
|
||||
asmgen.assignFromEvalResult(target)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// pass parameter via a register parameter
|
||||
val paramRegister = sub.asmParameterRegisters[parameter.index]
|
||||
val statusflag = paramRegister.statusflag
|
||||
val register = paramRegister.registerOrPair
|
||||
val stack = paramRegister.stack
|
||||
when {
|
||||
stack -> {
|
||||
// push arg onto the stack
|
||||
// note: argument order is reversed (first argument will be deepest on the stack)
|
||||
asmgen.translateExpression(value)
|
||||
}
|
||||
statusflag!=null -> {
|
||||
if (statusflag == Statusflag.Pc) {
|
||||
// this param needs to be set last, right before the jsr
|
||||
// for now, this is already enforced on the subroutine definition by the Ast Checker
|
||||
when(value) {
|
||||
is NumericLiteralValue -> {
|
||||
val carrySet = value.number.toInt() != 0
|
||||
asmgen.out(if(carrySet) " sec" else " clc")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val sourceName = asmgen.asmIdentifierName(value)
|
||||
asmgen.out("""
|
||||
val sourceName = asmgen.asmVariableName(value)
|
||||
asmgen.out("""
|
||||
pha
|
||||
lda $sourceName
|
||||
beq +
|
||||
sec
|
||||
bcs ++
|
||||
+ clc
|
||||
+
|
||||
+ pla
|
||||
""")
|
||||
}
|
||||
is RegisterExpr -> {
|
||||
when(value.register) {
|
||||
Register.A -> asmgen.out(" cmp #0")
|
||||
Register.X -> asmgen.out(" txa")
|
||||
Register.Y -> asmgen.out(" tya")
|
||||
}
|
||||
asmgen.out("""
|
||||
beq +
|
||||
sec
|
||||
bcs ++
|
||||
+ clc
|
||||
+
|
||||
""")
|
||||
}
|
||||
else -> {
|
||||
asmgen.translateExpression(value)
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda $ESTACK_LO_HEX,x
|
||||
beq +
|
||||
sec
|
||||
bcs ++
|
||||
+ clc
|
||||
+
|
||||
""")
|
||||
}
|
||||
}
|
||||
}
|
||||
else throw AssemblyError("can only use Carry as status flag parameter")
|
||||
}
|
||||
register!=null && register.name.length==1 -> {
|
||||
when (value) {
|
||||
is NumericLiteralValue -> {
|
||||
val target = AssignTarget(Register.valueOf(register.name), null, null, null, sub.position)
|
||||
target.linkParents(value.parent)
|
||||
asmgen.assignFromByteConstant(target, value.number.toShort())
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val target = AssignTarget(Register.valueOf(register.name), null, null, null, sub.position)
|
||||
target.linkParents(value.parent)
|
||||
asmgen.assignFromByteVariable(target, value)
|
||||
}
|
||||
else -> {
|
||||
asmgen.translateExpression(value)
|
||||
when(register) {
|
||||
RegisterOrPair.A -> asmgen.out(" inx | lda $ESTACK_LO_HEX,x")
|
||||
RegisterOrPair.X -> throw AssemblyError("can't pop into X register - use a variable instead")
|
||||
RegisterOrPair.Y -> asmgen.out(" inx | ldy $ESTACK_LO_HEX,x")
|
||||
else -> throw AssemblyError("cannot assign to register pair")
|
||||
}
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
|
||||
asmgen.out("""
|
||||
beq +
|
||||
sec
|
||||
bcs ++
|
||||
+ clc
|
||||
+""")
|
||||
}
|
||||
}
|
||||
}
|
||||
register!=null && register.name.length==2 -> {
|
||||
// register pair as a 16-bit value (only possible for subroutine parameters)
|
||||
when (value) {
|
||||
is NumericLiteralValue -> {
|
||||
// optimize when the argument is a constant literal
|
||||
val hex = value.number.toHex()
|
||||
when (register) {
|
||||
RegisterOrPair.AX -> asmgen.out(" lda #<$hex | ldx #>$hex")
|
||||
RegisterOrPair.AY -> asmgen.out(" lda #<$hex | ldy #>$hex")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldx #<$hex | ldy #>$hex")
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
is AddressOf -> {
|
||||
// optimize when the argument is an address of something
|
||||
val sourceName = asmgen.asmIdentifierName(value.identifier)
|
||||
when (register) {
|
||||
RegisterOrPair.AX -> asmgen.out(" lda #<$sourceName | ldx #>$sourceName")
|
||||
RegisterOrPair.AY -> asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldx #<$sourceName | ldy #>$sourceName")
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val sourceName = asmgen.asmIdentifierName(value)
|
||||
when (register) {
|
||||
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx $sourceName+1")
|
||||
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy $sourceName+1")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy $sourceName+1")
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
asmgen.translateExpression(value)
|
||||
if (register == RegisterOrPair.AX || register == RegisterOrPair.XY)
|
||||
throw AssemblyError("can't use X register here - use a variable")
|
||||
else if (register == RegisterOrPair.AY)
|
||||
asmgen.out(" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x")
|
||||
else throw AssemblyError("can only use Carry as status flag parameter")
|
||||
}
|
||||
else -> {
|
||||
// via register or register pair
|
||||
register!!
|
||||
if(requiredDt largerThan valueDt) {
|
||||
// we need to sign extend the source, do this via temporary word variable
|
||||
val scratchVar = asmgen.asmVariableName("P8ZP_SCRATCH_W1")
|
||||
asmgen.assignExpressionToVariable(value, scratchVar, DataType.UBYTE, sub)
|
||||
asmgen.signExtendVariableLsb(scratchVar, valueDt)
|
||||
asmgen.assignVariableToRegister(scratchVar, register)
|
||||
}
|
||||
else {
|
||||
val target = AsmAssignTarget.fromRegisters(register, sub, program, asmgen)
|
||||
val src = if(valueDt in PassByReferenceDatatypes) {
|
||||
if(value is IdentifierReference) {
|
||||
val addr = AddressOf(value, Position.DUMMY)
|
||||
AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target)
|
||||
} else {
|
||||
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
|
||||
}
|
||||
} else {
|
||||
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
|
||||
}
|
||||
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, Position.DUMMY))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun argumentTypeCompatible(argType: DataType, paramType: DataType): Boolean {
|
||||
private fun isArgumentTypeCompatible(argType: DataType, paramType: DataType): Boolean {
|
||||
if(argType isAssignableTo paramType)
|
||||
return true
|
||||
if(argType in ByteDatatypes && paramType in ByteDatatypes)
|
||||
return true
|
||||
if(argType in WordDatatypes && paramType in WordDatatypes)
|
||||
return true
|
||||
|
||||
// we have a special rule for some types.
|
||||
// strings are assignable to UWORD, for example, and vice versa
|
||||
|
@ -4,10 +4,8 @@ import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.RegisterExpr
|
||||
import prog8.ast.statements.PostIncrDecr
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
|
||||
import prog8.compiler.toHex
|
||||
|
||||
|
||||
@ -17,28 +15,11 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
val targetIdent = stmt.target.identifier
|
||||
val targetMemory = stmt.target.memoryAddress
|
||||
val targetArrayIdx = stmt.target.arrayindexed
|
||||
val targetRegister = stmt.target.register
|
||||
val scope = stmt.definingSubroutine()
|
||||
when {
|
||||
targetRegister!=null -> {
|
||||
when(targetRegister) {
|
||||
Register.A -> {
|
||||
if(incr)
|
||||
asmgen.out(" clc | adc #1 ")
|
||||
else
|
||||
asmgen.out(" sec | sbc #1 ")
|
||||
}
|
||||
Register.X -> {
|
||||
if(incr) asmgen.out(" inx") else asmgen.out(" dex")
|
||||
}
|
||||
Register.Y -> {
|
||||
if(incr) asmgen.out(" iny") else asmgen.out(" dey")
|
||||
}
|
||||
}
|
||||
}
|
||||
targetIdent!=null -> {
|
||||
val what = asmgen.asmIdentifierName(targetIdent)
|
||||
val dt = stmt.target.inferType(program, stmt).typeOrElse(DataType.STRUCT)
|
||||
when (dt) {
|
||||
val what = asmgen.asmVariableName(targetIdent)
|
||||
when (stmt.target.inferType(program).typeOrElse(DataType.STRUCT)) {
|
||||
in ByteDatatypes -> asmgen.out(if (incr) " inc $what" else " dec $what")
|
||||
in WordDatatypes -> {
|
||||
if(incr)
|
||||
@ -53,7 +34,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out(" lda #<$what | ldy #>$what")
|
||||
asmgen.out(if(incr) " jsr c64flt.inc_var_f" else " jsr c64flt.dec_var_f")
|
||||
asmgen.out(if(incr) " jsr floats.inc_var_f" else " jsr floats.dec_var_f")
|
||||
}
|
||||
else -> throw AssemblyError("need numeric type")
|
||||
}
|
||||
@ -65,90 +46,82 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
asmgen.out(if(incr) " inc $what" else " dec $what")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val what = asmgen.asmIdentifierName(addressExpr)
|
||||
val what = asmgen.asmVariableName(addressExpr)
|
||||
asmgen.out(" lda $what | sta (+) +1 | lda $what+1 | sta (+) +2")
|
||||
if(incr)
|
||||
asmgen.out("+\tinc ${'$'}ffff\t; modified")
|
||||
else
|
||||
asmgen.out("+\tdec ${'$'}ffff\t; modified")
|
||||
}
|
||||
else -> throw AssemblyError("weird target type $targetMemory")
|
||||
else -> {
|
||||
asmgen.assignExpressionToRegister(addressExpr, RegisterOrPair.AY)
|
||||
asmgen.out(" sta (+) + 1 | sty (+) + 2")
|
||||
if(incr)
|
||||
asmgen.out("+\tinc ${'$'}ffff\t; modified")
|
||||
else
|
||||
asmgen.out("+\tdec ${'$'}ffff\t; modified")
|
||||
}
|
||||
}
|
||||
}
|
||||
targetArrayIdx!=null -> {
|
||||
val index = targetArrayIdx.arrayspec.index
|
||||
val what = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||
val arrayDt = targetArrayIdx.identifier.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
val elementDt = ArrayElementTypes.getValue(arrayDt)
|
||||
when(index) {
|
||||
is NumericLiteralValue -> {
|
||||
val indexValue = index.number.toInt() * elementDt.memorySize()
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> asmgen.out(if (incr) " inc $what+$indexValue" else " dec $what+$indexValue")
|
||||
in WordDatatypes -> {
|
||||
if(incr)
|
||||
asmgen.out(" inc $what+$indexValue | bne + | inc $what+$indexValue+1 |+")
|
||||
else
|
||||
asmgen.out("""
|
||||
lda $what+$indexValue
|
||||
bne +
|
||||
dec $what+$indexValue+1
|
||||
+ dec $what+$indexValue
|
||||
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.arrayvar)
|
||||
val elementDt = targetArrayIdx.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
if(targetArrayIdx.indexer.indexNum!=null) {
|
||||
val indexValue = targetArrayIdx.indexer.constIndex()!! * elementDt.memorySize()
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue")
|
||||
in WordDatatypes -> {
|
||||
if(incr)
|
||||
asmgen.out(" inc $asmArrayvarname+$indexValue | bne + | inc $asmArrayvarname+$indexValue+1 |+")
|
||||
else
|
||||
asmgen.out("""
|
||||
lda $asmArrayvarname+$indexValue
|
||||
bne +
|
||||
dec $asmArrayvarname+$indexValue+1
|
||||
+ dec $asmArrayvarname+$indexValue
|
||||
""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out(" lda #<$what+$indexValue | ldy #>$what+$indexValue")
|
||||
asmgen.out(if(incr) " jsr c64flt.inc_var_f" else " jsr c64flt.dec_var_f")
|
||||
}
|
||||
else -> throw AssemblyError("need numeric type")
|
||||
}
|
||||
}
|
||||
is RegisterExpr -> {
|
||||
// TODO optimize common cases
|
||||
asmgen.translateArrayIndexIntoA(targetArrayIdx)
|
||||
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
// TODO optimize common cases
|
||||
asmgen.translateArrayIndexIntoA(targetArrayIdx)
|
||||
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
|
||||
}
|
||||
else -> {
|
||||
// TODO optimize common cases
|
||||
asmgen.translateArrayIndexIntoA(targetArrayIdx)
|
||||
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out(" lda #<$asmArrayvarname+$indexValue | ldy #>$asmArrayvarname+$indexValue")
|
||||
asmgen.out(if(incr) " jsr floats.inc_var_f" else " jsr floats.dec_var_f")
|
||||
}
|
||||
else -> throw AssemblyError("need numeric type")
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird target type ${stmt.target}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun incrDecrArrayvalueWithIndexA(incr: Boolean, arrayDt: DataType, arrayVarName: String) {
|
||||
asmgen.out(" stx ${C64Zeropage.SCRATCH_REG_X} | tax")
|
||||
when(arrayDt) {
|
||||
DataType.STR,
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||
asmgen.out(if(incr) " inc $arrayVarName,x" else " dec $arrayVarName,x")
|
||||
}
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
if(incr)
|
||||
asmgen.out(" inc $arrayVarName,x | bne + | inc $arrayVarName+1,x |+")
|
||||
else
|
||||
asmgen.out("""
|
||||
lda $arrayVarName,x
|
||||
bne +
|
||||
dec $arrayVarName+1,x
|
||||
+ dec $arrayVarName
|
||||
{
|
||||
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.A)
|
||||
asmgen.saveRegister(CpuRegister.X, false, scope!!)
|
||||
asmgen.out(" tax")
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.out(if(incr) " inc $asmArrayvarname,x" else " dec $asmArrayvarname,x")
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
if(incr)
|
||||
asmgen.out(" inc $asmArrayvarname,x | bne + | inc $asmArrayvarname+1,x |+")
|
||||
else
|
||||
asmgen.out("""
|
||||
lda $asmArrayvarname,x
|
||||
bne +
|
||||
dec $asmArrayvarname+1,x
|
||||
+ dec $asmArrayvarname
|
||||
""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out("""
|
||||
ldy #>$asmArrayvarname
|
||||
clc
|
||||
adc #<$asmArrayvarname
|
||||
bcc +
|
||||
iny
|
||||
+ jsr floats.inc_var_f""")
|
||||
}
|
||||
else -> throw AssemblyError("weird array elt dt")
|
||||
}
|
||||
asmgen.restoreRegister(CpuRegister.X, false)
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
asmgen.out(" lda #<$arrayVarName | ldy #>$arrayVarName")
|
||||
asmgen.out(if(incr) " jsr c64flt.inc_indexed_var_f" else " jsr c64flt.dec_indexed_var_f")
|
||||
}
|
||||
else -> throw AssemblyError("weird array dt")
|
||||
}
|
||||
asmgen.out(" ldx ${C64Zeropage.SCRATCH_REG_X}")
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,194 @@
|
||||
package prog8.compiler.target.c64.codegen.assignment
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.c64.codegen.AsmGen
|
||||
|
||||
|
||||
internal enum class TargetStorageKind {
|
||||
VARIABLE,
|
||||
ARRAY,
|
||||
MEMORY,
|
||||
REGISTER,
|
||||
STACK
|
||||
}
|
||||
|
||||
internal enum class SourceStorageKind {
|
||||
LITERALNUMBER,
|
||||
VARIABLE,
|
||||
ARRAY,
|
||||
MEMORY,
|
||||
REGISTER,
|
||||
STACK, // value is already present on stack
|
||||
EXPRESSION, // expression in ast-form, still to be evaluated
|
||||
}
|
||||
|
||||
internal class AsmAssignTarget(val kind: TargetStorageKind,
|
||||
private val program: Program,
|
||||
private val asmgen: AsmGen,
|
||||
val datatype: DataType,
|
||||
val scope: Subroutine?,
|
||||
private val variableAsmName: String? = null,
|
||||
val array: ArrayIndexedExpression? = null,
|
||||
val memory: DirectMemoryWrite? = null,
|
||||
val register: RegisterOrPair? = null,
|
||||
val origAstTarget: AssignTarget? = null
|
||||
)
|
||||
{
|
||||
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0}
|
||||
val constArrayIndexValue by lazy { array?.indexer?.constIndex() }
|
||||
val asmVarname: String
|
||||
get() = if(array==null)
|
||||
variableAsmName!!
|
||||
else
|
||||
asmgen.asmVariableName(array.arrayvar)
|
||||
|
||||
lateinit var origAssign: AsmAssignment
|
||||
|
||||
init {
|
||||
if(register!=null && datatype !in NumericDatatypes)
|
||||
throw AssemblyError("register must be integer or float type")
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromAstAssignment(assign: Assignment, program: Program, asmgen: AsmGen): AsmAssignTarget = with(assign.target) {
|
||||
val idt = inferType(program)
|
||||
if(!idt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val dt = idt.typeOrElse(DataType.STRUCT)
|
||||
when {
|
||||
identifier != null -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, assign.definingSubroutine(), variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
|
||||
arrayindexed != null -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, assign.definingSubroutine(), array = arrayindexed, origAstTarget = this)
|
||||
memoryAddress != null -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, dt, assign.definingSubroutine(), memory = memoryAddress, origAstTarget = this)
|
||||
else -> throw AssemblyError("weird target")
|
||||
}
|
||||
}
|
||||
|
||||
fun fromRegisters(registers: RegisterOrPair, scope: Subroutine?, program: Program, asmgen: AsmGen): AsmAssignTarget =
|
||||
when(registers) {
|
||||
RegisterOrPair.A,
|
||||
RegisterOrPair.X,
|
||||
RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UBYTE, scope, register = registers)
|
||||
RegisterOrPair.AX,
|
||||
RegisterOrPair.AY,
|
||||
RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UWORD, scope, register = registers)
|
||||
RegisterOrPair.FAC1,
|
||||
RegisterOrPair.FAC2 -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.FLOAT, scope, register = registers)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
private val program: Program,
|
||||
private val asmgen: AsmGen,
|
||||
val datatype: DataType,
|
||||
private val variableAsmName: String? = null,
|
||||
val array: ArrayIndexedExpression? = null,
|
||||
val memory: DirectMemoryRead? = null,
|
||||
val register: CpuRegister? = null,
|
||||
val number: NumericLiteralValue? = null,
|
||||
val expression: Expression? = null
|
||||
)
|
||||
{
|
||||
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0}
|
||||
val constArrayIndexValue by lazy { array?.indexer?.constIndex() }
|
||||
|
||||
val asmVarname: String
|
||||
get() = if(array==null)
|
||||
variableAsmName!!
|
||||
else
|
||||
asmgen.asmVariableName(array.arrayvar)
|
||||
|
||||
companion object {
|
||||
fun fromAstSource(indexer: ArrayIndex, program: Program, asmgen: AsmGen): AsmAssignSource {
|
||||
return when {
|
||||
indexer.indexNum!=null -> fromAstSource(indexer.indexNum!!, program, asmgen)
|
||||
indexer.indexVar!=null -> fromAstSource(indexer.indexVar!!, program, asmgen)
|
||||
else -> throw AssemblyError("weird indexer")
|
||||
}
|
||||
}
|
||||
|
||||
fun fromAstSource(value: Expression, program: Program, asmgen: AsmGen): AsmAssignSource {
|
||||
val cv = value.constValue(program)
|
||||
if(cv!=null)
|
||||
return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, cv.type, number = cv)
|
||||
|
||||
return when(value) {
|
||||
is NumericLiteralValue -> AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, value.type, number = cv)
|
||||
is StringLiteralValue -> throw AssemblyError("string literal value should not occur anymore for asm generation")
|
||||
is ArrayLiteralValue -> throw AssemblyError("array literal value should not occur anymore for asm generation")
|
||||
is IdentifierReference -> {
|
||||
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, dt, variableAsmName = asmgen.asmVariableName(value))
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
AsmAssignSource(SourceStorageKind.MEMORY, program, asmgen, DataType.UBYTE, memory = value)
|
||||
}
|
||||
is ArrayIndexedExpression -> {
|
||||
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value)
|
||||
}
|
||||
is FunctionCall -> {
|
||||
when (val sub = value.target.targetStatement(program.namespace)) {
|
||||
is Subroutine -> {
|
||||
val returnType = sub.returntypes.zip(sub.asmReturnvaluesRegisters).firstOrNull { rr -> rr.second.registerOrPair != null }?.first
|
||||
?: throw AssemblyError("can't translate zero return values in assignment")
|
||||
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)
|
||||
}
|
||||
is BuiltinFunctionStatementPlaceholder -> {
|
||||
val returnType = value.inferType(program)
|
||||
if(!returnType.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.typeOrElse(DataType.STRUCT), expression = value)
|
||||
}
|
||||
else -> {
|
||||
throw AssemblyError("weird call")
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
val dt = value.inferType(program)
|
||||
if(!dt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt.typeOrElse(DataType.STRUCT), expression = value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun adjustSignedUnsigned(target: AsmAssignTarget): AsmAssignSource {
|
||||
// allow some signed/unsigned relaxations
|
||||
|
||||
fun withAdjustedDt(newType: DataType) =
|
||||
AsmAssignSource(kind, program, asmgen, newType, variableAsmName, array, memory, register, number, expression)
|
||||
|
||||
if(target.datatype!=datatype) {
|
||||
if(target.datatype in ByteDatatypes && datatype in ByteDatatypes) {
|
||||
return withAdjustedDt(target.datatype)
|
||||
} else if(target.datatype in WordDatatypes && datatype in WordDatatypes) {
|
||||
return withAdjustedDt(target.datatype)
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
internal class AsmAssignment(val source: AsmAssignSource,
|
||||
val target: AsmAssignTarget,
|
||||
val isAugmentable: Boolean,
|
||||
val position: Position) {
|
||||
|
||||
init {
|
||||
if(target.register !in setOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY))
|
||||
require(source.datatype != DataType.STRUCT) { "must not be placeholder datatype" }
|
||||
require(source.datatype.memorySize() <= target.datatype.memorySize()) {
|
||||
"source storage size must be less or equal to target datatype storage size"
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
118
compiler/src/prog8/compiler/target/cx16/CX16MachineDefinition.kt
Normal file
118
compiler/src/prog8/compiler/target/cx16/CX16MachineDefinition.kt
Normal file
@ -0,0 +1,118 @@
|
||||
package prog8.compiler.target.cx16
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.compiler.*
|
||||
import prog8.compiler.target.CpuType
|
||||
import prog8.compiler.target.IMachineDefinition
|
||||
import prog8.compiler.target.c64.C64MachineDefinition
|
||||
import prog8.parser.ModuleImporter
|
||||
import java.io.IOException
|
||||
|
||||
internal object CX16MachineDefinition: IMachineDefinition {
|
||||
|
||||
override val cpu = CpuType.CPU65c02
|
||||
|
||||
// 5-byte cbm MFLPT format limitations:
|
||||
override val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255
|
||||
override val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
|
||||
override val FLOAT_MEM_SIZE = 5
|
||||
override val POINTER_MEM_SIZE = 2
|
||||
override val BASIC_LOAD_ADDRESS = 0x0801
|
||||
override val RAW_LOAD_ADDRESS = 0x8000
|
||||
|
||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
||||
// and some heavily used string constants derived from the two values above
|
||||
override val ESTACK_LO = 0x0400 // $0400-$04ff inclusive
|
||||
override val ESTACK_HI = 0x0500 // $0500-$05ff inclusive
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
|
||||
override fun getFloat(num: Number) = C64MachineDefinition.Mflpt5.fromNumber(num)
|
||||
|
||||
override fun getFloatRomConst(number: Double): String? = null // Cx16 has no pulblic ROM float locations
|
||||
override fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program) {
|
||||
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||
importer.importLibraryModule(program, "syslib")
|
||||
}
|
||||
|
||||
override fun launchEmulator(programName: String) {
|
||||
for(emulator in listOf("x16emu")) {
|
||||
println("\nStarting Commander X16 emulator $emulator...")
|
||||
val cmdline = listOf(emulator, "-rom", "/usr/share/x16-rom/rom.bin", "-scale", "2",
|
||||
"-run", "-prg", programName + ".prg")
|
||||
val processb = ProcessBuilder(cmdline).inheritIO()
|
||||
val process: Process
|
||||
try {
|
||||
process=processb.start()
|
||||
} catch(x: IOException) {
|
||||
continue // try the next emulator executable
|
||||
}
|
||||
process.waitFor()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
override fun isRegularRAMaddress(address: Int): Boolean = address < 0x9f00 || address in 0xa000..0xbfff
|
||||
|
||||
override fun initializeZeropage(compilerOptions: CompilationOptions) {
|
||||
zeropage = CX16Zeropage(compilerOptions)
|
||||
}
|
||||
|
||||
// 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names
|
||||
override val opcodeNames = setOf("adc", "and", "asl", "bcc", "bcs",
|
||||
"beq", "bge", "bit", "blt", "bmi", "bne", "bpl", "brk", "bvc", "bvs", "clc",
|
||||
"cld", "cli", "clv", "cmp", "cpx", "cpy", "dec", "dex", "dey",
|
||||
"eor", "gcc", "gcs", "geq", "gge", "glt", "gmi", "gne", "gpl", "gvc", "gvs",
|
||||
"inc", "inx", "iny", "jmp", "jsr",
|
||||
"lda", "ldx", "ldy", "lsr", "nop", "ora", "pha", "php",
|
||||
"pla", "plp", "rol", "ror", "rti", "rts", "sbc",
|
||||
"sec", "sed", "sei",
|
||||
"sta", "stx", "sty", "tax", "tay", "tsx", "txa", "txs", "tya",
|
||||
"bra", "phx", "phy", "plx", "ply", "stz", "trb", "tsb", "bbr", "bbs",
|
||||
"rmb", "smb", "stp", "wai")
|
||||
|
||||
|
||||
internal class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
|
||||
override val SCRATCH_B1 = 0x79 // temp storage for a single byte
|
||||
override val SCRATCH_REG = 0x7a // temp storage for a register, must be B1+1
|
||||
override val SCRATCH_W1 = 0x7c // temp storage 1 for a word $7c+$7d
|
||||
override val SCRATCH_W2 = 0x7e // temp storage 2 for a word $7e+$7f
|
||||
|
||||
|
||||
init {
|
||||
if (options.floats && options.zeropage !in setOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
|
||||
throw CompilerException("when floats are enabled, zero page type should be 'basicsafe' or 'dontuse'")
|
||||
|
||||
// the addresses 0x02 to 0x21 (inclusive) are taken for sixteen virtual 16-bit api registers.
|
||||
|
||||
when (options.zeropage) {
|
||||
ZeropageType.FULL -> {
|
||||
free.addAll(0x22..0xff)
|
||||
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
|
||||
}
|
||||
ZeropageType.KERNALSAFE -> {
|
||||
free.addAll(0x22..0x7f)
|
||||
free.addAll(0xa9..0xff)
|
||||
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
|
||||
}
|
||||
ZeropageType.BASICSAFE -> {
|
||||
free.addAll(0x22..0x7f)
|
||||
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
|
||||
}
|
||||
ZeropageType.DONTUSE -> {
|
||||
free.clear() // don't use zeropage at all
|
||||
}
|
||||
else -> throw CompilerException("for this machine target, zero page type 'floatsafe' is not available. ${options.zeropage}")
|
||||
}
|
||||
|
||||
require(SCRATCH_B1 !in free)
|
||||
require(SCRATCH_REG !in free)
|
||||
require(SCRATCH_W1 !in free)
|
||||
require(SCRATCH_W2 !in free)
|
||||
|
||||
for (reserved in options.zpReserved)
|
||||
reserve(reserved)
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,8 @@ package prog8.functions
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.StructDecl
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.compiler.CompilerException
|
||||
import kotlin.math.*
|
||||
|
||||
@ -13,86 +15,168 @@ class FParam(val name: String, val possibleDatatypes: Set<DataType>)
|
||||
typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program) -> NumericLiteralValue
|
||||
|
||||
|
||||
class FSignature(val pure: Boolean, // does it have side effects?
|
||||
class ReturnConvention(val dt: DataType, val reg: RegisterOrPair?, val floatFac1: Boolean)
|
||||
class ParamConvention(val dt: DataType, val reg: RegisterOrPair?, val variable: Boolean)
|
||||
class CallConvention(val params: List<ParamConvention>, val returns: ReturnConvention) {
|
||||
override fun toString(): String {
|
||||
val paramConvs = params.mapIndexed { index, it ->
|
||||
when {
|
||||
it.reg!=null -> "$index:${it.reg}"
|
||||
it.variable -> "$index:variable"
|
||||
else -> "$index:???"
|
||||
}
|
||||
}
|
||||
val returnConv =
|
||||
when {
|
||||
returns.reg!=null -> returns.reg.toString()
|
||||
returns.floatFac1 -> "floatFAC1"
|
||||
else -> "<no returnvalue>"
|
||||
}
|
||||
return "CallConvention[" + paramConvs.joinToString() + " ; returns: $returnConv]"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class FSignature(val name: String,
|
||||
val pure: Boolean, // does it have side effects?
|
||||
val parameters: List<FParam>,
|
||||
val returntype: DataType?,
|
||||
val constExpressionFunc: ConstExpressionCaller? = null)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val BuiltinFunctions = mapOf(
|
||||
private val functionSignatures: List<FSignature> = listOf(
|
||||
// this set of function have no return value and operate in-place:
|
||||
"rol" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"ror" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"rol2" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"ror2" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"lsl" to FSignature(false, listOf(FParam("item", IntegerDatatypes)), null),
|
||||
"lsr" to FSignature(false, listOf(FParam("item", IntegerDatatypes)), null),
|
||||
"sort" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||
"reverse" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||
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),
|
||||
// these few have a return value depending on the argument(s):
|
||||
"max" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args
|
||||
"min" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args
|
||||
"sum" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
|
||||
"abs" to FSignature(true, listOf(FParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
|
||||
"len" to FSignature(true, listOf(FParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
|
||||
FSignature("max" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args
|
||||
FSignature("min" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args
|
||||
FSignature("sum" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> 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:
|
||||
"sgn" to FSignature(true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
|
||||
"sin" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) },
|
||||
"sin8" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
|
||||
"sin8u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
|
||||
"sin16" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
|
||||
"sin16u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
|
||||
"cos" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::cos) },
|
||||
"cos8" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
|
||||
"cos8u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
|
||||
"cos16" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
|
||||
"cos16u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
|
||||
"tan" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::tan) },
|
||||
"atan" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::atan) },
|
||||
"ln" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::log) },
|
||||
"log2" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, ::log2) },
|
||||
"sqrt16" to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
|
||||
"sqrt" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
|
||||
"rad" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) },
|
||||
"deg" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
|
||||
"round" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
|
||||
"floor" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
|
||||
"ceil" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
|
||||
"any" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) },
|
||||
"all" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
|
||||
"lsb" to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 }},
|
||||
"msb" to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255}},
|
||||
"mkword" to FSignature(true, listOf(FParam("lsb", setOf(DataType.UBYTE)), FParam("msb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
|
||||
"rnd" to FSignature(true, emptyList(), DataType.UBYTE),
|
||||
"rndw" to FSignature(true, emptyList(), DataType.UWORD),
|
||||
"rndf" to FSignature(true, emptyList(), DataType.FLOAT),
|
||||
"exit" to FSignature(false, listOf(FParam("returnvalue", setOf(DataType.UBYTE))), null),
|
||||
"rsave" to FSignature(false, emptyList(), null),
|
||||
"rrestore" to FSignature(false, emptyList(), null),
|
||||
"set_carry" to FSignature(false, emptyList(), null),
|
||||
"clear_carry" to FSignature(false, emptyList(), null),
|
||||
"set_irqd" to FSignature(false, emptyList(), null),
|
||||
"clear_irqd" to FSignature(false, emptyList(), null),
|
||||
"read_flags" to FSignature(false, emptyList(), DataType.UBYTE),
|
||||
"swap" to FSignature(false, listOf(FParam("first", NumericDatatypes), FParam("second", NumericDatatypes)), null),
|
||||
"memcopy" to FSignature(false, listOf(
|
||||
FSignature("sgn" , true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
|
||||
FSignature("sin" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> 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 -> 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 -> oneDoubleArg(a, p, prg, Math::tan) },
|
||||
FSignature("atan" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::atan) },
|
||||
FSignature("ln" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::log) },
|
||||
FSignature("log2" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, ::log2) },
|
||||
FSignature("sqrt16" , true, listOf(FParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
|
||||
FSignature("sqrt" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
|
||||
FSignature("rad" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) },
|
||||
FSignature("deg" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
|
||||
FSignature("round" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
|
||||
FSignature("floor" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
|
||||
FSignature("ceil" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
|
||||
FSignature("any" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) },
|
||||
FSignature("all" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
|
||||
FSignature("lsb" , true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> 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 -> 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("rnd" , false, emptyList(), DataType.UBYTE),
|
||||
FSignature("rndw" , false, emptyList(), DataType.UWORD),
|
||||
FSignature("rndf" , false, emptyList(), DataType.FLOAT),
|
||||
FSignature("exit" , false, listOf(FParam("returnvalue", setOf(DataType.UBYTE))), null),
|
||||
FSignature("rsave" , false, emptyList(), null),
|
||||
FSignature("rrestore" , false, emptyList(), null),
|
||||
FSignature("set_carry" , false, emptyList(), null),
|
||||
FSignature("clear_carry" , false, emptyList(), null),
|
||||
FSignature("set_irqd" , false, emptyList(), null),
|
||||
FSignature("clear_irqd" , false, emptyList(), null),
|
||||
FSignature("read_flags" , false, emptyList(), DataType.UBYTE),
|
||||
FSignature("swap" , false, listOf(FParam("first", NumericDatatypes), FParam("second", NumericDatatypes)), null),
|
||||
FSignature("memcopy" , false, listOf(
|
||||
FParam("from", IterableDatatypes + DataType.UWORD),
|
||||
FParam("to", IterableDatatypes + DataType.UWORD),
|
||||
FParam("numbytes", setOf(DataType.UBYTE))), null),
|
||||
"memset" to FSignature(false, listOf(
|
||||
FParam("numbytes", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
FSignature("memset" , false, listOf(
|
||||
FParam("address", IterableDatatypes + DataType.UWORD),
|
||||
FParam("numbytes", setOf(DataType.UWORD)),
|
||||
FParam("bytevalue", ByteDatatypes)), null),
|
||||
"memsetw" to FSignature(false, listOf(
|
||||
FSignature("memsetw" , false, listOf(
|
||||
FParam("address", IterableDatatypes + DataType.UWORD),
|
||||
FParam("numwords", setOf(DataType.UWORD)),
|
||||
FParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null),
|
||||
"strlen" to FSignature(true, listOf(FParam("string", setOf(DataType.STR))), DataType.UBYTE, ::builtinStrlen)
|
||||
FSignature("strlen" , true, listOf(FParam("string", setOf(DataType.STR))), DataType.UBYTE, ::builtinStrlen),
|
||||
FSignature("substr" , false, listOf(
|
||||
FParam("source", IterableDatatypes + DataType.UWORD),
|
||||
FParam("target", IterableDatatypes + DataType.UWORD),
|
||||
FParam("start", setOf(DataType.UBYTE)),
|
||||
FParam("length", setOf(DataType.UBYTE))), null),
|
||||
FSignature("leftstr" , false, listOf(
|
||||
FParam("source", IterableDatatypes + DataType.UWORD),
|
||||
FParam("target", IterableDatatypes + DataType.UWORD),
|
||||
FParam("length", setOf(DataType.UBYTE))), null),
|
||||
FSignature("rightstr" , false, listOf(
|
||||
FParam("source", IterableDatatypes + DataType.UWORD),
|
||||
FParam("target", IterableDatatypes + DataType.UWORD),
|
||||
FParam("length", setOf(DataType.UBYTE))), null),
|
||||
FSignature("strcmp" , false, listOf(FParam("s1", IterableDatatypes + DataType.UWORD), FParam("s2", IterableDatatypes + DataType.UWORD)), DataType.BYTE, null)
|
||||
)
|
||||
|
||||
fun builtinMax(array: List<Number>): Number = array.maxBy { it.toDouble() }!!
|
||||
val BuiltinFunctions = functionSignatures.associateBy { it.name }
|
||||
|
||||
fun builtinMin(array: List<Number>): Number = array.minBy { it.toDouble() }!!
|
||||
|
||||
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.sumByDouble { it.toDouble() }
|
||||
|
||||
@ -129,17 +213,17 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
|
||||
}
|
||||
|
||||
val func = BuiltinFunctions.getValue(function)
|
||||
if(func.returntype!=null)
|
||||
return InferredTypes.knownFor(func.returntype)
|
||||
// function has return values, but the return type depends on the arguments
|
||||
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)
|
||||
if(dt.typeOrElse(DataType.STRUCT) in NumericDatatypes)
|
||||
return dt
|
||||
return if(dt.typeOrElse(DataType.STRUCT) in NumericDatatypes)
|
||||
dt
|
||||
else
|
||||
throw FatalAstException("weird datatype passed to abs $dt")
|
||||
InferredTypes.InferredType.unknown()
|
||||
}
|
||||
"max", "min" -> {
|
||||
when(val dt = datatypeFromIterableArg(args.single())) {
|
||||
@ -172,6 +256,7 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
|
||||
|
||||
|
||||
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 {
|
||||
@ -226,54 +311,88 @@ private fun builtinAbs(args: List<Expression>, position: Position, program: Prog
|
||||
}
|
||||
}
|
||||
|
||||
private fun builtinSizeof(args: List<Expression>, position: Position, program: Program): 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.namespace)
|
||||
?: throw CannotEvaluateException("sizeof", "no target")
|
||||
|
||||
fun structSize(target: StructDecl) =
|
||||
NumericLiteralValue(DataType.UBYTE, target.statements.map { (it as VarDecl).datatype.memorySize() }.sum(), position)
|
||||
|
||||
return when {
|
||||
dt.typeOrElse(DataType.STRUCT) in ArrayDatatypes -> {
|
||||
val length = (target as VarDecl).arraysize!!.constIndex() ?: throw CannotEvaluateException("sizeof", "unknown array size")
|
||||
val elementDt = ArrayElementTypes.getValue(dt.typeOrElse(DataType.STRUCT))
|
||||
numericLiteral(elementDt.memorySize() * length, position)
|
||||
}
|
||||
dt.istype(DataType.STRUCT) -> {
|
||||
when (target) {
|
||||
is VarDecl -> structSize(target.struct!!)
|
||||
is StructDecl -> structSize(target)
|
||||
else -> throw CompilerException("weird struct type $target")
|
||||
}
|
||||
}
|
||||
dt.istype(DataType.STR) -> throw SyntaxError("sizeof str is undefined, did you mean len?", position)
|
||||
else -> NumericLiteralValue(DataType.UBYTE, dt.typeOrElse(DataType.STRUCT).memorySize(), position)
|
||||
}
|
||||
} else {
|
||||
throw SyntaxError("sizeof invalid argument type", position)
|
||||
}
|
||||
}
|
||||
|
||||
private fun builtinStrlen(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||
if (args.size != 1)
|
||||
throw SyntaxError("strlen requires one argument", position)
|
||||
val argument = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
if(argument.type != DataType.STR)
|
||||
throw SyntaxError("strlen must have string argument", position)
|
||||
|
||||
throw NotConstArgumentException() // this function is not considering the string argument a constant
|
||||
val argument=args[0]
|
||||
if(argument is StringLiteralValue)
|
||||
return NumericLiteralValue.optimalInteger(argument.value.length, argument.position)
|
||||
val vardecl = (argument as IdentifierReference).targetVarDecl(program.namespace)
|
||||
if(vardecl!=null) {
|
||||
if(vardecl.datatype!=DataType.STR && vardecl.datatype!=DataType.UWORD)
|
||||
throw SyntaxError("strlen must have string argument", position)
|
||||
if(vardecl.autogeneratedDontRemove && vardecl.value!=null) {
|
||||
return NumericLiteralValue.optimalInteger((vardecl.value as StringLiteralValue).value.length, argument.position)
|
||||
}
|
||||
}
|
||||
throw NotConstArgumentException()
|
||||
}
|
||||
|
||||
private fun builtinLen(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||
// note: in some cases the length is > 255 and then we have to return a UWORD type instead of a UBYTE.
|
||||
if(args.size!=1)
|
||||
throw SyntaxError("len requires one argument", position)
|
||||
val constArg = args[0].constValue(program)
|
||||
if(constArg!=null)
|
||||
throw SyntaxError("len of weird argument ${args[0]}", position)
|
||||
|
||||
val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program.namespace)
|
||||
var arraySize = directMemVar?.arraysize?.size()
|
||||
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, but is ${args[0]}", position)
|
||||
val target = (args[0] as IdentifierReference).targetVarDecl(program.namespace)!!
|
||||
throw SyntaxError("len argument should be an identifier", position)
|
||||
val target = (args[0] as IdentifierReference).targetVarDecl(program.namespace)
|
||||
?: throw CannotEvaluateException("len", "no target vardecl")
|
||||
|
||||
return when(target.datatype) {
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
arraySize = target.arraysize!!.size()!!
|
||||
if(arraySize>256)
|
||||
throw CompilerException("array length exceeds byte limit ${target.position}")
|
||||
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
arraySize = target.arraysize!!.size()!!
|
||||
if(arraySize>256)
|
||||
throw CompilerException("array length exceeds byte limit ${target.position}")
|
||||
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
|
||||
if(refLv.value.length>255)
|
||||
throw CompilerException("string length exceeds byte limit ${refLv.position}")
|
||||
NumericLiteralValue.optimalInteger(refLv.value.length, args[0].position)
|
||||
}
|
||||
in NumericDatatypes -> throw SyntaxError("len of weird argument ${args[0]}", position)
|
||||
DataType.STRUCT -> throw SyntaxError("cannot use len on struct, did you mean sizeof?", args[0].position)
|
||||
in NumericDatatypes -> throw SyntaxError("cannot use len on numeric value, did you mean sizeof?", args[0].position)
|
||||
else -> throw CompilerException("weird datatype")
|
||||
}
|
||||
}
|
||||
@ -281,9 +400,9 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
|
||||
|
||||
private fun builtinMkword(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||
if (args.size != 2)
|
||||
throw SyntaxError("mkword requires lsb and msb arguments", position)
|
||||
val constLsb = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
val constMsb = args[1].constValue(program) ?: throw NotConstArgumentException()
|
||||
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)
|
||||
}
|
||||
@ -293,7 +412,7 @@ private fun builtinSin8(args: List<Expression>, position: Position, program: Pro
|
||||
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)).toShort(), position)
|
||||
return NumericLiteralValue(DataType.BYTE, (127.0 * sin(rad)).toInt().toShort(), position)
|
||||
}
|
||||
|
||||
private fun builtinSin8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||
@ -301,7 +420,7 @@ private fun builtinSin8u(args: List<Expression>, position: Position, program: Pr
|
||||
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)).toShort(), position)
|
||||
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toInt().toShort(), position)
|
||||
}
|
||||
|
||||
private fun builtinCos8(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||
@ -309,7 +428,7 @@ private fun builtinCos8(args: List<Expression>, position: Position, program: Pro
|
||||
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)).toShort(), position)
|
||||
return NumericLiteralValue(DataType.BYTE, (127.0 * cos(rad)).toInt().toShort(), position)
|
||||
}
|
||||
|
||||
private fun builtinCos8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||
@ -317,7 +436,7 @@ private fun builtinCos8u(args: List<Expression>, position: Position, program: Pr
|
||||
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)).toShort(), position)
|
||||
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toInt().toShort(), position)
|
||||
}
|
||||
|
||||
private fun builtinSin16(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||
@ -356,7 +475,7 @@ private fun builtinSgn(args: List<Expression>, position: Position, program: Prog
|
||||
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.toShort(), position)
|
||||
return NumericLiteralValue(DataType.BYTE, constval.number.toDouble().sign.toInt().toShort(), position)
|
||||
}
|
||||
|
||||
private fun numericLiteral(value: Number, position: Position): NumericLiteralValue {
|
||||
@ -368,8 +487,8 @@ private fun numericLiteral(value: Number, position: Position): NumericLiteralVal
|
||||
floatNum
|
||||
|
||||
return when(tweakedValue) {
|
||||
is Int -> NumericLiteralValue.optimalNumeric(value.toInt(), position)
|
||||
is Short -> NumericLiteralValue.optimalNumeric(value.toInt(), position)
|
||||
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)
|
||||
|
87
compiler/src/prog8/optimizer/BinExprSplitter.kt
Normal file
87
compiler/src/prog8/optimizer/BinExprSplitter.kt
Normal file
@ -0,0 +1,87 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModification
|
||||
import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.Assignment
|
||||
|
||||
|
||||
internal class BinExprSplitter(private val program: Program) : AstWalker() {
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
// override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
// TODO somehow if we do this, the resulting code for some programs (cube3d.p8) gets hundreds of bytes larger...:
|
||||
// if(decl.type==VarDeclType.VAR ) {
|
||||
// val binExpr = decl.value as? BinaryExpression
|
||||
// if (binExpr != null && binExpr.operator in augmentAssignmentOperators) {
|
||||
// // split into a vardecl with just the left expression, and an aug. assignment with the right expression.
|
||||
// val augExpr = BinaryExpression(IdentifierReference(listOf(decl.name), decl.position), binExpr.operator, binExpr.right, binExpr.position)
|
||||
// val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
|
||||
// val assign = Assignment(target, augExpr, binExpr.position)
|
||||
// println("SPLIT VARDECL $decl")
|
||||
// return listOf(
|
||||
// IAstModification.SetExpression({ decl.value = it }, binExpr.left, decl),
|
||||
// IAstModification.InsertAfter(decl, assign, parent)
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// return noModifications
|
||||
// }
|
||||
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
if (binExpr != null) {
|
||||
/*
|
||||
|
||||
Reduce the complexity of a (binary) expression that has to be evaluated on the eval stack,
|
||||
by attempting to splitting it up into individual simple steps.
|
||||
We only consider a binary expression *one* level deep (so the operands must not be a combined expression)
|
||||
|
||||
|
||||
X = BinExpr X = LeftExpr
|
||||
<operator> followed by
|
||||
/ \ IF 'X' not used X = BinExpr
|
||||
/ \ IN expression ==> <operator>
|
||||
/ \ / \
|
||||
LeftExpr. RightExpr. / \
|
||||
X RightExpr.
|
||||
|
||||
|
||||
*/
|
||||
if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target, program.namespace)) {
|
||||
if(assignment.target isSameAs binExpr.left || assignment.target isSameAs binExpr.right)
|
||||
return noModifications
|
||||
|
||||
if(isSimpleExpression(binExpr.right) && !assignment.isAugmentable) {
|
||||
val firstAssign = Assignment(assignment.target, binExpr.left, binExpr.left.position)
|
||||
val targetExpr = assignment.target.toExpression()
|
||||
val augExpr = BinaryExpression(targetExpr, binExpr.operator, binExpr.right, binExpr.right.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, firstAssign, assignment.definingScope()),
|
||||
IAstModification.ReplaceNode(assignment.value, augExpr, assignment))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO further unraveling of binary expression trees into flat statements.
|
||||
// however this should probably be done in a more generic way to also service
|
||||
// the expressiontrees that are not used in an assignment statement...
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun isSimpleExpression(expr: Expression) =
|
||||
expr is IdentifierReference || expr is NumericLiteralValue || expr is AddressOf || expr is DirectMemoryRead || expr is StringLiteralValue || expr is ArrayLiteralValue || expr is RangeExpr
|
||||
|
||||
private fun isSimpleTarget(target: AssignTarget, namespace: INameScope) =
|
||||
if (target.identifier!=null || target.memoryAddress!=null)
|
||||
target.isInRegularRAM(namespace)
|
||||
else
|
||||
false
|
||||
|
||||
}
|
@ -1,11 +1,10 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.*
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.ast.base.ParentSentinel
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.FunctionCall
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.processing.IAstVisitor
|
||||
@ -14,8 +13,7 @@ import prog8.compiler.loadAsmIncludeFile
|
||||
|
||||
private val alwaysKeepSubroutines = setOf(
|
||||
Pair("main", "start"),
|
||||
Pair("irq", "irq"),
|
||||
Pair("prog8_lib", "init_system")
|
||||
Pair("irq", "irq")
|
||||
)
|
||||
|
||||
private val asmJumpRx = Regex("""[\-+a-zA-Z0-9_ \t]+(jmp|jsr)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
|
||||
@ -24,12 +22,12 @@ private val asmRefRx = Regex("""[\-+a-zA-Z0-9_ \t]+(...)[ \t]+(\S+).*""", RegexO
|
||||
|
||||
class CallGraph(private val program: Program) : IAstVisitor {
|
||||
|
||||
val modulesImporting = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
|
||||
val modulesImportedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
|
||||
val subroutinesCalling = mutableMapOf<INameScope, List<Subroutine>>().withDefault { mutableListOf() }
|
||||
val subroutinesCalledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
|
||||
val imports = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
|
||||
val importedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
|
||||
val calls = mutableMapOf<Subroutine, List<Subroutine>>().withDefault { mutableListOf() }
|
||||
val calledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
|
||||
|
||||
// TODO add dataflow graph: what statements use what variables
|
||||
// TODO add dataflow graph: what statements use what variables - can be used to eliminate unused vars
|
||||
val usedSymbols = mutableSetOf<Statement>()
|
||||
|
||||
init {
|
||||
@ -55,17 +53,8 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
it.importedBy.clear()
|
||||
it.imports.clear()
|
||||
|
||||
it.importedBy.addAll(modulesImportedBy.getValue(it))
|
||||
it.imports.addAll(modulesImporting.getValue(it))
|
||||
|
||||
forAllSubroutines(it) { sub ->
|
||||
sub.calledBy.clear()
|
||||
sub.calls.clear()
|
||||
|
||||
sub.calledBy.addAll(subroutinesCalledBy.getValue(sub))
|
||||
sub.calls.addAll(subroutinesCalling.getValue(sub))
|
||||
}
|
||||
|
||||
it.importedBy.addAll(importedBy.getValue(it))
|
||||
it.imports.addAll(imports.getValue(it))
|
||||
}
|
||||
|
||||
val rootmodule = program.modules.first()
|
||||
@ -85,12 +74,14 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
val thisModule = directive.definingModule()
|
||||
if (directive.directive == "%import") {
|
||||
val importedModule: Module = program.modules.single { it.name == directive.args[0].name }
|
||||
modulesImporting[thisModule] = modulesImporting.getValue(thisModule).plus(importedModule)
|
||||
modulesImportedBy[importedModule] = modulesImportedBy.getValue(importedModule).plus(thisModule)
|
||||
imports[thisModule] = imports.getValue(thisModule).plus(importedModule)
|
||||
importedBy[importedModule] = importedBy.getValue(importedModule).plus(thisModule)
|
||||
} else if (directive.directive == "%asminclude") {
|
||||
val asm = loadAsmIncludeFile(directive.args[0].str!!, thisModule.source)
|
||||
val scope = directive.definingScope()
|
||||
scanAssemblyCode(asm, directive, scope)
|
||||
val scope = directive.definingSubroutine()
|
||||
if(scope!=null) {
|
||||
scanAssemblyCode(asm, directive, scope)
|
||||
}
|
||||
}
|
||||
|
||||
super.visit(directive)
|
||||
@ -127,7 +118,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
|
||||
override fun visit(decl: VarDecl) {
|
||||
if (decl.autogeneratedDontRemove || decl.definingModule().isLibraryModule) {
|
||||
// make sure autogenerated vardecls are in the used symbols
|
||||
// make sure autogenerated vardecls are in the used symbols and are never removed as 'unused'
|
||||
addNodeAndParentScopes(decl)
|
||||
}
|
||||
|
||||
@ -141,8 +132,8 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
val otherSub = functionCall.target.targetSubroutine(program.namespace)
|
||||
if (otherSub != null) {
|
||||
functionCall.definingSubroutine()?.let { thisSub ->
|
||||
subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub)
|
||||
subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(functionCall)
|
||||
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
|
||||
calledBy[otherSub] = calledBy.getValue(otherSub).plus(functionCall)
|
||||
}
|
||||
}
|
||||
super.visit(functionCall)
|
||||
@ -152,8 +143,8 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
val otherSub = functionCallStatement.target.targetSubroutine(program.namespace)
|
||||
if (otherSub != null) {
|
||||
functionCallStatement.definingSubroutine()?.let { thisSub ->
|
||||
subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub)
|
||||
subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(functionCallStatement)
|
||||
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
|
||||
calledBy[otherSub] = calledBy.getValue(otherSub).plus(functionCallStatement)
|
||||
}
|
||||
}
|
||||
super.visit(functionCallStatement)
|
||||
@ -163,8 +154,8 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
val otherSub = jump.identifier?.targetSubroutine(program.namespace)
|
||||
if (otherSub != null) {
|
||||
jump.definingSubroutine()?.let { thisSub ->
|
||||
subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub)
|
||||
subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(jump)
|
||||
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
|
||||
calledBy[otherSub] = calledBy.getValue(otherSub).plus(jump)
|
||||
}
|
||||
}
|
||||
super.visit(jump)
|
||||
@ -177,12 +168,12 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
|
||||
override fun visit(inlineAssembly: InlineAssembly) {
|
||||
// parse inline asm for subroutine calls (jmp, jsr)
|
||||
val scope = inlineAssembly.definingScope()
|
||||
val scope = inlineAssembly.definingSubroutine()
|
||||
scanAssemblyCode(inlineAssembly.assembly, inlineAssembly, scope)
|
||||
super.visit(inlineAssembly)
|
||||
}
|
||||
|
||||
private fun scanAssemblyCode(asm: String, context: Statement, scope: INameScope) {
|
||||
private fun scanAssemblyCode(asm: String, context: Statement, scope: Subroutine?) {
|
||||
asm.lines().forEach { line ->
|
||||
val matches = asmJumpRx.matchEntire(line)
|
||||
if (matches != null) {
|
||||
@ -190,14 +181,16 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
if (jumptarget != null && (jumptarget[0].isLetter() || jumptarget[0] == '_')) {
|
||||
val node = program.namespace.lookup(jumptarget.split('.'), context)
|
||||
if (node is Subroutine) {
|
||||
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node)
|
||||
subroutinesCalledBy[node] = subroutinesCalledBy.getValue(node).plus(context)
|
||||
if(scope!=null)
|
||||
calls[scope] = calls.getValue(scope).plus(node)
|
||||
calledBy[node] = calledBy.getValue(node).plus(context)
|
||||
} else if (jumptarget.contains('.')) {
|
||||
// maybe only the first part already refers to a subroutine
|
||||
val node2 = program.namespace.lookup(listOf(jumptarget.substringBefore('.')), context)
|
||||
if (node2 is Subroutine) {
|
||||
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node2)
|
||||
subroutinesCalledBy[node2] = subroutinesCalledBy.getValue(node2).plus(context)
|
||||
if(scope!=null)
|
||||
calls[scope] = calls.getValue(scope).plus(node2)
|
||||
calledBy[node2] = calledBy.getValue(node2).plus(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -209,8 +202,9 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
if (target.contains('.')) {
|
||||
val node = program.namespace.lookup(listOf(target.substringBefore('.')), context)
|
||||
if (node is Subroutine) {
|
||||
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node)
|
||||
subroutinesCalledBy[node] = subroutinesCalledBy.getValue(node).plus(context)
|
||||
if(scope!=null)
|
||||
calls[scope] = calls.getValue(scope).plus(node)
|
||||
calledBy[node] = calledBy.getValue(node).plus(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -218,4 +212,55 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun checkRecursiveCalls(errors: ErrorReporter) {
|
||||
val cycles = recursionCycles()
|
||||
if(cycles.any()) {
|
||||
errors.warn("Program contains recursive subroutine calls. These only works in very specific limited scenarios!", Position.DUMMY)
|
||||
val printed = mutableSetOf<Subroutine>()
|
||||
for(chain in cycles) {
|
||||
if(chain[0] !in printed) {
|
||||
val chainStr = chain.joinToString(" <-- ") { "${it.name} at ${it.position}" }
|
||||
errors.warn("Cycle in (a subroutine call in) $chainStr", Position.DUMMY)
|
||||
printed.add(chain[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun recursionCycles(): List<List<Subroutine>> {
|
||||
val chains = mutableListOf<MutableList<Subroutine>>()
|
||||
for(caller in calls.keys) {
|
||||
val visited = calls.keys.associateWith { false }.toMutableMap()
|
||||
val recStack = calls.keys.associateWith { false }.toMutableMap()
|
||||
val chain = mutableListOf<Subroutine>()
|
||||
if(hasCycle(caller, visited, recStack, chain))
|
||||
chains.add(chain)
|
||||
}
|
||||
return chains
|
||||
}
|
||||
|
||||
private fun hasCycle(sub: Subroutine, visited: MutableMap<Subroutine, Boolean>, recStack: MutableMap<Subroutine, Boolean>, chain: MutableList<Subroutine>): Boolean {
|
||||
// mark current node as visited and add to recursion stack
|
||||
if(recStack[sub]==true)
|
||||
return true
|
||||
if(visited[sub]==true)
|
||||
return false
|
||||
|
||||
// mark visited and add to recursion stack
|
||||
visited[sub] = true
|
||||
recStack[sub] = true
|
||||
|
||||
// recurse for all neighbours
|
||||
for(called in calls.getValue(sub)) {
|
||||
if(hasCycle(called, visited, recStack, chain)) {
|
||||
chain.add(called)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// pop from recursion stack
|
||||
recStack[sub] = false
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -132,11 +132,11 @@ class ConstExprEvaluator {
|
||||
private fun bitwiseand(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||
if(left.type== DataType.UBYTE) {
|
||||
if(right.type in IntegerDatatypes) {
|
||||
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() or (right.number.toInt() and 255)).toShort(), left.position)
|
||||
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() and (right.number.toInt() and 255)).toShort(), left.position)
|
||||
}
|
||||
} else if(left.type== DataType.UWORD) {
|
||||
if(right.type in IntegerDatatypes) {
|
||||
return NumericLiteralValue(DataType.UWORD, left.number.toInt() or right.number.toInt(), left.position)
|
||||
return NumericLiteralValue(DataType.UWORD, left.number.toInt() and right.number.toInt(), left.position)
|
||||
}
|
||||
}
|
||||
throw ExpressionError("cannot calculate $left & $right", left.position)
|
||||
@ -163,7 +163,7 @@ class ConstExprEvaluator {
|
||||
val error = "cannot add $left and $right"
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() + right.number.toInt(), left.position)
|
||||
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() + right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() + right.number.toDouble(), left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
@ -180,7 +180,7 @@ class ConstExprEvaluator {
|
||||
val error = "cannot subtract $left and $right"
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() - right.number.toInt(), left.position)
|
||||
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() - right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() - right.number.toDouble(), left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
@ -197,7 +197,7 @@ class ConstExprEvaluator {
|
||||
val error = "cannot multiply ${left.type} and ${right.type}"
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() * right.number.toInt(), left.position)
|
||||
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() * right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() * right.number.toDouble(), left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
@ -220,7 +220,7 @@ class ConstExprEvaluator {
|
||||
in IntegerDatatypes -> {
|
||||
if(right.number.toInt()==0) divideByZeroError(right.position)
|
||||
val result: Int = left.number.toInt() / right.number.toInt()
|
||||
NumericLiteralValue.optimalNumeric(result, left.position)
|
||||
NumericLiteralValue.optimalInteger(result, left.position)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
|
||||
|
@ -1,277 +1,84 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.processing.IAstModifyingVisitor
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModification
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.functions.BuiltinFunctions
|
||||
|
||||
|
||||
internal class ConstantFoldingOptimizer(private val program: Program, private val errors: ErrorReporter) : IAstModifyingVisitor {
|
||||
var optimizationsDone: Int = 0
|
||||
internal class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun visit(decl: VarDecl): Statement {
|
||||
// the initializer value can't refer to the variable itself (recursive definition)
|
||||
// TODO: use call tree for this?
|
||||
if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true) {
|
||||
errors.err("recursive var declaration", decl.position)
|
||||
return decl
|
||||
}
|
||||
|
||||
if(decl.type==VarDeclType.CONST || decl.type==VarDeclType.VAR) {
|
||||
if(decl.isArray){
|
||||
if(decl.arraysize==null) {
|
||||
// for arrays that have no size specifier (or a non-constant one) attempt to deduce the size
|
||||
val arrayval = decl.value as? ArrayLiteralValue
|
||||
if(arrayval!=null) {
|
||||
decl.arraysize = ArrayIndex(NumericLiteralValue.optimalInteger(arrayval.value.size, decl.position), decl.position)
|
||||
optimizationsDone++
|
||||
}
|
||||
}
|
||||
else if(decl.arraysize?.size()==null) {
|
||||
val size = decl.arraysize!!.index.accept(this)
|
||||
if(size is NumericLiteralValue) {
|
||||
decl.arraysize = ArrayIndex(size, decl.position)
|
||||
optimizationsDone++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when(decl.datatype) {
|
||||
DataType.FLOAT -> {
|
||||
// vardecl: for scalar float vars, promote constant integer initialization values to floats
|
||||
val litval = decl.value as? NumericLiteralValue
|
||||
if (litval!=null && litval.type in IntegerDatatypes) {
|
||||
val newValue = NumericLiteralValue(DataType.FLOAT, litval.number.toDouble(), litval.position)
|
||||
decl.value = newValue
|
||||
optimizationsDone++
|
||||
return super.visit(decl)
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
val numericLv = decl.value as? NumericLiteralValue
|
||||
val rangeExpr = decl.value as? RangeExpr
|
||||
if(rangeExpr!=null) {
|
||||
// convert the initializer range expression to an actual array
|
||||
val declArraySize = decl.arraysize?.size()
|
||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
|
||||
val constRange = rangeExpr.toConstantIntegerRange()
|
||||
if(constRange!=null) {
|
||||
val eltType = rangeExpr.inferType(program).typeOrElse(DataType.UBYTE)
|
||||
if(eltType in ByteDatatypes) {
|
||||
decl.value = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
|
||||
constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
} else {
|
||||
decl.value = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
|
||||
constRange.map { NumericLiteralValue(eltType, it, decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
}
|
||||
decl.value!!.linkParents(decl)
|
||||
optimizationsDone++
|
||||
return super.visit(decl)
|
||||
}
|
||||
}
|
||||
if(numericLv!=null && numericLv.type== DataType.FLOAT)
|
||||
errors.err("arraysize requires only integers here", numericLv.position)
|
||||
val size = decl.arraysize?.size() ?: return decl
|
||||
if (rangeExpr==null && numericLv!=null) {
|
||||
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
|
||||
val fillvalue = numericLv.number.toInt()
|
||||
when(decl.datatype){
|
||||
DataType.ARRAY_UB -> {
|
||||
if(fillvalue !in 0..255)
|
||||
errors.err("ubyte value overflow", numericLv.position)
|
||||
}
|
||||
DataType.ARRAY_B -> {
|
||||
if(fillvalue !in -128..127)
|
||||
errors.err("byte value overflow", numericLv.position)
|
||||
}
|
||||
DataType.ARRAY_UW -> {
|
||||
if(fillvalue !in 0..65535)
|
||||
errors.err("uword value overflow", numericLv.position)
|
||||
}
|
||||
DataType.ARRAY_W -> {
|
||||
if(fillvalue !in -32768..32767)
|
||||
errors.err("word value overflow", numericLv.position)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
// create the array itself, filled with the fillvalue.
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteralValue(ArrayElementTypes.getValue(decl.datatype), it, numericLv.position) as Expression}.toTypedArray()
|
||||
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
|
||||
decl.value = refValue
|
||||
refValue.parent=decl
|
||||
optimizationsDone++
|
||||
return super.visit(decl)
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
val size = decl.arraysize?.size() ?: return decl
|
||||
val litval = decl.value as? NumericLiteralValue
|
||||
if(litval==null) {
|
||||
// there's no initialization value, but the size is known, so we're ok.
|
||||
return super.visit(decl)
|
||||
} else {
|
||||
// arraysize initializer is a single int, and we know the size.
|
||||
val fillvalue = litval.number.toDouble()
|
||||
if (fillvalue < CompilationTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > CompilationTarget.machine.FLOAT_MAX_POSITIVE)
|
||||
errors.err("float value overflow", litval.position)
|
||||
else {
|
||||
// create the array itself, filled with the fillvalue.
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteralValue(DataType.FLOAT, it, litval.position) as Expression}.toTypedArray()
|
||||
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = litval.position)
|
||||
decl.value = refValue
|
||||
refValue.parent=decl
|
||||
optimizationsDone++
|
||||
return super.visit(decl)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// nothing to do for this type
|
||||
// this includes strings and structs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val declValue = decl.value
|
||||
if(declValue!=null && decl.type==VarDeclType.VAR
|
||||
&& declValue is NumericLiteralValue && !declValue.inferType(program).istype(decl.datatype)) {
|
||||
// cast the numeric literal to the appropriate datatype of the variable
|
||||
decl.value = declValue.cast(decl.datatype)
|
||||
}
|
||||
|
||||
return super.visit(decl)
|
||||
}
|
||||
|
||||
/**
|
||||
* replace identifiers that refer to const value, with the value itself (if it's a simple type)
|
||||
*/
|
||||
override fun visit(identifier: IdentifierReference): Expression {
|
||||
// don't replace when it's an assignment target or loop variable
|
||||
if(identifier.parent is AssignTarget)
|
||||
return identifier
|
||||
var forloop = identifier.parent as? ForLoop
|
||||
if(forloop==null)
|
||||
forloop = identifier.parent.parent as? ForLoop
|
||||
if(forloop!=null && identifier===forloop.loopVar)
|
||||
return identifier
|
||||
|
||||
val cval = identifier.constValue(program) ?: return identifier
|
||||
return when (cval.type) {
|
||||
in NumericDatatypes -> {
|
||||
val copy = NumericLiteralValue(cval.type, cval.number, identifier.position)
|
||||
copy.parent = identifier.parent
|
||||
copy
|
||||
}
|
||||
in PassByReferenceDatatypes -> throw FatalAstException("pass-by-reference type should not be considered a constant")
|
||||
else -> identifier
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(functionCall: FunctionCall): Expression {
|
||||
super.visit(functionCall)
|
||||
typeCastConstArguments(functionCall)
|
||||
return functionCall.constValue(program) ?: functionCall
|
||||
}
|
||||
|
||||
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
|
||||
super.visit(functionCallStatement)
|
||||
typeCastConstArguments(functionCallStatement)
|
||||
return functionCallStatement
|
||||
}
|
||||
|
||||
private fun typeCastConstArguments(functionCall: IFunctionCall) {
|
||||
if(functionCall.target.nameInSource.size==1) {
|
||||
val builtinFunction = BuiltinFunctions[functionCall.target.nameInSource.single()]
|
||||
if(builtinFunction!=null) {
|
||||
// match the arguments of a builtin function signature.
|
||||
for(arg in functionCall.args.withIndex().zip(builtinFunction.parameters)) {
|
||||
val possibleDts = arg.second.possibleDatatypes
|
||||
val argConst = arg.first.value.constValue(program)
|
||||
if(argConst!=null && argConst.type !in possibleDts) {
|
||||
val convertedValue = argConst.cast(possibleDts.first())
|
||||
functionCall.args[arg.first.index] = convertedValue
|
||||
optimizationsDone++
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
// match the arguments of a subroutine.
|
||||
val subroutine = functionCall.target.targetSubroutine(program.namespace)
|
||||
if(subroutine!=null) {
|
||||
// if types differ, try to typecast constant arguments to the function call to the desired data type of the parameter
|
||||
for(arg in functionCall.args.withIndex().zip(subroutine.parameters)) {
|
||||
val expectedDt = arg.second.type
|
||||
val argConst = arg.first.value.constValue(program)
|
||||
if(argConst!=null && argConst.type!=expectedDt) {
|
||||
val convertedValue = argConst.cast(expectedDt)
|
||||
functionCall.args[arg.first.index] = convertedValue
|
||||
optimizationsDone++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(memread: DirectMemoryRead): Expression {
|
||||
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
|
||||
// @( &thing ) --> thing
|
||||
val addrOf = memread.addressExpression as? AddressOf
|
||||
if(addrOf!=null)
|
||||
return super.visit(addrOf.identifier)
|
||||
return super.visit(memread)
|
||||
return if(addrOf!=null)
|
||||
listOf(IAstModification.ReplaceNode(memread, addrOf.identifier, parent))
|
||||
else
|
||||
noModifications
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to accept a unary prefix expression.
|
||||
* Compile-time constant sub expressions will be evaluated on the spot.
|
||||
* For instance, the expression for "- 4.5" will be optimized into the float literal -4.5
|
||||
*/
|
||||
override fun visit(expr: PrefixExpression): Expression {
|
||||
val prefixExpr=super.visit(expr)
|
||||
if(prefixExpr !is PrefixExpression)
|
||||
return prefixExpr
|
||||
|
||||
val subexpr = prefixExpr.expression
|
||||
override fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
|
||||
// Try to turn a unary prefix expression into a single constant value.
|
||||
// Compile-time constant sub expressions will be evaluated on the spot.
|
||||
// For instance, the expression for "- 4.5" will be optimized into the float literal -4.5
|
||||
val subexpr = expr.expression
|
||||
if (subexpr is NumericLiteralValue) {
|
||||
// accept prefixed literal values (such as -3, not true)
|
||||
return when (prefixExpr.operator) {
|
||||
"+" -> subexpr
|
||||
return when (expr.operator) {
|
||||
"+" -> listOf(IAstModification.ReplaceNode(expr, subexpr, parent))
|
||||
"-" -> when (subexpr.type) {
|
||||
in IntegerDatatypes -> {
|
||||
optimizationsDone++
|
||||
NumericLiteralValue.optimalNumeric(-subexpr.number.toInt(), subexpr.position)
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue.optimalInteger(-subexpr.number.toInt(), subexpr.position),
|
||||
parent))
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
optimizationsDone++
|
||||
NumericLiteralValue(DataType.FLOAT, -subexpr.number.toDouble(), subexpr.position)
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue(DataType.FLOAT, -subexpr.number.toDouble(), subexpr.position),
|
||||
parent))
|
||||
}
|
||||
else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
|
||||
}
|
||||
"~" -> when (subexpr.type) {
|
||||
in IntegerDatatypes -> {
|
||||
optimizationsDone++
|
||||
NumericLiteralValue.optimalNumeric(subexpr.number.toInt().inv(), subexpr.position)
|
||||
DataType.BYTE -> {
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue(DataType.BYTE, subexpr.number.toInt().inv(), subexpr.position),
|
||||
parent))
|
||||
}
|
||||
DataType.UBYTE -> {
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue(DataType.UBYTE, subexpr.number.toInt().inv() and 255, subexpr.position),
|
||||
parent))
|
||||
}
|
||||
DataType.WORD -> {
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue(DataType.WORD, subexpr.number.toInt().inv(), subexpr.position),
|
||||
parent))
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue(DataType.UWORD, subexpr.number.toInt().inv() and 65535, subexpr.position),
|
||||
parent))
|
||||
}
|
||||
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
|
||||
}
|
||||
"not" -> {
|
||||
optimizationsDone++
|
||||
NumericLiteralValue.fromBoolean(subexpr.number.toDouble() == 0.0, subexpr.position)
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue.fromBoolean(subexpr.number.toDouble() == 0.0, subexpr.position),
|
||||
parent))
|
||||
}
|
||||
else -> throw ExpressionError(prefixExpr.operator, subexpr.position)
|
||||
else -> throw ExpressionError(expr.operator, subexpr.position)
|
||||
}
|
||||
}
|
||||
return prefixExpr
|
||||
return noModifications
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to accept a binary expression.
|
||||
* Try to constfold a binary expression.
|
||||
* Compile-time constant sub expressions will be evaluated on the spot.
|
||||
* For instance, "9 * (4 + 2)" will be optimized into the integer literal 54.
|
||||
*
|
||||
@ -287,13 +94,7 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
* (X / c1) * c2 -> X / (c2/c1)
|
||||
* (X + c1) - c2 -> X + (c1-c2)
|
||||
*/
|
||||
override fun visit(expr: BinaryExpression): Expression {
|
||||
super.visit(expr)
|
||||
|
||||
if(expr.left is StringLiteralValue || expr.left is ArrayLiteralValue
|
||||
|| expr.right is StringLiteralValue || expr.right is ArrayLiteralValue)
|
||||
throw FatalAstException("binexpr with reference litval instead of numeric")
|
||||
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
val leftconst = expr.left.constValue(program)
|
||||
val rightconst = expr.right.constValue(program)
|
||||
|
||||
@ -307,21 +108,153 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
val subrightconst = subExpr.right.constValue(program)
|
||||
if ((subleftconst != null && subrightconst == null) || (subleftconst==null && subrightconst!=null)) {
|
||||
// try reordering.
|
||||
return groupTwoConstsTogether(expr, subExpr,
|
||||
val change = groupTwoConstsTogether(expr, subExpr,
|
||||
leftconst != null, rightconst != null,
|
||||
subleftconst != null, subrightconst != null)
|
||||
return change?.let { listOf(it) } ?: noModifications
|
||||
}
|
||||
}
|
||||
|
||||
// const fold when both operands are a const
|
||||
return when {
|
||||
leftconst != null && rightconst != null -> {
|
||||
optimizationsDone++
|
||||
val evaluator = ConstExprEvaluator()
|
||||
evaluator.evaluate(leftconst, expr.operator, rightconst)
|
||||
}
|
||||
if(leftconst != null && rightconst != null) {
|
||||
val evaluator = ConstExprEvaluator()
|
||||
val result = evaluator.evaluate(leftconst, expr.operator, rightconst)
|
||||
return listOf(IAstModification.ReplaceNode(expr, result, parent))
|
||||
}
|
||||
|
||||
else -> expr
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||
// because constant folding can result in arrays that are now suddenly capable
|
||||
// of telling the type of all their elements (for instance, when they contained -2 which
|
||||
// was a prefix expression earlier), we recalculate the array's datatype.
|
||||
if(array.type.isKnown)
|
||||
return noModifications
|
||||
|
||||
// if the array literalvalue is inside an array vardecl, take the type from that
|
||||
// otherwise infer it from the elements of the array
|
||||
val vardeclType = (array.parent as? VarDecl)?.datatype
|
||||
if(vardeclType!=null) {
|
||||
val newArray = array.cast(vardeclType)
|
||||
if (newArray != null && newArray != array)
|
||||
return listOf(IAstModification.ReplaceNode(array, newArray, parent))
|
||||
} else {
|
||||
val arrayDt = array.guessDatatype(program)
|
||||
if (arrayDt.isKnown) {
|
||||
val newArray = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
|
||||
if (newArray != null && newArray != array)
|
||||
return listOf(IAstModification.ReplaceNode(array, newArray, parent))
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||
// the args of a fuction are constfolded via recursion already.
|
||||
val constvalue = functionCall.constValue(program)
|
||||
return if(constvalue!=null)
|
||||
listOf(IAstModification.ReplaceNode(functionCall, constvalue, parent))
|
||||
else
|
||||
noModifications
|
||||
}
|
||||
|
||||
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
|
||||
fun adjustRangeDt(rangeFrom: NumericLiteralValue, targetDt: DataType, rangeTo: NumericLiteralValue, stepLiteral: NumericLiteralValue?, range: RangeExpr): RangeExpr? {
|
||||
val fromCast = rangeFrom.cast(targetDt)
|
||||
val toCast = rangeTo.cast(targetDt)
|
||||
if(!fromCast.isValid || !toCast.isValid)
|
||||
return null
|
||||
|
||||
val newStep =
|
||||
if(stepLiteral!=null) {
|
||||
val stepCast = stepLiteral.cast(targetDt)
|
||||
if(stepCast.isValid)
|
||||
stepCast.valueOrZero()
|
||||
else
|
||||
range.step
|
||||
} else {
|
||||
range.step
|
||||
}
|
||||
|
||||
return RangeExpr(fromCast.valueOrZero(), toCast.valueOrZero(), newStep, range.position)
|
||||
}
|
||||
|
||||
// adjust the datatype of a range expression in for loops to the loop variable.
|
||||
val iterableRange = forLoop.iterable as? RangeExpr ?: return noModifications
|
||||
val rangeFrom = iterableRange.from as? NumericLiteralValue
|
||||
val rangeTo = iterableRange.to as? NumericLiteralValue
|
||||
if(rangeFrom==null || rangeTo==null) return noModifications
|
||||
|
||||
val loopvar = forLoop.loopVar.targetVarDecl(program.namespace)!!
|
||||
val stepLiteral = iterableRange.step as? NumericLiteralValue
|
||||
when(loopvar.datatype) {
|
||||
DataType.UBYTE -> {
|
||||
if(rangeFrom.type!= DataType.UBYTE) {
|
||||
// attempt to translate the iterable into ubyte values
|
||||
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
||||
if(newIter!=null)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
|
||||
}
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
if(rangeFrom.type!= DataType.BYTE) {
|
||||
// attempt to translate the iterable into byte values
|
||||
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
||||
if(newIter!=null)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
if(rangeFrom.type!= DataType.UWORD) {
|
||||
// attempt to translate the iterable into uword values
|
||||
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
||||
if(newIter!=null)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
|
||||
}
|
||||
}
|
||||
DataType.WORD -> {
|
||||
if(rangeFrom.type!= DataType.WORD) {
|
||||
// attempt to translate the iterable into word values
|
||||
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
||||
if(newIter!=null)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
|
||||
}
|
||||
}
|
||||
else -> throw FatalAstException("invalid loopvar datatype $loopvar")
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
val numval = decl.value as? NumericLiteralValue
|
||||
if(decl.type== VarDeclType.CONST && numval!=null) {
|
||||
val valueDt = numval.inferType(program)
|
||||
if(!valueDt.istype(decl.datatype)) {
|
||||
val cast = numval.cast(decl.datatype)
|
||||
if(cast.isValid)
|
||||
return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl))
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private class ShuffleOperands(val expr: BinaryExpression,
|
||||
val exprOperator: String?,
|
||||
val subExpr: BinaryExpression,
|
||||
val newExprLeft: Expression?,
|
||||
val newExprRight: Expression?,
|
||||
val newSubexprLeft: Expression?,
|
||||
val newSubexprRight: Expression?
|
||||
): IAstModification {
|
||||
override fun perform() {
|
||||
if(exprOperator!=null) expr.operator = exprOperator
|
||||
if(newExprLeft!=null) expr.left = newExprLeft
|
||||
if(newExprRight!=null) expr.right = newExprRight
|
||||
if(newSubexprLeft!=null) subExpr.left = newSubexprLeft
|
||||
if(newSubexprRight!=null) subExpr.right = newSubexprRight
|
||||
}
|
||||
}
|
||||
|
||||
@ -330,64 +263,61 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
leftIsConst: Boolean,
|
||||
rightIsConst: Boolean,
|
||||
subleftIsConst: Boolean,
|
||||
subrightIsConst: Boolean): Expression
|
||||
subrightIsConst: Boolean): IAstModification?
|
||||
{
|
||||
// todo: this implements only a small set of possible reorderings at this time
|
||||
if(expr.operator==subExpr.operator) {
|
||||
// both operators are the isSameAs.
|
||||
// If + or *, we can simply swap the const of expr and Var in subexpr.
|
||||
if(expr.operator=="+" || expr.operator=="*") {
|
||||
if(leftIsConst) {
|
||||
// both operators are the same.
|
||||
|
||||
// If associative, we can simply shuffle the const operands around to optimize.
|
||||
if(expr.operator in associativeOperators) {
|
||||
return if(leftIsConst) {
|
||||
if(subleftIsConst)
|
||||
expr.left = subExpr.right.also { subExpr.right = expr.left }
|
||||
ShuffleOperands(expr, null, subExpr, subExpr.right, null, null, expr.left)
|
||||
else
|
||||
expr.left = subExpr.left.also { subExpr.left = expr.left }
|
||||
ShuffleOperands(expr, null, subExpr, subExpr.left, null, expr.left, null)
|
||||
} else {
|
||||
if(subleftIsConst)
|
||||
expr.right = subExpr.right.also {subExpr.right = expr.right }
|
||||
ShuffleOperands(expr, null, subExpr, null, subExpr.right, null, expr.right)
|
||||
else
|
||||
expr.right = subExpr.left.also { subExpr.left = expr.right }
|
||||
ShuffleOperands(expr, null, subExpr, null, subExpr.left, expr.right, null)
|
||||
}
|
||||
optimizationsDone++
|
||||
return expr
|
||||
}
|
||||
|
||||
// If - or /, we simetimes must reorder more, and flip operators (- -> +, / -> *)
|
||||
if(expr.operator=="-" || expr.operator=="/") {
|
||||
optimizationsDone++
|
||||
if(leftIsConst) {
|
||||
return if(subleftIsConst) {
|
||||
val tmp = subExpr.right
|
||||
subExpr.right = subExpr.left
|
||||
subExpr.left = expr.left
|
||||
expr.left = tmp
|
||||
expr.operator = if(expr.operator=="-") "+" else "*"
|
||||
expr
|
||||
} else
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.left, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
|
||||
expr.operator, subExpr.left, expr.position)
|
||||
return if (subleftIsConst) {
|
||||
ShuffleOperands(expr, if (expr.operator == "-") "+" else "*", subExpr, subExpr.right, null, expr.left, subExpr.left)
|
||||
} else {
|
||||
IAstModification.ReplaceNode(expr,
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.left, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
|
||||
expr.operator, subExpr.left, expr.position),
|
||||
expr.parent)
|
||||
}
|
||||
} else {
|
||||
return if(subleftIsConst) {
|
||||
expr.right = subExpr.right.also { subExpr.right = expr.right }
|
||||
expr
|
||||
} else
|
||||
BinaryExpression(
|
||||
subExpr.left, expr.operator,
|
||||
BinaryExpression(expr.right, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
|
||||
expr.position)
|
||||
return ShuffleOperands(expr, null, subExpr, null, subExpr.right, null, expr.right)
|
||||
} else {
|
||||
IAstModification.ReplaceNode(expr,
|
||||
BinaryExpression(
|
||||
subExpr.left, expr.operator,
|
||||
BinaryExpression(expr.right, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
|
||||
expr.position),
|
||||
expr.parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
return expr
|
||||
return null
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
if(expr.operator=="/" && subExpr.operator=="*") {
|
||||
optimizationsDone++
|
||||
if(leftIsConst) {
|
||||
return if(subleftIsConst) {
|
||||
val change = if(subleftIsConst) {
|
||||
// C1/(C2*V) -> (C1/C2)/V
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.left, "/", subExpr.left, subExpr.position),
|
||||
@ -400,8 +330,9 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
"/",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||
} else {
|
||||
return if(subleftIsConst) {
|
||||
val change = if(subleftIsConst) {
|
||||
// (C1*V)/C2 -> (C1/C2)*V
|
||||
BinaryExpression(
|
||||
BinaryExpression(subExpr.left, "/", expr.right, subExpr.position),
|
||||
@ -414,12 +345,12 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
"*",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||
}
|
||||
}
|
||||
else if(expr.operator=="*" && subExpr.operator=="/") {
|
||||
optimizationsDone++
|
||||
if(leftIsConst) {
|
||||
return if(subleftIsConst) {
|
||||
val change = if(subleftIsConst) {
|
||||
// C1*(C2/V) -> (C1*C2)/V
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.left, "*", subExpr.left, subExpr.position),
|
||||
@ -432,8 +363,9 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
"*",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||
} else {
|
||||
return if(subleftIsConst) {
|
||||
val change = if(subleftIsConst) {
|
||||
// (C1/V)*C2 -> (C1*C2)/V
|
||||
BinaryExpression(
|
||||
BinaryExpression(subExpr.left, "*", expr.right, subExpr.position),
|
||||
@ -446,12 +378,12 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
"*",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||
}
|
||||
}
|
||||
else if(expr.operator=="+" && subExpr.operator=="-") {
|
||||
optimizationsDone++
|
||||
if(leftIsConst){
|
||||
return if(subleftIsConst){
|
||||
val change = if(subleftIsConst){
|
||||
// c1+(c2-v) -> (c1+c2)-v
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.left, "+", subExpr.left, subExpr.position),
|
||||
@ -464,8 +396,9 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
"+",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||
} else {
|
||||
return if(subleftIsConst) {
|
||||
val change = if(subleftIsConst) {
|
||||
// (c1-v)+c2 -> (c1+c2)-v
|
||||
BinaryExpression(
|
||||
BinaryExpression(subExpr.left, "+", expr.right, subExpr.position),
|
||||
@ -478,12 +411,12 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
"+",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||
}
|
||||
}
|
||||
else if(expr.operator=="-" && subExpr.operator=="+") {
|
||||
optimizationsDone++
|
||||
if(leftIsConst) {
|
||||
return if(subleftIsConst) {
|
||||
val change = if(subleftIsConst) {
|
||||
// c1-(c2+v) -> (c1-c2)-v
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.left, "-", subExpr.left, subExpr.position),
|
||||
@ -496,8 +429,9 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
"-",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||
} else {
|
||||
return if(subleftIsConst) {
|
||||
val change = if(subleftIsConst) {
|
||||
// (c1+v)-c2 -> v+(c1-c2)
|
||||
BinaryExpression(
|
||||
BinaryExpression(subExpr.left, "-", expr.right, subExpr.position),
|
||||
@ -510,113 +444,12 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
"+",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||
}
|
||||
}
|
||||
|
||||
return expr
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(forLoop: ForLoop): Statement {
|
||||
|
||||
fun adjustRangeDt(rangeFrom: NumericLiteralValue, targetDt: DataType, rangeTo: NumericLiteralValue, stepLiteral: NumericLiteralValue?, range: RangeExpr): RangeExpr {
|
||||
val newFrom: NumericLiteralValue
|
||||
val newTo: NumericLiteralValue
|
||||
try {
|
||||
newFrom = rangeFrom.cast(targetDt)
|
||||
newTo = rangeTo.cast(targetDt)
|
||||
} catch (x: ExpressionError) {
|
||||
return range
|
||||
}
|
||||
val newStep: Expression = try {
|
||||
stepLiteral?.cast(targetDt)?: range.step
|
||||
} catch(ee: ExpressionError) {
|
||||
range.step
|
||||
}
|
||||
return RangeExpr(newFrom, newTo, newStep, range.position)
|
||||
}
|
||||
|
||||
val forLoop2 = super.visit(forLoop) as ForLoop
|
||||
|
||||
// check if we need to adjust an array literal to the loop variable's datatype
|
||||
val array = forLoop2.iterable as? ArrayLiteralValue
|
||||
if(array!=null) {
|
||||
val loopvarDt: DataType = when {
|
||||
forLoop.loopVar!=null -> forLoop.loopVar!!.inferType(program).typeOrElse(DataType.UBYTE)
|
||||
forLoop.loopRegister!=null -> DataType.UBYTE
|
||||
else -> throw FatalAstException("weird for loop")
|
||||
}
|
||||
|
||||
val arrayType = when(loopvarDt) {
|
||||
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 -> throw FatalAstException("invalid array elt type")
|
||||
}
|
||||
val array2 = array.cast(arrayType)
|
||||
if(array2!=null && array2!==array) {
|
||||
forLoop2.iterable = array2
|
||||
array2.linkParents(forLoop2)
|
||||
}
|
||||
}
|
||||
|
||||
// adjust the datatype of a range expression in for loops to the loop variable.
|
||||
val iterableRange = forLoop2.iterable as? RangeExpr ?: return forLoop2
|
||||
val rangeFrom = iterableRange.from as? NumericLiteralValue
|
||||
val rangeTo = iterableRange.to as? NumericLiteralValue
|
||||
if(rangeFrom==null || rangeTo==null) return forLoop2
|
||||
|
||||
val loopvar = forLoop2.loopVar?.targetVarDecl(program.namespace)
|
||||
if(loopvar!=null) {
|
||||
val stepLiteral = iterableRange.step as? NumericLiteralValue
|
||||
when(loopvar.datatype) {
|
||||
DataType.UBYTE -> {
|
||||
if(rangeFrom.type!= DataType.UBYTE) {
|
||||
// attempt to translate the iterable into ubyte values
|
||||
forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
||||
}
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
if(rangeFrom.type!= DataType.BYTE) {
|
||||
// attempt to translate the iterable into byte values
|
||||
forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
if(rangeFrom.type!= DataType.UWORD) {
|
||||
// attempt to translate the iterable into uword values
|
||||
forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
||||
}
|
||||
}
|
||||
DataType.WORD -> {
|
||||
if(rangeFrom.type!= DataType.WORD) {
|
||||
// attempt to translate the iterable into word values
|
||||
forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
||||
}
|
||||
}
|
||||
else -> throw FatalAstException("invalid loopvar datatype $loopvar")
|
||||
}
|
||||
}
|
||||
return forLoop2
|
||||
}
|
||||
|
||||
override fun visit(arrayLiteral: ArrayLiteralValue): Expression {
|
||||
// because constant folding can result in arrays that are now suddenly capable
|
||||
// of telling the type of all their elements (for instance, when they contained -2 which
|
||||
// was a prefix expression earlier), we recalculate the array's datatype.
|
||||
val array = super.visit(arrayLiteral)
|
||||
if(array is ArrayLiteralValue) {
|
||||
if(array.type.isKnown)
|
||||
return array
|
||||
val arrayDt = array.guessDatatype(program)
|
||||
if(arrayDt.isKnown) {
|
||||
val newArray = arrayLiteral.cast(arrayDt.typeOrElse(DataType.STRUCT))
|
||||
if(newArray!=null)
|
||||
return newArray
|
||||
}
|
||||
}
|
||||
return array
|
||||
}
|
||||
}
|
||||
|
192
compiler/src/prog8/optimizer/ConstantIdentifierReplacer.kt
Normal file
192
compiler/src/prog8/optimizer/ConstantIdentifierReplacer.kt
Normal file
@ -0,0 +1,192 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModification
|
||||
import prog8.ast.statements.ArrayIndex
|
||||
import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.ForLoop
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
|
||||
// Fix up the literal value's type to match that of the vardecl
|
||||
internal class VarConstantValueTypeAdjuster(private val program: Program) : AstWalker() {
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
val declConstValue = decl.value?.constValue(program)
|
||||
if(declConstValue!=null && (decl.type==VarDeclType.VAR || decl.type==VarDeclType.CONST)
|
||||
&& !declConstValue.inferType(program).istype(decl.datatype)) {
|
||||
// cast the numeric literal to the appropriate datatype of the variable
|
||||
val cast = declConstValue.cast(decl.datatype)
|
||||
if(cast.isValid)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, cast.valueOrZero(), decl))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Replace all constant identifiers with their actual value,
|
||||
// and the array var initializer values and sizes.
|
||||
// This is needed because further constant optimizations depend on those.
|
||||
internal class ConstantIdentifierReplacer(private val program: Program, private val errors: ErrorReporter) : AstWalker() {
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
|
||||
// replace identifiers that refer to const value, with the value itself
|
||||
// if it's a simple type and if it's not a left hand side variable
|
||||
if(identifier.parent is AssignTarget)
|
||||
return noModifications
|
||||
var forloop = identifier.parent as? ForLoop
|
||||
if(forloop==null)
|
||||
forloop = identifier.parent.parent as? ForLoop
|
||||
if(forloop!=null && identifier===forloop.loopVar)
|
||||
return noModifications
|
||||
|
||||
val cval = identifier.constValue(program) ?: return noModifications
|
||||
return when (cval.type) {
|
||||
in NumericDatatypes -> listOf(IAstModification.ReplaceNode(identifier, NumericLiteralValue(cval.type, cval.number, identifier.position), identifier.parent))
|
||||
in PassByReferenceDatatypes -> throw FatalAstException("pass-by-reference type should not be considered a constant")
|
||||
else -> noModifications
|
||||
}
|
||||
}
|
||||
|
||||
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
// the initializer value can't refer to the variable itself (recursive definition)
|
||||
// TODO: use call graph for this?
|
||||
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexVar?.referencesIdentifier(decl.name) == true) {
|
||||
errors.err("recursive var declaration", decl.position)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
if(decl.type== VarDeclType.CONST || decl.type== VarDeclType.VAR) {
|
||||
if(decl.isArray){
|
||||
val arraysize = decl.arraysize
|
||||
if(arraysize==null) {
|
||||
// for arrays that have no size specifier attempt to deduce the size
|
||||
val arrayval = decl.value as? ArrayLiteralValue
|
||||
if(arrayval!=null) {
|
||||
return listOf(IAstModification.SetExpression(
|
||||
{ decl.arraysize = ArrayIndex(it, decl.position) },
|
||||
NumericLiteralValue.optimalInteger(arrayval.value.size, decl.position),
|
||||
decl
|
||||
))
|
||||
}
|
||||
} else if(arraysize.constIndex()==null) {
|
||||
// see if we can calculate the size from other fields
|
||||
val cval = arraysize.indexVar?.constValue(program) ?: arraysize.origExpression?.constValue(program)
|
||||
if(cval!=null) {
|
||||
arraysize.indexVar = null
|
||||
arraysize.origExpression = null
|
||||
arraysize.indexNum = cval
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when(decl.datatype) {
|
||||
DataType.FLOAT -> {
|
||||
// vardecl: for scalar float vars, promote constant integer initialization values to floats
|
||||
val litval = decl.value as? NumericLiteralValue
|
||||
if (litval!=null && litval.type in IntegerDatatypes) {
|
||||
val newValue = NumericLiteralValue(DataType.FLOAT, litval.number.toDouble(), litval.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
val numericLv = decl.value as? NumericLiteralValue
|
||||
val rangeExpr = decl.value as? RangeExpr
|
||||
if(rangeExpr!=null) {
|
||||
// convert the initializer range expression to an actual array
|
||||
val declArraySize = decl.arraysize?.constIndex()
|
||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
|
||||
val constRange = rangeExpr.toConstantIntegerRange()
|
||||
if(constRange!=null) {
|
||||
val eltType = rangeExpr.inferType(program).typeOrElse(DataType.UBYTE)
|
||||
val newValue = if(eltType in ByteDatatypes) {
|
||||
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
|
||||
constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
} else {
|
||||
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
|
||||
constRange.map { NumericLiteralValue(eltType, it, decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||
}
|
||||
}
|
||||
if(numericLv!=null && numericLv.type== DataType.FLOAT)
|
||||
errors.err("arraysize requires only integers here", numericLv.position)
|
||||
val size = decl.arraysize?.constIndex() ?: return noModifications
|
||||
if (rangeExpr==null && numericLv!=null) {
|
||||
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
|
||||
val fillvalue = numericLv.number.toInt()
|
||||
when(decl.datatype){
|
||||
DataType.ARRAY_UB -> {
|
||||
if(fillvalue !in 0..255)
|
||||
errors.err("ubyte value overflow", numericLv.position)
|
||||
}
|
||||
DataType.ARRAY_B -> {
|
||||
if(fillvalue !in -128..127)
|
||||
errors.err("byte value overflow", numericLv.position)
|
||||
}
|
||||
DataType.ARRAY_UW -> {
|
||||
if(fillvalue !in 0..65535)
|
||||
errors.err("uword value overflow", numericLv.position)
|
||||
}
|
||||
DataType.ARRAY_W -> {
|
||||
if(fillvalue !in -32768..32767)
|
||||
errors.err("word value overflow", numericLv.position)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
// create the array itself, filled with the fillvalue.
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteralValue(ArrayElementTypes.getValue(decl.datatype), it, numericLv.position) }.toTypedArray<Expression>()
|
||||
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
val size = decl.arraysize?.constIndex() ?: return noModifications
|
||||
val litval = decl.value as? NumericLiteralValue
|
||||
val rangeExpr = decl.value as? RangeExpr
|
||||
if(rangeExpr!=null) {
|
||||
// convert the initializer range expression to an actual array of floats
|
||||
val declArraySize = decl.arraysize?.constIndex()
|
||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
|
||||
val constRange = rangeExpr.toConstantIntegerRange()
|
||||
if(constRange!=null) {
|
||||
val newValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F),
|
||||
constRange.map { NumericLiteralValue(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||
}
|
||||
}
|
||||
if(rangeExpr==null && litval!=null) {
|
||||
// arraysize initializer is a single int, and we know the size.
|
||||
val fillvalue = litval.number.toDouble()
|
||||
if (fillvalue < CompilationTarget.instance.machine.FLOAT_MAX_NEGATIVE || fillvalue > CompilationTarget.instance.machine.FLOAT_MAX_POSITIVE)
|
||||
errors.err("float value overflow", litval.position)
|
||||
else {
|
||||
// create the array itself, filled with the fillvalue.
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteralValue(DataType.FLOAT, it, litval.position) }.toTypedArray<Expression>()
|
||||
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = litval.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// nothing to do for this type
|
||||
// this includes strings and structs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
}
|
@ -6,7 +6,6 @@ import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModification
|
||||
import prog8.ast.statements.Assignment
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.log2
|
||||
import kotlin.math.pow
|
||||
@ -14,12 +13,6 @@ import kotlin.math.pow
|
||||
/*
|
||||
todo add more expression optimizations
|
||||
|
||||
x + x -> x << 1 (for words... for bytes too?)
|
||||
x + x + x + x -> x << 2 (for words... for bytes too?)
|
||||
x + x + x -> ???? x*3 ??? words/bytes?
|
||||
x - x -> 0
|
||||
|
||||
|
||||
Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html
|
||||
|
||||
*/
|
||||
@ -28,12 +21,7 @@ import kotlin.math.pow
|
||||
internal class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
|
||||
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
|
||||
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
if (assignment.aug_op != null)
|
||||
throw AstException("augmented assignments should have been converted to normal assignments before this optimizer: $assignment")
|
||||
return emptyList()
|
||||
}
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
val mods = mutableListOf<IAstModification>()
|
||||
@ -42,19 +30,22 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
val literal = typecast.expression as? NumericLiteralValue
|
||||
if (literal != null) {
|
||||
val newLiteral = literal.cast(typecast.type)
|
||||
if (newLiteral !== literal)
|
||||
mods += IAstModification.ReplaceNode(typecast.expression, newLiteral, typecast)
|
||||
if (newLiteral.isValid && newLiteral.valueOrZero() !== literal)
|
||||
mods += IAstModification.ReplaceNode(typecast.expression, newLiteral.valueOrZero(), typecast)
|
||||
}
|
||||
|
||||
// remove redundant nested typecasts:
|
||||
// if the typecast casts a value to the same type, remove the cast.
|
||||
// if the typecast contains another typecast, remove the inner typecast.
|
||||
// remove redundant nested typecasts
|
||||
val subTypecast = typecast.expression as? TypecastExpression
|
||||
if (subTypecast != null) {
|
||||
mods += IAstModification.ReplaceNode(typecast.expression, subTypecast.expression, typecast)
|
||||
// remove the sub-typecast if its datatype is larger than the outer typecast
|
||||
if(subTypecast.type largerThan typecast.type) {
|
||||
mods += IAstModification.ReplaceNode(typecast.expression, subTypecast.expression, typecast)
|
||||
}
|
||||
} else {
|
||||
if (typecast.expression.inferType(program).istype(typecast.type))
|
||||
if (typecast.expression.inferType(program).istype(typecast.type)) {
|
||||
// remove duplicate cast
|
||||
mods += IAstModification.ReplaceNode(typecast, typecast.expression, parent)
|
||||
}
|
||||
}
|
||||
|
||||
return mods
|
||||
@ -88,10 +79,10 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
if (newExpr != null)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||
}
|
||||
else -> return emptyList()
|
||||
else -> return noModifications
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
@ -103,7 +94,6 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
if (!leftIDt.isKnown || !rightIDt.isKnown)
|
||||
throw FatalAstException("can't determine datatype of both expression operands $expr")
|
||||
|
||||
|
||||
// ConstValue <associativeoperator> X --> X <associativeoperator> ConstValue
|
||||
if (leftVal != null && expr.operator in associativeOperators && rightVal == null)
|
||||
return listOf(IAstModification.SwapOperands(expr))
|
||||
@ -180,6 +170,42 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
}
|
||||
}
|
||||
|
||||
if(expr.operator == ">=" && rightVal?.number == 0) {
|
||||
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
|
||||
// unsigned >= 0 --> true
|
||||
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(true, expr.position), parent))
|
||||
}
|
||||
}
|
||||
|
||||
if(expr.operator == "<" && rightVal?.number == 0) {
|
||||
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
|
||||
// unsigned < 0 --> false
|
||||
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(false, expr.position), parent))
|
||||
}
|
||||
when(leftDt) {
|
||||
DataType.BYTE -> {
|
||||
// signed < 0 --> signed & $80
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr,
|
||||
BinaryExpression(expr.left, "&", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
|
||||
parent
|
||||
))
|
||||
}
|
||||
DataType.WORD -> {
|
||||
// signedw < 0 --> msb(signedw) & $80
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr,
|
||||
BinaryExpression(FunctionCall(IdentifierReference(listOf("msb"), expr.position),
|
||||
mutableListOf(expr.left),
|
||||
expr.position
|
||||
), "&", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
|
||||
parent
|
||||
))
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
// simplify when a term is constant and directly determines the outcome
|
||||
val constTrue = NumericLiteralValue.fromBoolean(true, expr.position)
|
||||
val constFalse = NumericLiteralValue.fromBoolean(false, expr.position)
|
||||
@ -246,7 +272,50 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
if(newExpr != null)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||
|
||||
return emptyList()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||
if(functionCall.target.nameInSource == listOf("lsb")) {
|
||||
val arg = functionCall.args[0]
|
||||
if(arg is TypecastExpression) {
|
||||
val valueDt = arg.expression.inferType(program)
|
||||
if (valueDt.istype(DataType.BYTE) || valueDt.istype(DataType.UBYTE)) {
|
||||
// useless lsb() of byte value that was casted to word
|
||||
return listOf(IAstModification.ReplaceNode(functionCall, arg.expression, parent))
|
||||
}
|
||||
} else {
|
||||
val argDt = arg.inferType(program)
|
||||
if (argDt.istype(DataType.BYTE) || argDt.istype(DataType.UBYTE)) {
|
||||
// useless lsb() of byte value
|
||||
return listOf(IAstModification.ReplaceNode(functionCall, arg, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(functionCall.target.nameInSource == listOf("msb")) {
|
||||
val arg = functionCall.args[0]
|
||||
if(arg is TypecastExpression) {
|
||||
val valueDt = arg.expression.inferType(program)
|
||||
if (valueDt.istype(DataType.BYTE) || valueDt.istype(DataType.UBYTE)) {
|
||||
// useless msb() of byte value that was casted to word, replace with 0
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
functionCall,
|
||||
NumericLiteralValue(valueDt.typeOrElse(DataType.UBYTE), 0, arg.expression.position),
|
||||
parent))
|
||||
}
|
||||
} else {
|
||||
val argDt = arg.inferType(program)
|
||||
if (argDt.istype(DataType.BYTE) || argDt.istype(DataType.UBYTE)) {
|
||||
// useless msb() of byte value, replace with 0
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
functionCall,
|
||||
NumericLiteralValue(argDt.typeOrElse(DataType.UBYTE), 0, arg.position),
|
||||
parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun determineY(x: Expression, subBinExpr: BinaryExpression): Expression? {
|
||||
@ -258,6 +327,14 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
}
|
||||
|
||||
private fun optimizeAdd(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? {
|
||||
if(expr.left.isSameAs(expr.right)) {
|
||||
// optimize X+X into X *2
|
||||
expr.operator = "*"
|
||||
expr.right = NumericLiteralValue.optimalInteger(2, expr.right.position)
|
||||
expr.right.linkParents(expr)
|
||||
return expr
|
||||
}
|
||||
|
||||
if (leftVal == null && rightVal == null)
|
||||
return null
|
||||
|
||||
@ -274,21 +351,37 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
}
|
||||
// no need to check for left val constant (because of associativity)
|
||||
|
||||
val rnum = rightVal?.number?.toDouble()
|
||||
if(rnum!=null && rnum<0.0) {
|
||||
expr.operator = "-"
|
||||
expr.right = NumericLiteralValue(rightVal.type, -rnum, rightVal.position)
|
||||
return expr
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun optimizeSub(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? {
|
||||
if(expr.left.isSameAs(expr.right)) {
|
||||
// optimize X-X into 0
|
||||
return NumericLiteralValue.optimalInteger(0, expr.position)
|
||||
}
|
||||
|
||||
if (leftVal == null && rightVal == null)
|
||||
return null
|
||||
|
||||
if (rightVal != null) {
|
||||
// right value is a constant, see if we can optimize
|
||||
val rightConst: NumericLiteralValue = rightVal
|
||||
when (rightConst.number.toDouble()) {
|
||||
0.0 -> {
|
||||
// left
|
||||
return expr.left
|
||||
}
|
||||
val rnum = rightVal.number.toDouble()
|
||||
if (rnum == 0.0) {
|
||||
// left
|
||||
return expr.left
|
||||
}
|
||||
|
||||
if(rnum<0.0) {
|
||||
expr.operator = "+"
|
||||
expr.right = NumericLiteralValue(rightVal.type, -rnum, rightVal.position)
|
||||
return expr
|
||||
}
|
||||
}
|
||||
if (leftVal != null) {
|
||||
@ -301,6 +394,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@ -383,7 +477,10 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
when (expr.operator) {
|
||||
"%" -> {
|
||||
if (cv == 1.0) {
|
||||
return NumericLiteralValue(expr.inferType(program).typeOrElse(DataType.STRUCT), 0, expr.position)
|
||||
val idt = expr.inferType(program)
|
||||
if(!idt.isKnown)
|
||||
throw FatalAstException("unknown dt")
|
||||
return NumericLiteralValue(idt.typeOrElse(DataType.STRUCT), 0, expr.position)
|
||||
} else if (cv == 2.0) {
|
||||
expr.operator = "&"
|
||||
expr.right = NumericLiteralValue.optimalInteger(1, expr.position)
|
||||
@ -512,8 +609,10 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
if (amount == 0) {
|
||||
return expr.left
|
||||
}
|
||||
val targetDt = expr.left.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
when (targetDt) {
|
||||
val targetIDt = expr.left.inferType(program)
|
||||
if(!targetIDt.isKnown)
|
||||
throw FatalAstException("unknown dt")
|
||||
when (val targetDt = targetIDt.typeOrElse(DataType.STRUCT)) {
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
if (amount >= 8) {
|
||||
return NumericLiteralValue(targetDt, 0, expr.position)
|
||||
@ -523,12 +622,12 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
if (amount >= 16) {
|
||||
return NumericLiteralValue(targetDt, 0, expr.position)
|
||||
} else if (amount >= 8) {
|
||||
val lsb = TypecastExpression(expr.left, DataType.UBYTE, true, expr.position)
|
||||
val lsb = FunctionCall(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
|
||||
if (amount == 8) {
|
||||
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(NumericLiteralValue.optimalInteger(0, expr.position), lsb), expr.position)
|
||||
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(lsb, NumericLiteralValue.optimalInteger(0, expr.position)), expr.position)
|
||||
}
|
||||
val shifted = BinaryExpression(lsb, "<<", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position)
|
||||
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(NumericLiteralValue.optimalInteger(0, expr.position), shifted), expr.position)
|
||||
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(shifted, NumericLiteralValue.optimalInteger(0, expr.position)), expr.position)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
@ -545,8 +644,10 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
if (amount == 0) {
|
||||
return expr.left
|
||||
}
|
||||
val targetDt = expr.left.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
when (targetDt) {
|
||||
val idt = expr.left.inferType(program)
|
||||
if(!idt.isKnown)
|
||||
throw FatalAstException("unknown dt")
|
||||
when (idt.typeOrElse(DataType.STRUCT)) {
|
||||
DataType.UBYTE -> {
|
||||
if (amount >= 8) {
|
||||
return NumericLiteralValue.optimalInteger(0, expr.position)
|
||||
@ -563,8 +664,9 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
return NumericLiteralValue.optimalInteger(0, expr.position)
|
||||
} else if (amount >= 8) {
|
||||
val msb = FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
|
||||
if (amount == 8)
|
||||
return msb
|
||||
if (amount == 8) {
|
||||
return TypecastExpression(msb, DataType.UWORD, true, expr.position)
|
||||
}
|
||||
return BinaryExpression(msb, ">>", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position)
|
||||
}
|
||||
}
|
||||
@ -572,14 +674,6 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
if (amount > 16) {
|
||||
expr.right = NumericLiteralValue.optimalInteger(16, expr.right.position)
|
||||
return null
|
||||
} else if (amount >= 8) {
|
||||
val msbAsByte = TypecastExpression(
|
||||
FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position),
|
||||
DataType.BYTE,
|
||||
true, expr.position)
|
||||
if (amount == 8)
|
||||
return msbAsByte
|
||||
return BinaryExpression(msbAsByte, ">>", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
|
@ -5,12 +5,32 @@ import prog8.ast.base.ErrorReporter
|
||||
|
||||
|
||||
internal fun Program.constantFold(errors: ErrorReporter) {
|
||||
val optimizer = ConstantFoldingOptimizer(this, errors)
|
||||
optimizer.visit(this)
|
||||
val valuetypefixer = VarConstantValueTypeAdjuster(this)
|
||||
valuetypefixer.visit(this)
|
||||
if(errors.isEmpty()) {
|
||||
valuetypefixer.applyModifications()
|
||||
|
||||
while(errors.isEmpty() && optimizer.optimizationsDone>0) {
|
||||
optimizer.optimizationsDone = 0
|
||||
optimizer.visit(this)
|
||||
val replacer = ConstantIdentifierReplacer(this, errors)
|
||||
replacer.visit(this)
|
||||
if (errors.isEmpty()) {
|
||||
replacer.applyModifications()
|
||||
|
||||
valuetypefixer.visit(this)
|
||||
if(errors.isEmpty()) {
|
||||
valuetypefixer.applyModifications()
|
||||
|
||||
val optimizer = ConstantFoldingOptimizer(this)
|
||||
optimizer.visit(this)
|
||||
while (errors.isEmpty() && optimizer.applyModifications() > 0) {
|
||||
optimizer.visit(this)
|
||||
}
|
||||
|
||||
if (errors.isEmpty()) {
|
||||
replacer.visit(this)
|
||||
replacer.applyModifications()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(errors.isEmpty())
|
||||
@ -21,9 +41,11 @@ internal fun Program.constantFold(errors: ErrorReporter) {
|
||||
internal fun Program.optimizeStatements(errors: ErrorReporter): Int {
|
||||
val optimizer = StatementOptimizer(this, errors)
|
||||
optimizer.visit(this)
|
||||
val optimizationCount = optimizer.applyModifications()
|
||||
|
||||
modules.forEach { it.linkParents(this.namespace) } // re-link in final configuration
|
||||
|
||||
return optimizer.optimizationsDone
|
||||
return optimizationCount
|
||||
}
|
||||
|
||||
internal fun Program.simplifyExpressions() : Int {
|
||||
@ -31,3 +53,9 @@ internal fun Program.simplifyExpressions() : Int {
|
||||
opti.visit(this)
|
||||
return opti.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.splitBinaryExpressions() : Int {
|
||||
val opti = BinExprSplitter(this)
|
||||
opti.visit(this)
|
||||
return opti.applyModifications()
|
||||
}
|
||||
|
@ -1,46 +0,0 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.processing.IAstVisitor
|
||||
import prog8.ast.statements.AnonymousScope
|
||||
import prog8.ast.statements.NopStatement
|
||||
import prog8.ast.statements.Statement
|
||||
|
||||
internal class FlattenAnonymousScopesAndNopRemover: IAstVisitor {
|
||||
private var scopesToFlatten = mutableListOf<INameScope>()
|
||||
private val nopStatements = mutableListOf<NopStatement>()
|
||||
|
||||
override fun visit(program: Program) {
|
||||
super.visit(program)
|
||||
for(scope in scopesToFlatten.reversed()) {
|
||||
val namescope = scope.parent as INameScope
|
||||
val idx = namescope.statements.indexOf(scope as Statement)
|
||||
if(idx>=0) {
|
||||
val nop = NopStatement.insteadOf(namescope.statements[idx])
|
||||
nop.parent = namescope as Node
|
||||
namescope.statements[idx] = nop
|
||||
namescope.statements.addAll(idx, scope.statements)
|
||||
scope.statements.forEach { it.parent = namescope }
|
||||
visit(nop)
|
||||
}
|
||||
}
|
||||
|
||||
this.nopStatements.forEach {
|
||||
it.definingScope().remove(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(scope: AnonymousScope) {
|
||||
if(scope.parent is INameScope) {
|
||||
scopesToFlatten.add(scope) // get rid of the anonymous scope
|
||||
}
|
||||
|
||||
return super.visit(scope)
|
||||
}
|
||||
|
||||
override fun visit(nopStatement: NopStatement) {
|
||||
nopStatements.add(nopStatement)
|
||||
}
|
||||
}
|
@ -1,173 +1,86 @@
|
||||
package prog8.optimizer
|
||||
|
||||
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.processing.IAstModifyingVisitor
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModification
|
||||
import prog8.ast.processing.IAstVisitor
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.functions.BuiltinFunctions
|
||||
import kotlin.math.floor
|
||||
|
||||
|
||||
/*
|
||||
TODO: remove unreachable code after return and exit()
|
||||
TODO: proper inlining of tiny subroutines (at first, restrict to subs without parameters and variables in them, and build it up from there: correctly renaming/relocating all variables in them and refs to those as well)
|
||||
*/
|
||||
|
||||
|
||||
internal class StatementOptimizer(private val program: Program,
|
||||
private val errors: ErrorReporter) : IAstModifyingVisitor {
|
||||
var optimizationsDone: Int = 0
|
||||
private set
|
||||
private val errors: ErrorReporter) : AstWalker() {
|
||||
|
||||
private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure }
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
private val callgraph = CallGraph(program)
|
||||
private val vardeclsToRemove = mutableListOf<VarDecl>()
|
||||
private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure }
|
||||
|
||||
override fun visit(program: Program) {
|
||||
removeUnusedCode(callgraph)
|
||||
super.visit(program)
|
||||
|
||||
for(decl in vardeclsToRemove) {
|
||||
decl.definingScope().remove(decl)
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeUnusedCode(callgraph: CallGraph) {
|
||||
// remove all subroutines that aren't called, or are empty
|
||||
val removeSubroutines = mutableSetOf<Subroutine>()
|
||||
val entrypoint = program.entrypoint()
|
||||
program.modules.forEach {
|
||||
callgraph.forAllSubroutines(it) { sub ->
|
||||
if (sub !== entrypoint && !sub.keepAlways && (sub.calledBy.isEmpty() || (sub.containsNoCodeNorVars() && !sub.isAsmSubroutine)))
|
||||
removeSubroutines.add(sub)
|
||||
}
|
||||
}
|
||||
|
||||
if (removeSubroutines.isNotEmpty()) {
|
||||
removeSubroutines.forEach {
|
||||
it.definingScope().remove(it)
|
||||
}
|
||||
}
|
||||
|
||||
val removeBlocks = mutableSetOf<Block>()
|
||||
program.modules.flatMap { it.statements }.filterIsInstance<Block>().forEach { block ->
|
||||
if (block.containsNoCodeNorVars() && "force_output" !in block.options())
|
||||
removeBlocks.add(block)
|
||||
}
|
||||
|
||||
if (removeBlocks.isNotEmpty()) {
|
||||
removeBlocks.forEach { it.definingScope().remove(it) }
|
||||
}
|
||||
|
||||
// remove modules that are not imported, or are empty (unless it's a library modules)
|
||||
val removeModules = mutableSetOf<Module>()
|
||||
program.modules.forEach {
|
||||
if (!it.isLibraryModule && (it.importedBy.isEmpty() || it.containsNoCodeNorVars()))
|
||||
removeModules.add(it)
|
||||
}
|
||||
|
||||
if (removeModules.isNotEmpty()) {
|
||||
program.modules.removeAll(removeModules)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(block: Block): Statement {
|
||||
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
|
||||
if("force_output" !in block.options()) {
|
||||
if (block.containsNoCodeNorVars()) {
|
||||
optimizationsDone++
|
||||
errors.warn("removing empty block '${block.name}'", block.position)
|
||||
return NopStatement.insteadOf(block)
|
||||
return listOf(IAstModification.Remove(block, parent as INameScope))
|
||||
}
|
||||
|
||||
if (block !in callgraph.usedSymbols) {
|
||||
optimizationsDone++
|
||||
errors.warn("removing unused block '${block.name}'", block.position)
|
||||
return NopStatement.insteadOf(block) // remove unused block
|
||||
return listOf(IAstModification.Remove(block, parent as INameScope))
|
||||
}
|
||||
}
|
||||
|
||||
return super.visit(block)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun visit(subroutine: Subroutine): Statement {
|
||||
super.visit(subroutine)
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
val forceOutput = "force_output" in subroutine.definingBlock().options()
|
||||
if(subroutine.asmAddress==null && !forceOutput) {
|
||||
if(subroutine.containsNoCodeNorVars()) {
|
||||
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(subroutine)
|
||||
val removals = callgraph.calledBy.getValue(subroutine).map {
|
||||
IAstModification.Remove(it, it.definingScope())
|
||||
}.toMutableList()
|
||||
removals += IAstModification.Remove(subroutine, subroutine.definingScope())
|
||||
return removals
|
||||
}
|
||||
}
|
||||
|
||||
val linesToRemove = deduplicateAssignments(subroutine.statements)
|
||||
if(linesToRemove.isNotEmpty()) {
|
||||
linesToRemove.reversed().forEach{subroutine.statements.removeAt(it)}
|
||||
}
|
||||
|
||||
if(subroutine !in callgraph.usedSymbols && !forceOutput) {
|
||||
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(subroutine)
|
||||
return listOf(IAstModification.Remove(subroutine, subroutine.definingScope()))
|
||||
}
|
||||
|
||||
return subroutine
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun visit(decl: VarDecl): Statement {
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
val forceOutput = "force_output" in decl.definingBlock().options()
|
||||
if(decl !in callgraph.usedSymbols && !forceOutput) {
|
||||
if(decl.type == VarDeclType.VAR)
|
||||
errors.warn("removing unused variable ${decl.type} '${decl.name}'", decl.position)
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(decl)
|
||||
errors.warn("removing unused variable '${decl.name}'", decl.position)
|
||||
|
||||
return listOf(IAstModification.Remove(decl, decl.definingScope()))
|
||||
}
|
||||
|
||||
return super.visit(decl)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun deduplicateAssignments(statements: List<Statement>): MutableList<Int> {
|
||||
// removes 'duplicate' assignments that assign the isSameAs target
|
||||
val linesToRemove = mutableListOf<Int>()
|
||||
var previousAssignmentLine: Int? = null
|
||||
for (i in statements.indices) {
|
||||
val stmt = statements[i] as? Assignment
|
||||
if (stmt != null && stmt.value is NumericLiteralValue) {
|
||||
if (previousAssignmentLine == null) {
|
||||
previousAssignmentLine = i
|
||||
continue
|
||||
} else {
|
||||
val prev = statements[previousAssignmentLine] as Assignment
|
||||
if (prev.target.isSameAs(stmt.target, program)) {
|
||||
// get rid of the previous assignment, if the target is not MEMORY
|
||||
if (prev.target.isNotMemory(program.namespace))
|
||||
linesToRemove.add(previousAssignmentLine)
|
||||
}
|
||||
previousAssignmentLine = i
|
||||
}
|
||||
} else
|
||||
previousAssignmentLine = null
|
||||
}
|
||||
return linesToRemove
|
||||
}
|
||||
|
||||
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in BuiltinFunctions) {
|
||||
val functionName = functionCallStatement.target.nameInSource[0]
|
||||
if (functionName in pureBuiltinFunctions) {
|
||||
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(functionCallStatement)
|
||||
return listOf(IAstModification.Remove(functionCallStatement, functionCallStatement.definingScope()))
|
||||
}
|
||||
}
|
||||
|
||||
if(functionCallStatement.target.nameInSource==listOf("c64scr", "print") ||
|
||||
functionCallStatement.target.nameInSource==listOf("c64scr", "print_p")) {
|
||||
// printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters
|
||||
// printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters
|
||||
// this is a C-64 specific optimization
|
||||
if(functionCallStatement.target.nameInSource==listOf("c64scr", "print")) {
|
||||
val arg = functionCallStatement.args.single()
|
||||
val stringVar: IdentifierReference?
|
||||
stringVar = if(arg is AddressOf) {
|
||||
@ -177,85 +90,78 @@ internal class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
if(stringVar!=null) {
|
||||
val vardecl = stringVar.targetVarDecl(program.namespace)!!
|
||||
val string = vardecl.value!! as StringLiteralValue
|
||||
if(string.value.length==1) {
|
||||
val firstCharEncoded = CompilationTarget.encodeString(string.value, string.altEncoding)[0]
|
||||
functionCallStatement.args.clear()
|
||||
functionCallStatement.args.add(NumericLiteralValue.optimalInteger(firstCharEncoded.toInt(), functionCallStatement.position))
|
||||
functionCallStatement.target = IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position)
|
||||
vardeclsToRemove.add(vardecl)
|
||||
optimizationsDone++
|
||||
return functionCallStatement
|
||||
} else if(string.value.length==2) {
|
||||
val firstTwoCharsEncoded = CompilationTarget.encodeString(string.value.take(2), string.altEncoding)
|
||||
val scope = AnonymousScope(mutableListOf(), functionCallStatement.position)
|
||||
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position),
|
||||
mutableListOf(NumericLiteralValue.optimalInteger(firstTwoCharsEncoded[0].toInt(), functionCallStatement.position)),
|
||||
functionCallStatement.void, functionCallStatement.position))
|
||||
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position),
|
||||
mutableListOf(NumericLiteralValue.optimalInteger(firstTwoCharsEncoded[1].toInt(), functionCallStatement.position)),
|
||||
functionCallStatement.void, functionCallStatement.position))
|
||||
vardeclsToRemove.add(vardecl)
|
||||
optimizationsDone++
|
||||
return scope
|
||||
val string = vardecl.value as? StringLiteralValue
|
||||
if(string!=null) {
|
||||
val pos = functionCallStatement.position
|
||||
if (string.value.length == 1) {
|
||||
val firstCharEncoded = CompilationTarget.instance.encodeString(string.value, string.altEncoding)[0]
|
||||
val chrout = FunctionCallStatement(
|
||||
IdentifierReference(listOf("c64", "CHROUT"), pos),
|
||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toInt(), pos)),
|
||||
functionCallStatement.void, pos
|
||||
)
|
||||
return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent))
|
||||
} else if (string.value.length == 2) {
|
||||
val firstTwoCharsEncoded = CompilationTarget.instance.encodeString(string.value.take(2), string.altEncoding)
|
||||
val chrout1 = FunctionCallStatement(
|
||||
IdentifierReference(listOf("c64", "CHROUT"), pos),
|
||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toInt(), pos)),
|
||||
functionCallStatement.void, pos
|
||||
)
|
||||
val chrout2 = FunctionCallStatement(
|
||||
IdentifierReference(listOf("c64", "CHROUT"), pos),
|
||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toInt(), pos)),
|
||||
functionCallStatement.void, pos
|
||||
)
|
||||
val anonscope = AnonymousScope(mutableListOf(), pos)
|
||||
anonscope.statements.add(chrout1)
|
||||
anonscope.statements.add(chrout2)
|
||||
return listOf(IAstModification.ReplaceNode(functionCallStatement, anonscope, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if it calls a subroutine,
|
||||
// and the first instruction in the subroutine is a jump, call that jump target instead
|
||||
// if the first instruction in the subroutine is a return statement, replace with a nop instruction
|
||||
// if the first instruction in the called subroutine is a return statement, remove the jump altogeter
|
||||
val subroutine = functionCallStatement.target.targetSubroutine(program.namespace)
|
||||
if(subroutine!=null) {
|
||||
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||
if(first is Jump && first.identifier!=null) {
|
||||
optimizationsDone++
|
||||
return FunctionCallStatement(first.identifier, functionCallStatement.args, functionCallStatement.void, functionCallStatement.position)
|
||||
}
|
||||
if(first is ReturnFromIrq || first is Return) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(functionCallStatement)
|
||||
}
|
||||
if(first is Return)
|
||||
return listOf(IAstModification.Remove(functionCallStatement, functionCallStatement.definingScope()))
|
||||
}
|
||||
|
||||
return super.visit(functionCallStatement)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun visit(functionCall: FunctionCall): Expression {
|
||||
// if it calls a subroutine,
|
||||
// and the first instruction in the subroutine is a jump, call that jump target instead
|
||||
// if the first instruction in the subroutine is a return statement with constant value, replace with the constant value
|
||||
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||
// if the first instruction in the called subroutine is a return statement with constant value, replace with the constant value
|
||||
val subroutine = functionCall.target.targetSubroutine(program.namespace)
|
||||
if(subroutine!=null) {
|
||||
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||
if(first is Jump && first.identifier!=null) {
|
||||
optimizationsDone++
|
||||
return FunctionCall(first.identifier, functionCall.args, functionCall.position)
|
||||
}
|
||||
if(first is Return && first.value!=null) {
|
||||
val constval = first.value?.constValue(program)
|
||||
if(constval!=null)
|
||||
return constval
|
||||
return listOf(IAstModification.ReplaceNode(functionCall, constval, parent))
|
||||
}
|
||||
}
|
||||
return super.visit(functionCall)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun visit(ifStatement: IfStatement): Statement {
|
||||
super.visit(ifStatement)
|
||||
|
||||
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsNoCodeNorVars()) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(ifStatement)
|
||||
}
|
||||
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
|
||||
// remove empty if statements
|
||||
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsNoCodeNorVars())
|
||||
return listOf(IAstModification.Remove(ifStatement, ifStatement.definingScope()))
|
||||
|
||||
// empty true part? switch with the else part
|
||||
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsCodeOrVars()) {
|
||||
// invert the condition and move else part to true part
|
||||
ifStatement.truepart = ifStatement.elsepart
|
||||
ifStatement.elsepart = AnonymousScope(mutableListOf(), ifStatement.elsepart.position)
|
||||
ifStatement.condition = PrefixExpression("not", ifStatement.condition, ifStatement.condition.position)
|
||||
optimizationsDone++
|
||||
return ifStatement
|
||||
val invertedCondition = PrefixExpression("not", ifStatement.condition, ifStatement.condition.position)
|
||||
val emptyscope = AnonymousScope(mutableListOf(), ifStatement.elsepart.position)
|
||||
val truepart = AnonymousScope(ifStatement.elsepart.statements, ifStatement.truepart.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(ifStatement.condition, invertedCondition, ifStatement),
|
||||
IAstModification.ReplaceNode(ifStatement.truepart, truepart, ifStatement),
|
||||
IAstModification.ReplaceNode(ifStatement.elsepart, emptyscope, ifStatement)
|
||||
)
|
||||
}
|
||||
|
||||
val constvalue = ifStatement.condition.constValue(program)
|
||||
@ -263,115 +169,289 @@ internal class StatementOptimizer(private val program: Program,
|
||||
return if(constvalue.asBooleanValue){
|
||||
// always true -> keep only if-part
|
||||
errors.warn("condition is always true", ifStatement.position)
|
||||
optimizationsDone++
|
||||
ifStatement.truepart
|
||||
listOf(IAstModification.ReplaceNode(ifStatement, ifStatement.truepart, parent))
|
||||
} else {
|
||||
// always false -> keep only else-part
|
||||
errors.warn("condition is always false", ifStatement.position)
|
||||
optimizationsDone++
|
||||
ifStatement.elsepart
|
||||
listOf(IAstModification.ReplaceNode(ifStatement, ifStatement.elsepart, parent))
|
||||
}
|
||||
}
|
||||
return ifStatement
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun visit(forLoop: ForLoop): Statement {
|
||||
super.visit(forLoop)
|
||||
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
|
||||
if(forLoop.body.containsNoCodeNorVars()) {
|
||||
// remove empty for loop
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(forLoop)
|
||||
errors.warn("removing empty for loop", forLoop.position)
|
||||
return listOf(IAstModification.Remove(forLoop, forLoop.definingScope()))
|
||||
} else if(forLoop.body.statements.size==1) {
|
||||
val loopvar = forLoop.body.statements[0] as? VarDecl
|
||||
if(loopvar!=null && loopvar.name==forLoop.loopVar?.nameInSource?.singleOrNull()) {
|
||||
// remove empty for loop
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(forLoop)
|
||||
if(loopvar!=null && loopvar.name==forLoop.loopVar.nameInSource.singleOrNull()) {
|
||||
// remove empty for loop (only loopvar decl in it)
|
||||
return listOf(IAstModification.Remove(forLoop, forLoop.definingScope()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val range = forLoop.iterable as? RangeExpr
|
||||
if(range!=null) {
|
||||
if(range.size()==1) {
|
||||
// for loop over a (constant) range of just a single value-- optimize the loop away
|
||||
// loopvar/reg = range value , follow by block
|
||||
val assignment = Assignment(AssignTarget(forLoop.loopRegister, forLoop.loopVar, null, null, forLoop.position), null, range.from, forLoop.position)
|
||||
forLoop.body.statements.add(0, assignment)
|
||||
optimizationsDone++
|
||||
return forLoop.body
|
||||
val scope = AnonymousScope(mutableListOf(), forLoop.position)
|
||||
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), range.from, forLoop.position))
|
||||
scope.statements.addAll(forLoop.body.statements)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
|
||||
}
|
||||
}
|
||||
return forLoop
|
||||
val iterable = (forLoop.iterable as? IdentifierReference)?.targetVarDecl(program.namespace)
|
||||
if(iterable!=null) {
|
||||
if(iterable.datatype==DataType.STR) {
|
||||
val sv = iterable.value as StringLiteralValue
|
||||
val size = sv.value.length
|
||||
if(size==1) {
|
||||
// loop over string of length 1 -> just assign the single character
|
||||
val character = CompilationTarget.instance.encodeString(sv.value, sv.altEncoding)[0]
|
||||
val byte = NumericLiteralValue(DataType.UBYTE, character, iterable.position)
|
||||
val scope = AnonymousScope(mutableListOf(), forLoop.position)
|
||||
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), byte, forLoop.position))
|
||||
scope.statements.addAll(forLoop.body.statements)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
|
||||
}
|
||||
}
|
||||
else if(iterable.datatype in ArrayDatatypes) {
|
||||
val size = iterable.arraysize!!.constIndex()
|
||||
if(size==1) {
|
||||
// loop over array of length 1 -> just assign the single value
|
||||
val av = (iterable.value as ArrayLiteralValue).value[0].constValue(program)?.number
|
||||
if(av!=null) {
|
||||
val scope = AnonymousScope(mutableListOf(), forLoop.position)
|
||||
scope.statements.add(Assignment(
|
||||
AssignTarget(forLoop.loopVar, null, null, forLoop.position), NumericLiteralValue.optimalInteger(av.toInt(), iterable.position),
|
||||
forLoop.position))
|
||||
scope.statements.addAll(forLoop.body.statements)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun visit(whileLoop: WhileLoop): Statement {
|
||||
super.visit(whileLoop)
|
||||
override fun before(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
|
||||
val constvalue = untilLoop.condition.constValue(program)
|
||||
if(constvalue!=null) {
|
||||
if(constvalue.asBooleanValue) {
|
||||
// always true -> keep only the statement block (if there are no break statements)
|
||||
errors.warn("condition is always true", untilLoop.condition.position)
|
||||
if(!hasBreak(untilLoop.body))
|
||||
return listOf(IAstModification.ReplaceNode(untilLoop, untilLoop.body, parent))
|
||||
} else {
|
||||
// always false
|
||||
val forever = RepeatLoop(null, untilLoop.body, untilLoop.position)
|
||||
return listOf(IAstModification.ReplaceNode(untilLoop, forever, parent))
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
|
||||
val constvalue = whileLoop.condition.constValue(program)
|
||||
if(constvalue!=null) {
|
||||
return if(constvalue.asBooleanValue){
|
||||
// always true -> print a warning, and optimize into a forever-loop
|
||||
errors.warn("condition is always true", whileLoop.condition.position)
|
||||
optimizationsDone++
|
||||
ForeverLoop(whileLoop.body, whileLoop.position)
|
||||
return if(constvalue.asBooleanValue) {
|
||||
// always true
|
||||
val forever = RepeatLoop(null, whileLoop.body, whileLoop.position)
|
||||
listOf(IAstModification.ReplaceNode(whileLoop, forever, parent))
|
||||
} else {
|
||||
// always false -> remove the while statement altogether
|
||||
errors.warn("condition is always false", whileLoop.condition.position)
|
||||
optimizationsDone++
|
||||
NopStatement.insteadOf(whileLoop)
|
||||
listOf(IAstModification.Remove(whileLoop, whileLoop.definingScope()))
|
||||
}
|
||||
}
|
||||
return whileLoop
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun visit(repeatLoop: RepeatLoop): Statement {
|
||||
super.visit(repeatLoop)
|
||||
val constvalue = repeatLoop.untilCondition.constValue(program)
|
||||
if(constvalue!=null) {
|
||||
return if(constvalue.asBooleanValue){
|
||||
// always true -> keep only the statement block (if there are no continue and break statements)
|
||||
errors.warn("condition is always true", repeatLoop.untilCondition.position)
|
||||
if(hasContinueOrBreak(repeatLoop.body))
|
||||
repeatLoop
|
||||
else {
|
||||
optimizationsDone++
|
||||
repeatLoop.body
|
||||
}
|
||||
} else {
|
||||
// always false -> print a warning, and optimize into a forever loop
|
||||
errors.warn("condition is always false", repeatLoop.untilCondition.position)
|
||||
optimizationsDone++
|
||||
ForeverLoop(repeatLoop.body, repeatLoop.position)
|
||||
override fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> {
|
||||
val iter = repeatLoop.iterations
|
||||
if(iter!=null) {
|
||||
if(repeatLoop.body.containsNoCodeNorVars()) {
|
||||
errors.warn("empty loop removed", repeatLoop.position)
|
||||
return listOf(IAstModification.Remove(repeatLoop, repeatLoop.definingScope()))
|
||||
}
|
||||
val iterations = iter.constValue(program)?.number?.toInt()
|
||||
if (iterations == 0) {
|
||||
errors.warn("iterations is always 0, removed loop", iter.position)
|
||||
return listOf(IAstModification.Remove(repeatLoop, repeatLoop.definingScope()))
|
||||
}
|
||||
if (iterations == 1) {
|
||||
errors.warn("iterations is always 1", iter.position)
|
||||
return listOf(IAstModification.ReplaceNode(repeatLoop, repeatLoop.body, parent))
|
||||
}
|
||||
}
|
||||
return repeatLoop
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun visit(whenStatement: WhenStatement): Statement {
|
||||
val choices = whenStatement.choices.toList()
|
||||
for(choice in choices) {
|
||||
if(choice.statements.containsNoCodeNorVars())
|
||||
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
|
||||
// remove empty choices
|
||||
class ChoiceRemover(val choice: WhenChoice) : IAstModification {
|
||||
override fun perform() {
|
||||
whenStatement.choices.remove(choice)
|
||||
}
|
||||
}
|
||||
return super.visit(whenStatement)
|
||||
return whenStatement.choices
|
||||
.filter { !it.statements.containsCodeOrVars() }
|
||||
.map { ChoiceRemover(it) }
|
||||
}
|
||||
|
||||
private fun hasContinueOrBreak(scope: INameScope): Boolean {
|
||||
override fun after(jump: Jump, parent: Node): Iterable<IAstModification> {
|
||||
// if the jump is to the next statement, remove the jump
|
||||
val scope = jump.definingScope()
|
||||
val label = jump.identifier?.targetStatement(scope)
|
||||
if(label!=null && scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1)
|
||||
return listOf(IAstModification.Remove(jump, jump.definingScope()))
|
||||
|
||||
class Searcher: IAstModifyingVisitor
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
if(binExpr!=null) {
|
||||
if(binExpr.left isSameAs assignment.target) {
|
||||
val rExpr = binExpr.right as? BinaryExpression
|
||||
if(rExpr!=null) {
|
||||
val op1 = binExpr.operator
|
||||
val op2 = rExpr.operator
|
||||
|
||||
if(rExpr.left is NumericLiteralValue && op2 in setOf("+", "*", "&", "|")) {
|
||||
// associative operator, make sure the constant numeric value is second (right)
|
||||
return listOf(IAstModification.SwapOperands(rExpr))
|
||||
}
|
||||
|
||||
val rNum = (rExpr.right as? NumericLiteralValue)?.number
|
||||
if(rNum!=null) {
|
||||
if (op1 == "+" || op1 == "-") {
|
||||
if (op2 == "+") {
|
||||
// A = A +/- B + N
|
||||
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
|
||||
val addConstant = Assignment(
|
||||
assignment.target,
|
||||
BinaryExpression(binExpr.left, "+", rExpr.right, rExpr.position),
|
||||
assignment.position
|
||||
)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
|
||||
IAstModification.InsertAfter(assignment, addConstant, assignment.definingScope()))
|
||||
} else if (op2 == "-") {
|
||||
// A = A +/- B - N
|
||||
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
|
||||
val subConstant = Assignment(
|
||||
assignment.target,
|
||||
BinaryExpression(binExpr.left, "-", rExpr.right, rExpr.position),
|
||||
assignment.position
|
||||
)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
|
||||
IAstModification.InsertAfter(assignment, subConstant, assignment.definingScope()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(binExpr.operator in associativeOperators && binExpr.right isSameAs assignment.target) {
|
||||
// associative operator, swap the operands so that the assignment target is first (left)
|
||||
// unless the other operand is the same in which case we don't swap (endless loop!)
|
||||
if (!(binExpr.left isSameAs binExpr.right))
|
||||
return listOf(IAstModification.SwapOperands(binExpr))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
if(assignment.target isSameAs assignment.value) {
|
||||
// remove assignment to self
|
||||
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
}
|
||||
|
||||
val targetIDt = assignment.target.inferType(program)
|
||||
if(!targetIDt.isKnown)
|
||||
throw FatalAstException("can't infer type of assignment target")
|
||||
|
||||
// optimize binary expressions a bit
|
||||
val targetDt = targetIDt.typeOrElse(DataType.STRUCT)
|
||||
val bexpr=assignment.value as? BinaryExpression
|
||||
if(bexpr!=null) {
|
||||
val rightCv = bexpr.right.constValue(program)?.number?.toDouble()
|
||||
if (rightCv != null && assignment.target isSameAs bexpr.left) {
|
||||
// assignments of the form: X = X <operator> <expr>
|
||||
// remove assignments that have no effect (such as X=X+0)
|
||||
// optimize/rewrite some other expressions
|
||||
val vardeclDt = (assignment.target.identifier?.targetVarDecl(program.namespace))?.type
|
||||
when (bexpr.operator) {
|
||||
"+" -> {
|
||||
if (rightCv == 0.0) {
|
||||
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
|
||||
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0) {
|
||||
// replace by several INCs if it's not a memory address (inc on a memory mapped register doesn't work very well)
|
||||
val incs = AnonymousScope(mutableListOf(), assignment.position)
|
||||
repeat(rightCv.toInt()) {
|
||||
incs.statements.add(PostIncrDecr(assignment.target, "++", assignment.position))
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(assignment, incs, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
"-" -> {
|
||||
if (rightCv == 0.0) {
|
||||
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
|
||||
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0) {
|
||||
// replace by several DECs if it's not a memory address (dec on a memory mapped register doesn't work very well)
|
||||
val decs = AnonymousScope(mutableListOf(), assignment.position)
|
||||
repeat(rightCv.toInt()) {
|
||||
decs.statements.add(PostIncrDecr(assignment.target, "--", assignment.position))
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(assignment, decs, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
"*" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
"/" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
"**" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
"|" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
"^" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
"<<" -> {
|
||||
if (rightCv == 0.0)
|
||||
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
}
|
||||
">>" -> {
|
||||
if (rightCv == 0.0)
|
||||
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun hasBreak(scope: INameScope): Boolean {
|
||||
|
||||
class Searcher: IAstVisitor
|
||||
{
|
||||
var count=0
|
||||
|
||||
override fun visit(breakStmt: Break): Statement {
|
||||
override fun visit(breakStmt: Break) {
|
||||
count++
|
||||
return super.visit(breakStmt)
|
||||
}
|
||||
|
||||
override fun visit(contStmt: Continue): Statement {
|
||||
count++
|
||||
return super.visit(contStmt)
|
||||
}
|
||||
}
|
||||
|
||||
val s=Searcher()
|
||||
for(stmt in scope.statements) {
|
||||
stmt.accept(s)
|
||||
@ -381,185 +461,4 @@ internal class StatementOptimizer(private val program: Program,
|
||||
return s.count > 0
|
||||
}
|
||||
|
||||
override fun visit(jump: Jump): Statement {
|
||||
val subroutine = jump.identifier?.targetSubroutine(program.namespace)
|
||||
if(subroutine!=null) {
|
||||
// if the first instruction in the subroutine is another jump, shortcut this one
|
||||
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||
if(first is Jump) {
|
||||
optimizationsDone++
|
||||
return first
|
||||
}
|
||||
}
|
||||
|
||||
// if the jump is to the next statement, remove the jump
|
||||
val scope = jump.definingScope()
|
||||
val label = jump.identifier?.targetStatement(scope)
|
||||
if(label!=null) {
|
||||
if(scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(jump)
|
||||
}
|
||||
}
|
||||
|
||||
return jump
|
||||
}
|
||||
|
||||
override fun visit(assignment: Assignment): Statement {
|
||||
if(assignment.aug_op!=null)
|
||||
throw AstException("augmented assignments should have been converted to normal assignments before this optimizer: $assignment")
|
||||
|
||||
if(assignment.target isSameAs assignment.value) {
|
||||
if(assignment.target.isNotMemory(program.namespace)) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(assignment)
|
||||
}
|
||||
}
|
||||
val targetIDt = assignment.target.inferType(program, assignment)
|
||||
if(!targetIDt.isKnown)
|
||||
throw FatalAstException("can't infer type of assignment target")
|
||||
val targetDt = targetIDt.typeOrElse(DataType.STRUCT)
|
||||
val bexpr=assignment.value as? BinaryExpression
|
||||
if(bexpr!=null) {
|
||||
val cv = bexpr.right.constValue(program)?.number?.toDouble()
|
||||
if (cv == null) {
|
||||
if (bexpr.operator == "+" && targetDt != DataType.FLOAT) {
|
||||
if (bexpr.left isSameAs bexpr.right && assignment.target isSameAs bexpr.left) {
|
||||
bexpr.operator = "*"
|
||||
bexpr.right = NumericLiteralValue.optimalInteger(2, assignment.value.position)
|
||||
optimizationsDone++
|
||||
return assignment
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (assignment.target isSameAs bexpr.left) {
|
||||
// remove assignments that have no effect X=X , X+=0, X-=0, X*=1, X/=1, X//=1, A |= 0, A ^= 0, A<<=0, etc etc
|
||||
// A = A <operator> B
|
||||
val vardeclDt = (assignment.target.identifier?.targetVarDecl(program.namespace))?.type
|
||||
|
||||
when (bexpr.operator) {
|
||||
"+" -> {
|
||||
if (cv == 0.0) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(assignment)
|
||||
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
|
||||
if ((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt != VarDeclType.MEMORY && cv in 1.0..8.0)) {
|
||||
// replace by several INCs (a bit less when dealing with memory targets)
|
||||
val decs = AnonymousScope(mutableListOf(), assignment.position)
|
||||
repeat(cv.toInt()) {
|
||||
decs.statements.add(PostIncrDecr(assignment.target, "++", assignment.position))
|
||||
}
|
||||
return decs
|
||||
}
|
||||
}
|
||||
}
|
||||
"-" -> {
|
||||
if (cv == 0.0) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(assignment)
|
||||
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
|
||||
if ((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt != VarDeclType.MEMORY && cv in 1.0..8.0)) {
|
||||
// replace by several DECs (a bit less when dealing with memory targets)
|
||||
val decs = AnonymousScope(mutableListOf(), assignment.position)
|
||||
repeat(cv.toInt()) {
|
||||
decs.statements.add(PostIncrDecr(assignment.target, "--", assignment.position))
|
||||
}
|
||||
return decs
|
||||
}
|
||||
}
|
||||
}
|
||||
"*" -> if (cv == 1.0) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(assignment)
|
||||
}
|
||||
"/" -> if (cv == 1.0) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(assignment)
|
||||
}
|
||||
"**" -> if (cv == 1.0) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(assignment)
|
||||
}
|
||||
"|" -> if (cv == 0.0) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(assignment)
|
||||
}
|
||||
"^" -> if (cv == 0.0) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(assignment)
|
||||
}
|
||||
"<<" -> {
|
||||
if (cv == 0.0) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(assignment)
|
||||
}
|
||||
if (((targetDt == DataType.UWORD || targetDt == DataType.WORD) && cv > 15.0) ||
|
||||
((targetDt == DataType.UBYTE || targetDt == DataType.BYTE) && cv > 7.0)) {
|
||||
assignment.value = NumericLiteralValue.optimalInteger(0, assignment.value.position)
|
||||
assignment.value.linkParents(assignment)
|
||||
optimizationsDone++
|
||||
} else {
|
||||
// replace by in-place lsl(...) call
|
||||
val scope = AnonymousScope(mutableListOf(), assignment.position)
|
||||
var numshifts = cv.toInt()
|
||||
while (numshifts > 0) {
|
||||
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("lsl"), assignment.position),
|
||||
mutableListOf(bexpr.left), true, assignment.position))
|
||||
numshifts--
|
||||
}
|
||||
optimizationsDone++
|
||||
return scope
|
||||
}
|
||||
}
|
||||
">>" -> {
|
||||
if (cv == 0.0) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(assignment)
|
||||
}
|
||||
if ((targetDt == DataType.UWORD && cv > 15.0) || (targetDt == DataType.UBYTE && cv > 7.0)) {
|
||||
assignment.value = NumericLiteralValue.optimalInteger(0, assignment.value.position)
|
||||
assignment.value.linkParents(assignment)
|
||||
optimizationsDone++
|
||||
} else {
|
||||
// replace by in-place lsr(...) call
|
||||
val scope = AnonymousScope(mutableListOf(), assignment.position)
|
||||
var numshifts = cv.toInt()
|
||||
while (numshifts > 0) {
|
||||
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("lsr"), assignment.position),
|
||||
mutableListOf(bexpr.left), true, assignment.position))
|
||||
numshifts--
|
||||
}
|
||||
optimizationsDone++
|
||||
return scope
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return super.visit(assignment)
|
||||
}
|
||||
|
||||
override fun visit(scope: AnonymousScope): Statement {
|
||||
val linesToRemove = deduplicateAssignments(scope.statements)
|
||||
if(linesToRemove.isNotEmpty()) {
|
||||
linesToRemove.reversed().forEach{scope.statements.removeAt(it)}
|
||||
}
|
||||
return super.visit(scope)
|
||||
}
|
||||
|
||||
override fun visit(label: Label): Statement {
|
||||
// remove duplicate labels
|
||||
val stmts = label.definingScope().statements
|
||||
val startIdx = stmts.indexOf(label)
|
||||
if(startIdx< stmts.lastIndex && stmts[startIdx+1] == label)
|
||||
return NopStatement.insteadOf(label)
|
||||
|
||||
return super.visit(label)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
103
compiler/src/prog8/optimizer/UnusedCodeRemover.kt
Normal file
103
compiler/src/prog8/optimizer/UnusedCodeRemover.kt
Normal file
@ -0,0 +1,103 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModification
|
||||
import prog8.ast.statements.*
|
||||
|
||||
|
||||
internal class UnusedCodeRemover(private val program: Program, private val errors: ErrorReporter): AstWalker() {
|
||||
|
||||
override fun before(program: Program, parent: Node): Iterable<IAstModification> {
|
||||
val callgraph = CallGraph(program)
|
||||
val removals = mutableListOf<IAstModification>()
|
||||
|
||||
// remove all subroutines that aren't called, or are empty
|
||||
val entrypoint = program.entrypoint()
|
||||
program.modules.forEach {
|
||||
callgraph.forAllSubroutines(it) { sub ->
|
||||
if (sub !== entrypoint && !sub.isAsmSubroutine && (callgraph.calledBy[sub].isNullOrEmpty() || sub.containsNoCodeNorVars())) {
|
||||
removals.add(IAstModification.Remove(sub, sub.definingScope()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
program.modules.flatMap { it.statements }.filterIsInstance<Block>().forEach { block ->
|
||||
if (block.containsNoCodeNorVars() && "force_output" !in block.options())
|
||||
removals.add(IAstModification.Remove(block, block.definingScope()))
|
||||
}
|
||||
|
||||
// remove modules that are not imported, or are empty (unless it's a library modules)
|
||||
program.modules.forEach {
|
||||
if (!it.isLibraryModule && (it.importedBy.isEmpty() || it.containsNoCodeNorVars()))
|
||||
removals.add(IAstModification.Remove(it, it.definingScope()))
|
||||
}
|
||||
|
||||
return removals
|
||||
}
|
||||
|
||||
|
||||
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
|
||||
reportUnreachable(breakStmt, parent as INameScope)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun before(jump: Jump, parent: Node): Iterable<IAstModification> {
|
||||
reportUnreachable(jump, parent as INameScope)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> {
|
||||
reportUnreachable(returnStmt, parent as INameScope)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
if(functionCallStatement.target.nameInSource.last() == "exit")
|
||||
reportUnreachable(functionCallStatement, parent as INameScope)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
private fun reportUnreachable(stmt: Statement, parent: INameScope) {
|
||||
when(val next = parent.nextSibling(stmt)) {
|
||||
null, is Label, is Directive, is VarDecl, is InlineAssembly, is Subroutine, is StructDecl -> {}
|
||||
else -> errors.warn("unreachable code", next.position)
|
||||
}
|
||||
}
|
||||
|
||||
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
val removeDoubleAssignments = deduplicateAssignments(scope.statements)
|
||||
return removeDoubleAssignments.map { IAstModification.Remove(it, scope) }
|
||||
}
|
||||
|
||||
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
|
||||
val removeDoubleAssignments = deduplicateAssignments(block.statements)
|
||||
return removeDoubleAssignments.map { IAstModification.Remove(it, block) }
|
||||
}
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
val removeDoubleAssignments = deduplicateAssignments(subroutine.statements)
|
||||
return removeDoubleAssignments.map { IAstModification.Remove(it, subroutine) }
|
||||
}
|
||||
|
||||
private fun deduplicateAssignments(statements: List<Statement>): List<Assignment> {
|
||||
// removes 'duplicate' assignments that assign the same target directly after another
|
||||
val linesToRemove = mutableListOf<Assignment>()
|
||||
|
||||
for (stmtPairs in statements.windowed(2, step = 1)) {
|
||||
val assign1 = stmtPairs[0] as? Assignment
|
||||
val assign2 = stmtPairs[1] as? Assignment
|
||||
if (assign1 != null && assign2 != null && !assign2.isAugmentable) {
|
||||
if (assign1.target.isSameAs(assign2.target, program) && assign1.target.isInRegularRAM(program.namespace)) {
|
||||
if(assign2.target.identifier==null || !assign2.value.referencesIdentifier(*(assign2.target.identifier!!.nameInSource.toTypedArray())))
|
||||
linesToRemove.add(assign1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return linesToRemove
|
||||
}
|
||||
}
|
@ -4,12 +4,12 @@ import org.antlr.v4.runtime.*
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.antlr.toAst
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.base.SyntaxError
|
||||
import prog8.ast.base.checkImportedValid
|
||||
import prog8.ast.statements.Directive
|
||||
import prog8.ast.statements.DirectiveArg
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.pathFrom
|
||||
import java.io.InputStream
|
||||
import java.nio.file.Files
|
||||
@ -20,21 +20,13 @@ import java.nio.file.Paths
|
||||
internal class ParsingFailedError(override var message: String) : Exception(message)
|
||||
|
||||
|
||||
private class LexerErrorListener: BaseErrorListener() {
|
||||
var numberOfErrors: Int = 0
|
||||
override fun syntaxError(p0: Recognizer<*, *>?, p1: Any?, p2: Int, p3: Int, p4: String?, p5: RecognitionException?) {
|
||||
numberOfErrors++
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal class CustomLexer(val modulePath: Path, input: CharStream?) : prog8Lexer(input)
|
||||
|
||||
|
||||
internal fun moduleName(fileName: Path) = fileName.toString().substringBeforeLast('.')
|
||||
|
||||
|
||||
internal class ModuleImporter(private val errors: ErrorReporter) {
|
||||
internal class ModuleImporter {
|
||||
|
||||
internal fun importModule(program: Program, filePath: Path): Module {
|
||||
print("importing '${moduleName(filePath.fileName)}'")
|
||||
@ -61,13 +53,28 @@ internal class ModuleImporter(private val errors: ErrorReporter) {
|
||||
return executeImportDirective(program, import, Paths.get(""))
|
||||
}
|
||||
|
||||
private class MyErrorListener: ConsoleErrorListener() {
|
||||
var numberOfErrors: Int = 0
|
||||
override fun syntaxError(recognizer: Recognizer<*, *>?, offendingSymbol: Any?, line: Int, charPositionInLine: Int, msg: String, e: RecognitionException?) {
|
||||
numberOfErrors++
|
||||
when (recognizer) {
|
||||
is CustomLexer -> System.err.println("${recognizer.modulePath}:$line:$charPositionInLine: $msg")
|
||||
is prog8Parser -> System.err.println("${recognizer.inputStream.sourceName}:$line:$charPositionInLine: $msg")
|
||||
else -> System.err.println("$line:$charPositionInLine $msg")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun importModule(program: Program, stream: CharStream, modulePath: Path, isLibrary: Boolean): Module {
|
||||
val moduleName = moduleName(modulePath.fileName)
|
||||
val lexer = CustomLexer(modulePath, stream)
|
||||
val lexerErrors = LexerErrorListener()
|
||||
lexer.removeErrorListeners()
|
||||
val lexerErrors = MyErrorListener()
|
||||
lexer.addErrorListener(lexerErrors)
|
||||
val tokens = CommentHandlingTokenStream(lexer)
|
||||
val parser = prog8Parser(tokens)
|
||||
parser.removeErrorListeners()
|
||||
parser.addErrorListener(MyErrorListener())
|
||||
val parseTree = parser.module()
|
||||
val numberOfErrors = parser.numberOfSyntaxErrors + lexerErrors.numberOfErrors
|
||||
if(numberOfErrors > 0)
|
||||
@ -85,7 +92,7 @@ internal class ModuleImporter(private val errors: ErrorReporter) {
|
||||
// accept additional imports
|
||||
val lines = moduleAst.statements.toMutableList()
|
||||
lines.asSequence()
|
||||
.mapIndexed { i, it -> Pair(i, it) }
|
||||
.mapIndexed { i, it -> i to it }
|
||||
.filter { (it.second as? Directive)?.directive == "%import" }
|
||||
.forEach { executeImportDirective(program, it.second as Directive, modulePath) }
|
||||
|
||||
@ -95,7 +102,7 @@ internal class ModuleImporter(private val errors: ErrorReporter) {
|
||||
|
||||
private fun discoverImportedModuleFile(name: String, source: Path, position: Position?): Path {
|
||||
val fileName = "$name.p8"
|
||||
val locations = mutableListOf(source.parent)
|
||||
val locations = if(source.toString().isEmpty()) mutableListOf<Path>() else mutableListOf(source.parent)
|
||||
|
||||
val propPath = System.getProperty("prog8.libdir")
|
||||
if(propPath!=null)
|
||||
@ -110,7 +117,7 @@ internal class ModuleImporter(private val errors: ErrorReporter) {
|
||||
if (Files.isReadable(file)) return file
|
||||
}
|
||||
|
||||
throw ParsingFailedError("$position Import: no module source file '$fileName' found (I've looked in: $locations)")
|
||||
throw ParsingFailedError("$position Import: no module source file '$fileName' found (I've looked in: embedded libs and $locations)")
|
||||
}
|
||||
|
||||
private fun executeImportDirective(program: Program, import: Directive, source: Path): Module? {
|
||||
@ -124,16 +131,14 @@ internal class ModuleImporter(private val errors: ErrorReporter) {
|
||||
if(existing!=null)
|
||||
return null
|
||||
|
||||
val resource = tryGetEmbeddedResource("$moduleName.p8")
|
||||
val rsc = tryGetEmbeddedResource("$moduleName.p8")
|
||||
val importedModule =
|
||||
if(resource!=null) {
|
||||
if(rsc!=null) {
|
||||
// load the module from the embedded resource
|
||||
val (resource, resourcePath) = rsc
|
||||
resource.use {
|
||||
if(import.args[0].int==42)
|
||||
println("importing '$moduleName' (library, auto)")
|
||||
else
|
||||
println("importing '$moduleName' (library)")
|
||||
importModule(program, CharStreams.fromStream(it), Paths.get("@embedded@/$moduleName"), true)
|
||||
println("importing '$moduleName' (library)")
|
||||
importModule(program, CharStreams.fromStream(it), Paths.get("@embedded@/$resourcePath"), true)
|
||||
}
|
||||
} else {
|
||||
val modulePath = discoverImportedModuleFile(moduleName, source, import.position)
|
||||
@ -144,7 +149,18 @@ internal class ModuleImporter(private val errors: ErrorReporter) {
|
||||
return importedModule
|
||||
}
|
||||
|
||||
private fun tryGetEmbeddedResource(name: String): InputStream? {
|
||||
return object{}.javaClass.getResourceAsStream("/prog8lib/$name")
|
||||
private fun tryGetEmbeddedResource(name: String): Pair<InputStream, String>? {
|
||||
val target = CompilationTarget.instance.name
|
||||
val targetSpecificPath = "/prog8lib/$target/$name"
|
||||
val targetSpecificResource = object{}.javaClass.getResourceAsStream(targetSpecificPath)
|
||||
if(targetSpecificResource!=null)
|
||||
return Pair(targetSpecificResource, targetSpecificPath)
|
||||
|
||||
val generalPath = "/prog8lib/$name"
|
||||
val generalResource = object{}.javaClass.getResourceAsStream(generalPath)
|
||||
if(generalResource!=null)
|
||||
return Pair(generalResource, generalPath)
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
@ -5,17 +5,18 @@ import org.hamcrest.Matchers.closeTo
|
||||
import org.hamcrest.Matchers.equalTo
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.StringLiteralValue
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.*
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_NEGATIVE
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_POSITIVE
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.Mflpt5
|
||||
import prog8.compiler.target.c64.Petscii
|
||||
import prog8.compiler.target.cx16.CX16MachineDefinition
|
||||
import java.io.CharConversionException
|
||||
import kotlin.test.*
|
||||
|
||||
@ -123,13 +124,13 @@ class TestCompiler {
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestZeropage {
|
||||
class TestC64Zeropage {
|
||||
|
||||
private val errors = ErrorReporter()
|
||||
|
||||
@Test
|
||||
fun testNames() {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false))
|
||||
|
||||
zp.allocate("", DataType.UBYTE, null, errors)
|
||||
zp.allocate("", DataType.UBYTE, null, errors)
|
||||
@ -142,37 +143,37 @@ class TestZeropage {
|
||||
|
||||
@Test
|
||||
fun testZpFloatEnable() {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
|
||||
assertFailsWith<CompilerException> {
|
||||
zp.allocate("", DataType.FLOAT, null, errors)
|
||||
}
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true))
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false))
|
||||
assertFailsWith<CompilerException> {
|
||||
zp2.allocate("", DataType.FLOAT, null, errors)
|
||||
}
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true))
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false))
|
||||
zp3.allocate("", DataType.FLOAT, null, errors)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testZpModesWithFloats() {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false))
|
||||
assertFailsWith<CompilerException> {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true, false))
|
||||
}
|
||||
assertFailsWith<CompilerException> {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testZpDontuse() {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false))
|
||||
println(zp.free)
|
||||
assertEquals(0, zp.available())
|
||||
assertFailsWith<CompilerException> {
|
||||
@ -182,19 +183,19 @@ class TestZeropage {
|
||||
|
||||
@Test
|
||||
fun testFreeSpaces() {
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
|
||||
assertEquals(16, zp1.available())
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false))
|
||||
assertEquals(91, zp2.available())
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false))
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
|
||||
assertEquals(18, zp1.available())
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false))
|
||||
assertEquals(89, zp2.available())
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false))
|
||||
assertEquals(125, zp3.available())
|
||||
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
|
||||
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
|
||||
assertEquals(238, zp4.available())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testReservedSpace() {
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
|
||||
assertEquals(238, zp1.available())
|
||||
assertTrue(50 in zp1.free)
|
||||
assertTrue(100 in zp1.free)
|
||||
@ -203,7 +204,7 @@ class TestZeropage {
|
||||
assertTrue(200 in zp1.free)
|
||||
assertTrue(255 in zp1.free)
|
||||
assertTrue(199 in zp1.free)
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false))
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false, false))
|
||||
assertEquals(139, zp2.available())
|
||||
assertFalse(50 in zp2.free)
|
||||
assertFalse(100 in zp2.free)
|
||||
@ -216,8 +217,8 @@ class TestZeropage {
|
||||
|
||||
@Test
|
||||
fun testBasicsafeAllocation() {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
|
||||
assertEquals(16, zp.available())
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
|
||||
assertEquals(18, zp.available())
|
||||
|
||||
assertFailsWith<ZeropageDepletedError> {
|
||||
// in regular zp there aren't 5 sequential bytes free
|
||||
@ -239,7 +240,7 @@ class TestZeropage {
|
||||
|
||||
@Test
|
||||
fun testFullAllocation() {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
|
||||
assertEquals(238, zp.available())
|
||||
val loc = zp.allocate("", DataType.UWORD, null, errors)
|
||||
assertTrue(loc > 3)
|
||||
@ -269,20 +270,38 @@ class TestZeropage {
|
||||
|
||||
@Test
|
||||
fun testEfficientAllocation() {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
|
||||
assertEquals(16, zp.available())
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
|
||||
assertEquals(18, zp.available())
|
||||
assertEquals(0x04, zp.allocate("", DataType.WORD, null, errors))
|
||||
assertEquals(0x06, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
assertEquals(0x0a, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
assertEquals(0x94, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0xa7, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0xa9, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0xb5, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0xf7, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0x9b, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0x9e, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0xa5, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0xb0, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0xbe, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0x0e, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
assertEquals(0x92, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
assertEquals(0x96, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
assertEquals(0xf9, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
assertEquals(0, zp.available())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testReservedLocations() {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false))
|
||||
assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestCx16Zeropage {
|
||||
@Test
|
||||
fun testReservedLocations() {
|
||||
val zp = CX16MachineDefinition.CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false))
|
||||
assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -379,3 +398,169 @@ class TestPetscii {
|
||||
assertFalse(abc!=abc)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class TestMemory {
|
||||
@Test
|
||||
fun testInValidRamC64_memory_addresses() {
|
||||
CompilationTarget.instance = C64Target
|
||||
|
||||
var memexpr = NumericLiteralValue.optimalInteger(0x0000, Position.DUMMY)
|
||||
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
var scope = AnonymousScope(mutableListOf(), Position.DUMMY)
|
||||
assertTrue(target.isInRegularRAM(scope))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
|
||||
assertTrue(target.isInRegularRAM(scope))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0x9fff, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
|
||||
assertTrue(target.isInRegularRAM(scope))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xc000, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
|
||||
assertTrue(target.isInRegularRAM(scope))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xcfff, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
|
||||
assertTrue(target.isInRegularRAM(scope))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotInValidRamC64_memory_addresses() {
|
||||
CompilationTarget.instance = C64Target
|
||||
|
||||
var memexpr = NumericLiteralValue.optimalInteger(0xa000, Position.DUMMY)
|
||||
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
var scope = AnonymousScope(mutableListOf(), Position.DUMMY)
|
||||
assertFalse(target.isInRegularRAM(scope))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xafff, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
|
||||
assertFalse(target.isInRegularRAM(scope))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xd000, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
|
||||
assertFalse(target.isInRegularRAM(scope))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xffff, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
|
||||
assertFalse(target.isInRegularRAM(scope))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_memory_identifiers() {
|
||||
CompilationTarget.instance = C64Target
|
||||
var target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.VAR)
|
||||
assertTrue(target.isInRegularRAM(target.definingScope()))
|
||||
target = createTestProgramForMemoryRefViaVar(0xd020, VarDeclType.VAR)
|
||||
assertFalse(target.isInRegularRAM(target.definingScope()))
|
||||
target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.CONST)
|
||||
assertTrue(target.isInRegularRAM(target.definingScope()))
|
||||
target = createTestProgramForMemoryRefViaVar(0xd020, VarDeclType.CONST)
|
||||
assertFalse(target.isInRegularRAM(target.definingScope()))
|
||||
target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.MEMORY)
|
||||
assertFalse(target.isInRegularRAM(target.definingScope()))
|
||||
}
|
||||
|
||||
@Test
|
||||
private fun createTestProgramForMemoryRefViaVar(address: Int, vartype: VarDeclType): AssignTarget {
|
||||
val decl = VarDecl(vartype, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
|
||||
val memexpr = IdentifierReference(listOf("address"), Position.DUMMY)
|
||||
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
subroutine.linkParents(ParentSentinel)
|
||||
return target
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_memory_expression() {
|
||||
CompilationTarget.instance = C64Target
|
||||
val memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
val scope = AnonymousScope(mutableListOf(), Position.DUMMY)
|
||||
assertFalse(target.isInRegularRAM(scope))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_variable() {
|
||||
CompilationTarget.instance = C64Target
|
||||
val decl = VarDecl(VarDeclType.VAR, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, null, false, false, Position.DUMMY)
|
||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
subroutine.linkParents(ParentSentinel)
|
||||
assertTrue(target.isInRegularRAM(target.definingScope()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_memmap_variable() {
|
||||
CompilationTarget.instance = C64Target
|
||||
val address = 0x1000
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
|
||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
subroutine.linkParents(ParentSentinel)
|
||||
assertTrue(target.isInRegularRAM(target.definingScope()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotInValidRamC64_memmap_variable() {
|
||||
CompilationTarget.instance = C64Target
|
||||
val address = 0xd020
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
|
||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
subroutine.linkParents(ParentSentinel)
|
||||
assertFalse(target.isInRegularRAM(target.definingScope()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_array() {
|
||||
CompilationTarget.instance = C64Target
|
||||
val decl = VarDecl(VarDeclType.VAR, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, null, false, false, Position.DUMMY)
|
||||
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
subroutine.linkParents(ParentSentinel)
|
||||
assertTrue(target.isInRegularRAM(target.definingScope()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_array_memmapped() {
|
||||
CompilationTarget.instance = C64Target
|
||||
val address = 0x1000
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
|
||||
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
subroutine.linkParents(ParentSentinel)
|
||||
assertTrue(target.isInRegularRAM(target.definingScope()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotValidRamC64_array_memmapped() {
|
||||
CompilationTarget.instance = C64Target
|
||||
val address = 0xe000
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
|
||||
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
subroutine.linkParents(ParentSentinel)
|
||||
assertFalse(target.isInRegularRAM(target.definingScope()))
|
||||
}
|
||||
}
|
||||
|
BIN
docs/source/_static/cobra3d.png
Normal file
BIN
docs/source/_static/cobra3d.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
BIN
docs/source/_static/primes_cx16.png
Normal file
BIN
docs/source/_static/primes_cx16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
@ -41,7 +41,7 @@ of that build task, you can start the compiler with:
|
||||
|
||||
(You should probably make an alias...)
|
||||
|
||||
.. note::
|
||||
.. hint::
|
||||
Development and testing is done on Linux, but the compiler should run on most
|
||||
operating systems. If you do have trouble building or running
|
||||
the compiler on another operating system, please let me know!
|
||||
@ -83,7 +83,7 @@ For normal use the compiler is invoked with the command:
|
||||
By default, assembly code is generated and written to ``sourcefile.asm``.
|
||||
It is then (automatically) fed to the `64tass <https://sourceforge.net/projects/tass64/>`_ cross assembler tool
|
||||
that assembles it into the final program.
|
||||
If you use the option to let the compiler auto-start a C-64 emulator, it will do so after
|
||||
If you use the option to let the compiler auto-start an emulator, it will do so after
|
||||
a successful compilation. This will load your program and the symbol and breakpoint lists
|
||||
(for the machine code monitor) into the emulator.
|
||||
|
||||
@ -93,6 +93,8 @@ Almost instant compilation times (less than a second) can be achieved when using
|
||||
Start the compiler with the ``-watch`` argument to enable this.
|
||||
It will compile your program and then instead of exiting, it waits for any changes in the module source files.
|
||||
As soon as a change happens, the program gets compiled again.
|
||||
It is possible to use the watch mode with multiple modules as well, but it will
|
||||
recompile everything in that list even if only of the files got updated.
|
||||
|
||||
Other options
|
||||
^^^^^^^^^^^^^
|
||||
@ -109,8 +111,8 @@ A module source file is a text file with the ``.p8`` suffix, containing the prog
|
||||
It consists of compilation options and other directives, imports of other modules,
|
||||
and source code for one or more code blocks.
|
||||
|
||||
Prog8 has a couple of *LIBRARY* modules that are defined in special internal files provided by the compiler:
|
||||
``c64lib``, ``c64utils``, ``c64flt`` and ``prog8lib``. You should not overwrite these or reuse their names.
|
||||
Prog8 has various *LIBRARY* modules that are defined in special internal files provided by the compiler.
|
||||
You should not overwrite these or reuse their names.
|
||||
They are embedded into the packaged release version of the compiler so you don't have to worry about
|
||||
where they are, but their names are still reserved.
|
||||
|
||||
@ -149,10 +151,10 @@ If your running program hits one of the breakpoints, Vice will halt execution an
|
||||
Troubleshooting
|
||||
---------------
|
||||
|
||||
Getting an assembler error about undefined symbols such as ``not defined 'c64flt'``?
|
||||
This happens when your program uses floating point values, and you forgot to import ``c64flt`` library.
|
||||
Getting an assembler error about undefined symbols such as ``not defined 'floats'``?
|
||||
This happens when your program uses floating point values, and you forgot to import ``floats`` library.
|
||||
If you use floating points, the compiler needs routines from that library.
|
||||
Fix it by adding an ``%import c64flt``.
|
||||
Fix it by adding an ``%import floats``.
|
||||
|
||||
|
||||
Examples
|
||||
|
@ -12,6 +12,7 @@ What is Prog8?
|
||||
|
||||
This is an experimental compiled programming language targeting the 8-bit
|
||||
`6502 <https://en.wikipedia.org/wiki/MOS_Technology_6502>`_ /
|
||||
`65c02 <https://en.wikipedia.org/wiki/MOS_Technology_65C02>`_ /
|
||||
`6510 <https://en.wikipedia.org/wiki/MOS_Technology_6510>`_ microprocessor.
|
||||
This CPU is from the late 1970's and early 1980's and was used in many home computers from that era,
|
||||
such as the `Commodore-64 <https://en.wikipedia.org/wiki/Commodore_64>`_.
|
||||
@ -37,49 +38,70 @@ This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/license
|
||||
:alt: Fully playable tetris clone
|
||||
|
||||
|
||||
Code examples
|
||||
-------------
|
||||
Language features
|
||||
-----------------
|
||||
|
||||
- It is a cross-compiler running on modern machines (Linux, MacOS, Windows, ...)
|
||||
The generated output is a machine code program runnable on actual 8-bit 6502 hardware.
|
||||
- Provide a very convenient edit/compile/run cycle by being able to directly launch
|
||||
the compiled program in an emulator and provide debugging information to this emulator.
|
||||
- Based on simple and familiar imperative structured programming (it looks like a mix of C and Python)
|
||||
- Modular programming and scoping via modules, code blocks, and subroutines.
|
||||
- Provide high level programming constructs but at the same time stay close to the metal;
|
||||
still able to directly use memory addresses and ROM subroutines,
|
||||
and inline assembly to have full control when every register, cycle or byte matters
|
||||
- Arbitrary number of subroutine parameters, Complex nested expressions are possible
|
||||
- No stack frame allocations because parameters and local variables are automatically allocated statically
|
||||
- Nested subroutines can access variables from outer scopes to avoids the overhead to pass everything via parameters
|
||||
- Variable data types include signed and unsigned bytes and words, arrays, strings and floats.
|
||||
- High-level code optimizations, such as const-folding, expression and statement simplifications/rewriting.
|
||||
- Many built-in functions, such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``, ``substr``, ``sort`` and ``reverse`` (and others)
|
||||
- If you only use standard kernel and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
|
||||
|
||||
|
||||
Code example
|
||||
------------
|
||||
|
||||
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() {
|
||||
; clear the sieve, to reset starting situation on subsequent runs
|
||||
memset(sieve, 256, false)
|
||||
|
||||
c64scr.print("prime numbers up to 255:\n\n")
|
||||
; calculate primes
|
||||
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.chrout('\n')
|
||||
txt.print("number of primes (expected 54): ")
|
||||
txt.print_ub(amount)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
|
||||
sub find_next_prime() -> ubyte {
|
||||
|
||||
while sieve[candidate_prime] {
|
||||
candidate_prime++
|
||||
if candidate_prime==0
|
||||
return 0
|
||||
return 0 ; we wrapped; no more primes available in the sieve
|
||||
}
|
||||
|
||||
; 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
|
||||
@ -95,69 +117,12 @@ when compiled an ran on a C-64 you get this:
|
||||
:align: center
|
||||
:alt: result when run on C-64
|
||||
|
||||
when the exact same program is compiled for the Commander X16 target, and run on the emulator, you get this:
|
||||
|
||||
The following programs shows a use of the high level ``struct`` type::
|
||||
.. image:: _static/primes_cx16.png
|
||||
:align: center
|
||||
:alt: result when run on CX16 emulator
|
||||
|
||||
%import c64utils
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
|
||||
struct Color {
|
||||
ubyte red
|
||||
ubyte green
|
||||
ubyte blue
|
||||
}
|
||||
|
||||
sub start() {
|
||||
|
||||
Color purple = {255, 0, 255}
|
||||
Color other
|
||||
other = purple
|
||||
other.red /= 2
|
||||
other.green = 10 + other.green / 2
|
||||
other.blue = 99
|
||||
|
||||
c64scr.print_ub(other.red)
|
||||
c64.CHROUT(',')
|
||||
c64scr.print_ub(other.green)
|
||||
c64.CHROUT(',')
|
||||
c64scr.print_ub(other.blue)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
}
|
||||
|
||||
when compiled and ran, it prints ``127,10,99`` on the screen.
|
||||
|
||||
|
||||
Design principles and features
|
||||
------------------------------
|
||||
|
||||
- It is a cross-compiler running on modern machines (Linux, MacOS, Windows, ...)
|
||||
The generated output is a machine code program runnable on actual 8-bit 6502 hardware.
|
||||
- Based on simple and familiar imperative structured programming (it looks like a mix of C and Python)
|
||||
- 'One statement per line' code, resulting in clear readable programs.
|
||||
- Modular programming and scoping via modules, code blocks, and subroutines.
|
||||
- Provide high level programming constructs but at the same time stay close to the metal;
|
||||
still able to directly use memory addresses, CPU registers and ROM subroutines,
|
||||
and inline assembly to have full control when every cycle or byte matters
|
||||
- Arbitrary number of subroutine parameters
|
||||
- Complex nested expressions are possible
|
||||
- Nested subroutines can access variables from outer scopes to avoids the overhead to pass everything via parameters
|
||||
- Values are typed. Available data types include signed and unsigned bytes and words, arrays, strings and floats.
|
||||
- No dynamic memory allocation or sizing! All variables stay fixed size as determined at compile time.
|
||||
- Provide various quality of life language features and library subroutines specifically for the target platform.
|
||||
- Provide a very convenient edit/compile/run cycle by being able to directly launch
|
||||
the compiled program in an emulator and provide debugging information to this emulator.
|
||||
- Arbitrary control flow jumps and branches are possible,
|
||||
and will usually translate directly into the appropriate single 6502 jump/branch instruction.
|
||||
- There are no complicated built-in error handling or overflow checks, you'll have to take care
|
||||
of this yourself if required. This keeps the language and code simple and efficient.
|
||||
- The compiler tries to optimize the program and generated code a bit, but hand-tuning of the
|
||||
performance or space-critical parts will likely still be required. This is supported by
|
||||
the ability to easily write embedded assembly code directly in the program source code.
|
||||
- There are many built-in functions, such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``, ``sort`` and ``reverse``
|
||||
- Assembling the generated code into a program wil be done by an external cross-assembler tool.
|
||||
|
||||
|
||||
.. _requirements:
|
||||
@ -168,13 +133,17 @@ Required tools
|
||||
`64tass <https://sourceforge.net/projects/tass64/>`_ - cross assembler. Install this on your shell path.
|
||||
It's very easy to compile yourself.
|
||||
A recent precompiled .exe for Windows can be obtained from my `clone <https://github.com/irmen/64tass/releases>`_ of this project.
|
||||
*You need at least version 1.55.2257 of this assembler to correctly use the breakpoints feature.*
|
||||
It's possible to use older versions, but it is very likely that the automatic Vice breakpoints won't work with them.
|
||||
|
||||
A **Java runtime (jre or jdk), version 8 or newer** is required to run the prog8 compiler itself.
|
||||
If you're scared of Oracle's licensing terms, most Linux distributions ship OpenJDK instead.
|
||||
Fnd for Windows it's possible to get that as well. Check out `AdoptOpenJDK <https://adoptopenjdk.net/>`_ .
|
||||
A **Java runtime (jre or jdk), version 11 or newer** is required to run the prog8 compiler itself.
|
||||
If you're scared of Oracle's licensing terms, most Linux distributions ship OpenJDK in their packages repository instead.
|
||||
For Windows it's possible to get that as well; check out `AdoptOpenJDK <https://adoptopenjdk.net/>`_ .
|
||||
For MacOS you can use the Homebrew system to install a recent version of OpenJDK.
|
||||
|
||||
Finally: a **C-64 emulator** (or a real C-64 ofcourse) can be nice to test and run your programs on.
|
||||
The compiler assumes the presence of the `Vice emulator <http://vice-emu.sourceforge.net/>`_.
|
||||
Finally: an **emulator** (or a real machine ofcourse) to test and run your programs on.
|
||||
In C64 mode, thhe compiler assumes the presence of the `Vice emulator <http://vice-emu.sourceforge.net/>`_.
|
||||
If you're targeting the CommanderX16 instead, there's the `x16emu <https://github.com/commanderx16/x16-emulator>`_.
|
||||
|
||||
.. important::
|
||||
**Building the compiler itself:** (*Only needed if you have not downloaded a pre-built 'fat-jar'*)
|
||||
@ -195,6 +164,7 @@ The compiler assumes the presence of the `Vice emulator <http://vice-emu.sourcef
|
||||
building.rst
|
||||
programming.rst
|
||||
syntaxreference.rst
|
||||
libraries.rst
|
||||
todo.rst
|
||||
|
||||
|
||||
|
94
docs/source/libraries.rst
Normal file
94
docs/source/libraries.rst
Normal file
@ -0,0 +1,94 @@
|
||||
************************
|
||||
Compiler library modules
|
||||
************************
|
||||
|
||||
The compiler provides several "built-in" library modules with useful subroutine and variables.
|
||||
|
||||
Some of these may be specific for a certain compilation target, or work slightly different,
|
||||
but some effort is put into making them available across compilation targets.
|
||||
|
||||
This means that as long as your program is only using the subroutines from these
|
||||
libraries and not using hardware- and/or system dependent code, and isn't hardcoding certain
|
||||
assumptions like the screen size, the exact same source program can
|
||||
be compiled for multiple different target platforms. Many of the example programs that come
|
||||
with Prog8 are written like this.
|
||||
|
||||
You can ``%import`` and use these modules explicitly, but the compiler may also import one or more
|
||||
of these library modules automatically as required.
|
||||
|
||||
.. caution::
|
||||
The resulting compiled binary program *only works on the target machine it was compiled for*.
|
||||
You must recompile the program for every target you want to run it on.
|
||||
|
||||
|
||||
|
||||
syslib
|
||||
------
|
||||
The "system library" for your target machine. It contains many system-specific definitions such
|
||||
as ROM/kernal subroutine definitions, memory location constants, and utility subroutines.
|
||||
|
||||
Many of these definitions overlap for the C64 and Commander X16 targets so it is still possible
|
||||
to write programs that work on both targets without modifications.
|
||||
|
||||
conv
|
||||
----
|
||||
Routines to convert strings to numbers or vice versa.
|
||||
|
||||
- numbers to strings, in various formats (binary, hex, decimal)
|
||||
- strings in decimal, hex and binary format into numbers
|
||||
|
||||
|
||||
textio (txt.*)
|
||||
--------------
|
||||
This will probably be the most used library module. It contains a whole lot of routines
|
||||
dealing with text-based input and output (to the screen). Such as
|
||||
|
||||
- printing strings and numbers
|
||||
- reading text input from the user via the keyboard
|
||||
- filling or clearing the screen and colors
|
||||
- scrolling the text on the screen
|
||||
- placing individual characters on the screen
|
||||
|
||||
|
||||
diskio
|
||||
------
|
||||
Provides several routines that deal with disk drive I/O, such as:
|
||||
|
||||
- show directory
|
||||
- display disk drive status
|
||||
- load and save data from and to the disk
|
||||
- delete and rename files on the disk
|
||||
|
||||
|
||||
floats
|
||||
------
|
||||
Provides definitions for the ROM/kernel subroutines and utility routines dealing with floating
|
||||
point variables. This includes ``print_f``, the routine used to print floating point numbers.
|
||||
|
||||
|
||||
graphics
|
||||
--------
|
||||
High-res monochrome bitmap graphics routines:
|
||||
|
||||
- clearing the screen
|
||||
- drawing lines
|
||||
- drawing circles and discs (filled circles)
|
||||
- plotting individual pixels
|
||||
|
||||
|
||||
math
|
||||
----
|
||||
Low level math routines. You should not normally have to bother with this directly.
|
||||
The compiler needs it to implement most of the math operations in your programs.
|
||||
|
||||
|
||||
cx16logo
|
||||
--------
|
||||
A 'fun' module that contains the Commander X16 logo and that allows you
|
||||
to print it anywhere on the screen.
|
||||
|
||||
|
||||
prog8_lib
|
||||
---------
|
||||
Low level language support. You should not normally have to bother with this directly.
|
||||
The compiler needs it for verious built-in system routines.
|
@ -50,7 +50,7 @@ Code
|
||||
There are different kinds of instructions ('statements' is a better name) such as:
|
||||
|
||||
- value assignment
|
||||
- looping (for, while, repeat, unconditional jumps)
|
||||
- looping (for, while, do-until, repeat, unconditional jumps)
|
||||
- conditional execution (if - then - else, when, and conditional jumps)
|
||||
- subroutine calls
|
||||
- label definition
|
||||
@ -137,7 +137,7 @@ Scopes are created using either of these two statements:
|
||||
|
||||
.. important::
|
||||
Unlike most other programming languages, a new scope is *not* created inside
|
||||
for, while and repeat statements, the if statement, and the branching conditionals.
|
||||
for, while, repeat, and do-until statements, the if statement, and the branching conditionals.
|
||||
These all share the same scope from the subroutine they're defined in.
|
||||
You can define variables in these blocks, but these will be treated as if they
|
||||
were defined in the subroutine instead.
|
||||
@ -204,13 +204,6 @@ Example::
|
||||
byte @zp zeropageCounter = 42
|
||||
|
||||
|
||||
Variables that represent CPU hardware registers
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The following variables are reserved
|
||||
and map directly (read/write) to a CPU hardware register: ``A``, ``X``, ``Y``.
|
||||
|
||||
|
||||
Integers
|
||||
^^^^^^^^
|
||||
|
||||
@ -233,7 +226,7 @@ This is because routines in the C-64 BASIC and KERNAL ROMs are used for that.
|
||||
So floating point operations will only work if the C-64 BASIC ROM (and KERNAL ROM)
|
||||
are banked in.
|
||||
|
||||
Also your code needs to import the ``c64flt`` library to enable floating point support
|
||||
Also your code needs to import the ``floats`` library to enable floating point support
|
||||
in the compiler, and to gain access to the floating point routines.
|
||||
(this library contains the directive to enable floating points, you don't have
|
||||
to worry about this yourself)
|
||||
@ -243,12 +236,15 @@ The largest 5-byte MFLPT float that can be stored is: **1.7014118345e+38** (ne
|
||||
|
||||
Arrays
|
||||
^^^^^^
|
||||
Array types are also supported. They can be made of bytes, words or floats::
|
||||
Array types are also supported. They can be made of bytes, words or floats, strings, and other arrays
|
||||
(although the usefulness of the latter is very limited for now)::
|
||||
|
||||
byte[10] array ; array of 10 bytes, initially set to 0
|
||||
byte[] array = [1, 2, 3, 4] ; initialize the array, size taken from value
|
||||
byte[99] array = 255 ; initialize array with 99 times 255 [255, 255, 255, 255, ...]
|
||||
byte[] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
|
||||
str[] names = ["ally", "pete"] ; array of string pointers/addresses (equivalent to uword)
|
||||
uword[] others = [names, array] ; array of pointers/addresses to other arrays
|
||||
|
||||
value = array[3] ; the fourth value in the array (index is 0-based)
|
||||
char = string[4] ; the fifth character (=byte) in the string
|
||||
@ -265,6 +261,22 @@ can't be used as *identifiers* elsewhere. You can't make a variable, block or su
|
||||
for instance.
|
||||
|
||||
|
||||
It's possible to assign a new array to another array, this will overwrite all elements in the original
|
||||
array with those in the value array. The number and types of elements have to match.
|
||||
For large arrays this is a slow operation because every element is copied over. It should probably be avoided.
|
||||
|
||||
|
||||
**Arrays at a specific memory location:**
|
||||
Using the memory-mapped syntax it is possible to define an array to be located at a specific memory location.
|
||||
For instance to reference the first 5 rows of the Commodore 64's screen matrix as an array, you can define::
|
||||
|
||||
&ubyte[5*40] top5screenrows = $0400
|
||||
|
||||
This way you can set the second character on the second row from the top like this::
|
||||
|
||||
top5screenrows[41] = '!'
|
||||
|
||||
|
||||
Strings
|
||||
^^^^^^^
|
||||
|
||||
@ -279,16 +291,35 @@ This @-prefix can also be used for character byte values.
|
||||
|
||||
|
||||
You can concatenate two string literals using '+' (not very useful though) or repeat
|
||||
a string literal a given number of times using '*'::
|
||||
a string literal a given number of times using '*'. You can also assign a new string
|
||||
value to another string. No bounds check is done so be sure the destination string is
|
||||
large enough to contain the new value (it is overwritten in memory)::
|
||||
|
||||
str string1 = "first part" + "second part"
|
||||
str string2 = "hello!" * 10
|
||||
|
||||
string1 = string2
|
||||
string1 = "new value"
|
||||
|
||||
|
||||
There are several 'escape sequences' to help you put special characters into strings, such
|
||||
as newlines, quote characters themselves, and so on. The ones used most often are
|
||||
``\\``, ``\"``, ``\n``, ``\r``. For a detailed description of all of them and what they mean,
|
||||
read the syntax reference on strings.
|
||||
|
||||
|
||||
.. hint::
|
||||
Strings and uwords (=memory address) can often be interchanged.
|
||||
An array of strings is actually an array of uwords where every element is the memory
|
||||
address of the string. You can pass a memory address to assembly functions
|
||||
that require a string as an argument.
|
||||
|
||||
.. caution::
|
||||
Avoid changing strings after they've been created.
|
||||
It's probably best to avoid changing strings after they've been created. This
|
||||
includes changing certain letters by index, or by assigning a new value, or by
|
||||
modifying the string via other means for example ``substr`` function and its cousins.
|
||||
This is because if your program exits and is restarted (without loading it again),
|
||||
it will then start working with the changed strings instead of the original ones.
|
||||
it will then start working with the changed strings instead of the original ones!
|
||||
The same is true for arrays.
|
||||
|
||||
|
||||
@ -317,7 +348,7 @@ and then create a variable with it::
|
||||
ubyte blue
|
||||
}
|
||||
|
||||
Color rgb = {255,122,0} ; note the curly braces here instead of brackets
|
||||
Color rgb = [255,122,0] ; note that struct initializer value is same as an array
|
||||
Color another ; the init value is optional, like arrays
|
||||
|
||||
another = rgb ; assign all of the values of rgb to another
|
||||
@ -342,13 +373,6 @@ address you specified, and setting the varible will directly modify that memory
|
||||
&word SCREENCOLORS = $d020 ; a 16-bit word at the addres $d020-$d021
|
||||
|
||||
|
||||
.. note::
|
||||
Directly accessing random memory locations is not yet supported without the
|
||||
intermediate step of declaring a memory-mapped variable for the memory location.
|
||||
The advantages of this however, is that it's clearer what the memory location
|
||||
stands for, and the compiler also knows the data type.
|
||||
|
||||
|
||||
Converting types into other types
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@ -386,21 +410,21 @@ expected when the program is restarted.
|
||||
Loops
|
||||
-----
|
||||
|
||||
The *for*-loop is used to let a variable (or register) iterate over a range of values. Iteration is done in steps of 1, but you can change this.
|
||||
The *for*-loop is used to let a variable iterate over a range of values. Iteration is done in steps of 1, but you can change this.
|
||||
The loop variable must be declared as byte or word earlier so you can reuse it for multiple occasions.
|
||||
Iterating with a floating point variable is not supported. If you want to loop over a floating-point array, use a loop with an integer index variable instead.
|
||||
|
||||
The *while*-loop is used to repeat a piece of code while a certain condition is still true.
|
||||
The *repeat--until* loop is used to repeat a piece of code until a certain condition is true.
|
||||
|
||||
The *forever*-loop is used to simply run a piece of code in a loop, forever. You can still
|
||||
break out of this loop if desired. A "while true" or "until false" loop is equivalent to
|
||||
a forever-loop.
|
||||
The *do--until* loop is used to repeat a piece of code until a certain condition is true.
|
||||
The *repeat* loop is used as a short notation of a for loop where the loop variable doesn't matter and you're only interested in the number of iterations.
|
||||
(without iteration count specified it simply loops forever).
|
||||
|
||||
You can also create loops by using the ``goto`` statement, but this should usually be avoided.
|
||||
|
||||
Breaking out of a loop prematurely is possible with the ``break`` statement.
|
||||
|
||||
.. attention::
|
||||
The value of the loop variable or register after executing the loop *is undefined*. Don't use it immediately
|
||||
The value of the loop variable after executing the loop *is undefined*. Don't use it immediately
|
||||
after the loop without first assigning a new value to it!
|
||||
(this is an optimization issue to avoid having to deal with mostly useless post-loop logic to adjust the loop variable's value)
|
||||
|
||||
@ -414,15 +438,15 @@ if statements
|
||||
Conditional execution means that the flow of execution changes based on certiain conditions,
|
||||
rather than having fixed gotos or subroutine calls::
|
||||
|
||||
if A>4 goto overflow
|
||||
if aa>4 goto overflow
|
||||
|
||||
if X==3 Y = 4
|
||||
if X==3 Y = 4 else A = 2
|
||||
if xx==3 yy = 4
|
||||
if xx==3 yy = 4 else aa = 2
|
||||
|
||||
if X==5 {
|
||||
Y = 99
|
||||
if xx==5 {
|
||||
yy = 99
|
||||
} else {
|
||||
A = 3
|
||||
aa = 3
|
||||
}
|
||||
|
||||
|
||||
@ -486,16 +510,16 @@ Assignments
|
||||
-----------
|
||||
|
||||
Assignment statements assign a single value to a target variable or memory location.
|
||||
Augmented assignments (such as ``A += X``) are also available, but these are just shorthands
|
||||
for normal assignments (``A = A + X``).
|
||||
Augmented assignments (such as ``aa += xx``) are also available, but these are just shorthands
|
||||
for normal assignments (``aa = aa + xx``).
|
||||
|
||||
Only register variables and variables of type byte, word and float can be assigned a new value.
|
||||
Only variables of type byte, word and float can be assigned a new value.
|
||||
It's not possible to set a new value to string or array variables etc, because they get allocated
|
||||
a fixed amount of memory which will not change.
|
||||
a fixed amount of memory which will not change. (You *can* change the value of elements in a string or array though).
|
||||
|
||||
.. attention::
|
||||
**Data type conversion (in assignments):**
|
||||
When assigning a value with a 'smaller' datatype to a register or variable with a 'larger' datatype,
|
||||
When assigning a value with a 'smaller' datatype to variable with a 'larger' datatype,
|
||||
the value will be automatically converted to the target datatype: byte --> word --> float.
|
||||
So assigning a byte to a word variable, or a word to a floating point variable, is fine.
|
||||
The reverse is *not* true: it is *not* possible to assign a value of a 'larger' datatype to
|
||||
@ -511,7 +535,7 @@ as the memory mapped address $d021.
|
||||
If you want to access a memory location directly (by using the address itself), without defining
|
||||
a memory mapped location, you can do so by enclosing the address in ``@(...)``::
|
||||
|
||||
A = @($d020) ; set the A register to the current c64 screen border color ("peek(53280)")
|
||||
color = @($d020) ; set the variable 'color' to the current c64 screen border color ("peek(53280)")
|
||||
@($d020) = 0 ; set the c64 screen border to black ("poke 53280,0")
|
||||
@(vic+$20) = 6 ; you can also use expressions to 'calculate' the address
|
||||
|
||||
@ -568,25 +592,24 @@ within parentheses will be evaluated first. So ``(4 + 8) * 2`` is 24 and not 20,
|
||||
and ``(true or false) and false`` is false instead of true.
|
||||
|
||||
.. attention::
|
||||
**calculations keep their datatype:**
|
||||
**calculations keep their datatype even if the target variable is larger:**
|
||||
When you do calculations on a BYTE type, the result will remain a BYTE.
|
||||
When you do calculations on a WORD type, the result will remain a WORD.
|
||||
For instance::
|
||||
|
||||
byte b = 44
|
||||
word w = b*55 ; the result will be 116! (even though the target variable is a word)
|
||||
w *= 999 ; the result will be -15188 (the multiplication stays within a word)
|
||||
w *= 999 ; the result will be -15188 (the multiplication stays within a word, but overflows)
|
||||
|
||||
The compiler will NOT give a warning about this! It's doing this for
|
||||
*The compiler does NOT warn about this!* It's doing this for
|
||||
performance reasons - so you won't get sudden 16 bit (or even float)
|
||||
calculations where you needed only simple fast byte arithmetic.
|
||||
If you do need the extended resulting value, cast at least one of the
|
||||
operands of an operator to the larger datatype. For example::
|
||||
operands explicitly to the larger datatype. For example::
|
||||
|
||||
byte b = 44
|
||||
word w = b*55.w ; the result will be 2420
|
||||
w = (b as word)*55 ; same result
|
||||
|
||||
w = (b as word)*55
|
||||
w = b*(55 as word)
|
||||
|
||||
|
||||
|
||||
@ -627,23 +650,20 @@ There's a set of predefined functions in the language. These are fixed and can't
|
||||
You can use them in expressions and the compiler will evaluate them at compile-time if possible.
|
||||
|
||||
|
||||
sin(x)
|
||||
Sine. (floating point version)
|
||||
Math
|
||||
^^^^
|
||||
|
||||
abs(x)
|
||||
Absolute value.
|
||||
|
||||
atan(x)
|
||||
Arctangent.
|
||||
|
||||
ceil(x)
|
||||
Rounds the floating point up to an integer towards positive infinity.
|
||||
|
||||
cos(x)
|
||||
Cosine. (floating point version)
|
||||
|
||||
sin8u(x)
|
||||
Fast 8-bit ubyte sine of angle 0..255, result is in range 0..255
|
||||
|
||||
sin8(x)
|
||||
Fast 8-bit byte sine of angle 0..255, result is in range -127..127
|
||||
|
||||
sin16u(x)
|
||||
Fast 16-bit uword sine of angle 0..255, result is in range 0..65535
|
||||
|
||||
sin16(x)
|
||||
Fast 16-bit word sine of angle 0..255, result is in range -32767..32767
|
||||
Cosine. (floating point version)
|
||||
|
||||
cos8u(x)
|
||||
Fast 8-bit ubyte cosine of angle 0..255, result is in range 0..255
|
||||
@ -657,14 +677,11 @@ cos16u(x)
|
||||
cos16(x)
|
||||
Fast 16-bit word cosine of angle 0..255, result is in range -32767..32767
|
||||
|
||||
abs(x)
|
||||
Absolute value.
|
||||
deg(x)
|
||||
Radians to degrees.
|
||||
|
||||
tan(x)
|
||||
Tangent.
|
||||
|
||||
atan(x)
|
||||
Arctangent.
|
||||
floor (x)
|
||||
Rounds the floating point down to an integer towards minus infinity.
|
||||
|
||||
ln(x)
|
||||
Natural logarithm (base e).
|
||||
@ -672,26 +689,55 @@ ln(x)
|
||||
log2(x)
|
||||
Base 2 logarithm.
|
||||
|
||||
rad(x)
|
||||
Degrees to radians.
|
||||
|
||||
round(x)
|
||||
Rounds the floating point to the closest integer.
|
||||
|
||||
sin(x)
|
||||
Sine. (floating point version)
|
||||
|
||||
sgn(x)
|
||||
Get the sign of the value. Result is -1, 0 or 1 (negative, zero, positive).
|
||||
|
||||
sin8u(x)
|
||||
Fast 8-bit ubyte sine of angle 0..255, result is in range 0..255
|
||||
|
||||
sin8(x)
|
||||
Fast 8-bit byte sine of angle 0..255, result is in range -127..127
|
||||
|
||||
sin16u(x)
|
||||
Fast 16-bit uword sine of angle 0..255, result is in range 0..65535
|
||||
|
||||
sin16(x)
|
||||
Fast 16-bit word sine of angle 0..255, result is in range -32767..32767
|
||||
|
||||
sqrt16(w)
|
||||
16 bit unsigned integer Square root. Result is unsigned byte.
|
||||
|
||||
sqrt(x)
|
||||
Floating point Square root.
|
||||
|
||||
round(x)
|
||||
Rounds the floating point to the closest integer.
|
||||
tan(x)
|
||||
Tangent.
|
||||
|
||||
floor (x)
|
||||
Rounds the floating point down to an integer towards minus infinity.
|
||||
|
||||
ceil(x)
|
||||
Rounds the floating point up to an integer towards positive infinity.
|
||||
Array operations
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
rad(x)
|
||||
Degrees to radians.
|
||||
any(x)
|
||||
1 ('true') if any of the values in the array value x is 'true' (not zero), else 0 ('false')
|
||||
|
||||
deg(x)
|
||||
Radians to degrees.
|
||||
all(x)
|
||||
1 ('true') if all of the values in the array value x are 'true' (not zero), else 0 ('false')
|
||||
|
||||
len(x)
|
||||
Number of values in the array value x, or the number of characters in a string (excluding the size or 0-byte).
|
||||
Note: this can be different from the number of *bytes* in memory if the datatype isn't a byte. See sizeof().
|
||||
Note: lengths of strings and arrays are determined at compile-time! If your program modifies the actual
|
||||
length of the string during execution, the value of len(string) may no longer be correct!
|
||||
(use strlen function if you want to dynamically determine the length)
|
||||
|
||||
max(x)
|
||||
Maximum of the values in the array value x
|
||||
@ -699,29 +745,77 @@ max(x)
|
||||
min(x)
|
||||
Minimum of the values in the array value x
|
||||
|
||||
reverse(array)
|
||||
Reverse the values in the array (in-place).
|
||||
Can be used after sort() to sort an array in descending order.
|
||||
|
||||
sum(x)
|
||||
Sum of the values in the array value x
|
||||
|
||||
sort(array)
|
||||
Sort the array in ascending order (in-place)
|
||||
Note: sorting a floating-point array is not supported right now, as a general sorting routine for this will
|
||||
be extremely slow. Either build one yourself or find another solution that doesn't require sorting
|
||||
floating point values.
|
||||
Supported are arrays of bytes or word values.
|
||||
Sorting a floating-point array is not supported right now, as a general sorting routine for this will
|
||||
be extremely slow. Either build one yourself or find another solution that doesn't require sorting.
|
||||
Finally, note that sorting an array with strings in it will not do what you might think;
|
||||
it considers the array as just an array of integer words and sorts the string *pointers* accordingly.
|
||||
Sorting strings alphabetically has to be programmed yourself if you need it.
|
||||
|
||||
reverse(array)
|
||||
Reverse the values in the array (in-place). Supports all data types including floats.
|
||||
Can be used after sort() to sort an array in descending order.
|
||||
|
||||
len(x)
|
||||
Number of values in the array value x, or the number of characters in a string (excluding the size or 0-byte).
|
||||
Note: this can be different from the number of *bytes* in memory if the datatype isn't a byte.
|
||||
Note: lengths of strings and arrays are determined at compile-time! If your program modifies the actual
|
||||
length of the string during execution, the value of len(string) may no longer be correct!
|
||||
(use strlen function if you want to dynamically determine the length)
|
||||
Strings and memory blocks
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
memcopy(from, to, numbytes)
|
||||
Efficiently copy a number of bytes from a memory location to another.
|
||||
NOTE: 'to' must NOT overlap with 'from', unless it is *before* 'from'.
|
||||
Because this function imposes some overhead to handle the parameters,
|
||||
it is only faster if the number of bytes is larger than a certain threshold.
|
||||
Compare the generated code to see if it was beneficial or not.
|
||||
The most efficient will often be to write a specialized copy routine in assembly yourself!
|
||||
|
||||
memset(address, numbytes, bytevalue)
|
||||
Efficiently set a part of memory to the given (u)byte value.
|
||||
But the most efficient will always be to write a specialized fill routine in assembly yourself!
|
||||
Note that for clearing the screen, very fast specialized subroutines are
|
||||
available in the ``textio`` and ``graphics`` library modules.
|
||||
|
||||
memsetw(address, numwords, wordvalue)
|
||||
Efficiently set a part of memory to the given (u)word value.
|
||||
But the most efficient will always be to write a specialized fill routine in assembly yourself!
|
||||
|
||||
leftstr(source, target, length)
|
||||
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).
|
||||
|
||||
rightstr(source, target, length)
|
||||
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).
|
||||
|
||||
strlen(str)
|
||||
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``
|
||||
|
||||
strcmp(string1, string2)
|
||||
Returns -1, 0 or 1 depeding on wether string1 sorts before, equal or after string2.
|
||||
Note that you can also directly compare strings and string values with eachother
|
||||
using ``==``, ``<`` etcetera (it will use strcmp for you under water automatically).
|
||||
|
||||
substr(source, target, start, length)
|
||||
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).
|
||||
|
||||
Miscellaneous
|
||||
^^^^^^^^^^^^^
|
||||
exit(returncode)
|
||||
Immediately stops the program and exits it, with the returncode in the A register.
|
||||
Note: custom interrupt handlers remain active unless manually cleared first!
|
||||
|
||||
lsb(x)
|
||||
Get the least significant byte of the word x. Equivalent to the cast "x as ubyte".
|
||||
@ -729,17 +823,9 @@ lsb(x)
|
||||
msb(x)
|
||||
Get the most significant byte of the word x.
|
||||
|
||||
sgn(x)
|
||||
Get the sign of the value. Result is -1, 0 or 1 (negative, zero, positive).
|
||||
|
||||
mkword(lsb, msb)
|
||||
Efficiently create a word value from two bytes (the lsb and the msb). Avoids multiplication and shifting.
|
||||
|
||||
any(x)
|
||||
1 ('true') if any of the values in the array value x is 'true' (not zero), else 0 ('false')
|
||||
|
||||
all(x)
|
||||
1 ('true') if all of the values in the array value x are 'true' (not zero), else 0 ('false')
|
||||
mkword(msb, lsb)
|
||||
Efficiently create a word value from two bytes (the msb and the lsb). Avoids multiplication and shifting.
|
||||
So mkword($80, $22) results in $8022.
|
||||
|
||||
rnd()
|
||||
returns a pseudo-random byte from 0..255
|
||||
@ -750,16 +836,6 @@ rndw()
|
||||
rndf()
|
||||
returns a pseudo-random float between 0.0 and 1.0
|
||||
|
||||
lsl(x)
|
||||
Shift the bits in x (byte or word) one position to the left.
|
||||
Bit 0 is set to 0 (and the highest bit is shifted into the status register's Carry flag)
|
||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||
|
||||
lsr(x)
|
||||
Shift the bits in x (byte or word) one position to the right.
|
||||
The highest bit is set to 0 (and bit 0 is shifted into the status register's Carry flag)
|
||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||
|
||||
rol(x)
|
||||
Rotate the bits in x (byte or word) one position to the left.
|
||||
This uses the CPU's rotate semantics: bit 0 will be set to the current value of the Carry flag,
|
||||
@ -784,35 +860,6 @@ ror2(x)
|
||||
It uses some extra logic to not consider the carry flag as extra rotation bit.
|
||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||
|
||||
memcopy(from, to, numbytes)
|
||||
Efficiently copy a number of bytes (1 - 256) from a memory location to another.
|
||||
NOTE: 'to' must NOT overlap with 'from', unless it is *before* 'from'.
|
||||
Because this function imposes some overhead to handle the parameters,
|
||||
it is only faster if the number of bytes is larger than a certain threshold.
|
||||
Compare the generated code to see if it was beneficial or not.
|
||||
The most efficient will always be to write a specialized copy routine in assembly yourself!
|
||||
|
||||
memset(address, numbytes, bytevalue)
|
||||
Efficiently set a part of memory to the given (u)byte value.
|
||||
But the most efficient will always be to write a specialized fill routine in assembly yourself!
|
||||
Note that for clearing the character screen, very fast specialized subroutines are
|
||||
available in the ``c64scr`` block (part of the ``c64utils`` module)
|
||||
|
||||
memsetw(address, numwords, wordvalue)
|
||||
Efficiently set a part of memory to the given (u)word value.
|
||||
But the most efficient will always be to write a specialized fill routine in assembly yourself!
|
||||
|
||||
swap(x, y)
|
||||
Swap the values of numerical variables (or memory locations) x and y in a fast way.
|
||||
|
||||
set_carry() / clear_carry()
|
||||
Set (or clear) the CPU status register Carry flag. No result value.
|
||||
(translated into ``SEC`` or ``CLC`` cpu instruction)
|
||||
|
||||
set_irqd() / clear_irqd()
|
||||
Set (or clear) the CPU status register Interrupt Disable flag. No result value.
|
||||
(translated into ``SEI`` or ``CLI`` cpu instruction)
|
||||
|
||||
rsave()
|
||||
Saves the CPU registers and the status flags.
|
||||
You can now more or less 'safely' use the registers directly, until you
|
||||
@ -827,10 +874,22 @@ rrestore()
|
||||
read_flags()
|
||||
Returns the current value of the CPU status register.
|
||||
|
||||
exit(returncode)
|
||||
Immediately stops the program and exits it, with the returncode in the A register.
|
||||
Note: custom interrupt handlers remain active unless manually cleared first!
|
||||
sizeof(name)
|
||||
Number of bytes that the object 'name' occupies in memory. This is a constant determined by the data type of
|
||||
the object. For instance, for a variable of type uword, the sizeof is 2.
|
||||
For an 10 element array of floats, it is 50 (on the C-64, where a float is 5 bytes).
|
||||
Note: usually you will be interested in the number of elements in an array, use len() for that.
|
||||
|
||||
set_carry() / clear_carry()
|
||||
Set (or clear) the CPU status register Carry flag. No result value.
|
||||
(translated into ``SEC`` or ``CLC`` cpu instruction)
|
||||
|
||||
set_irqd() / clear_irqd()
|
||||
Set (or clear) the CPU status register Interrupt Disable flag. No result value.
|
||||
(translated into ``SEI`` or ``CLI`` cpu instruction)
|
||||
|
||||
swap(x, y)
|
||||
Swap the values of numerical variables (or memory locations) x and y in a fast way.
|
||||
|
||||
|
||||
Library routines
|
||||
|
@ -24,7 +24,7 @@ Everything after a semicolon ``;`` is a comment and is ignored.
|
||||
If the whole line is just a comment, it will be copied into the resulting assembly source code.
|
||||
This makes it easier to understand and relate the generated code. Examples::
|
||||
|
||||
A = 42 ; set the initial value to 42
|
||||
counter = 42 ; set the initial value to 42
|
||||
; next is the code that...
|
||||
|
||||
|
||||
@ -33,6 +33,13 @@ This makes it easier to understand and relate the generated code. Examples::
|
||||
Directives
|
||||
-----------
|
||||
|
||||
.. data:: %target <target>
|
||||
|
||||
Level: module.
|
||||
Global setting, specifies that this module can only work for the given compiler target.
|
||||
If compiled with a different target, compilation is aborted with an error message.
|
||||
|
||||
|
||||
.. data:: %output <type>
|
||||
|
||||
Level: module.
|
||||
@ -60,7 +67,8 @@ Directives
|
||||
- style ``kernalsafe`` -- use the part of the ZP that is 'free' or only used by BASIC routines,
|
||||
and don't change anything else. This allows full use of KERNAL ROM routines (but not BASIC routines),
|
||||
including default IRQs during normal system operation.
|
||||
When the program exits, a system reset is performed (because BASIC will be in a corrupt state).
|
||||
It's not possible to return cleanly to BASIC when the program exits. The only choice is
|
||||
to perform a system reset. (A ``system_reset`` subroutine is available in the syslib to help you do this)
|
||||
- style ``floatsafe`` -- like the previous one but also reserves the addresses that
|
||||
are required to perform floating point operations (from the BASIC kernel). No clean exit is possible.
|
||||
- style ``basicsafe`` -- the most restricted mode; only use the handful 'free' addresses in the ZP, and don't
|
||||
@ -70,10 +78,11 @@ Directives
|
||||
- style ``full`` -- claim the whole ZP for variables for the program, overwriting everything,
|
||||
except the few addresses mentioned above that are used by the system's IRQ routine.
|
||||
Even though the default IRQ routine is still active, it is impossible to use most BASIC and KERNAL ROM routines.
|
||||
This includes many floating point operations and several utility routines that do I/O, such as ``print_string``.
|
||||
As with ``kernalsafe``, it is not possible to cleanly exit the program, other than to reset the machine.
|
||||
This includes many floating point operations and several utility routines that do I/O, such as ``print``.
|
||||
This option makes programs smaller and faster because even more variables can
|
||||
be stored in the ZP (which allows for more efficient assembly code).
|
||||
It's not possible to return cleanly to BASIC when the program exits. The only choice is
|
||||
to perform a system reset. (A ``system_reset`` subroutine is available in the syslib to help you do this)
|
||||
- style ``dontuse`` -- don't use *any* location in the zeropage.
|
||||
|
||||
Also read :ref:`zeropage`.
|
||||
@ -110,33 +119,39 @@ Directives
|
||||
|
||||
Level: module, block.
|
||||
Sets special compiler options.
|
||||
For a module option, only the ``enable_floats`` option is recognised, which will tell the compiler
|
||||
to deal with floating point numbers (by using various subroutines from the Commodore-64 kernal).
|
||||
Otherwise, floating point support is not enabled.
|
||||
When used in a block with the ``force_output`` option, it will force the block to be outputted
|
||||
in the final program. Can be useful to make sure some
|
||||
data is generated that would otherwise be discarded because it's not referenced (such as sprite data).
|
||||
|
||||
- For a module option, there is ``enable_floats``, which will tell the compiler
|
||||
to deal with floating point numbers (by using various subroutines from the Commodore-64 kernal).
|
||||
Otherwise, floating point support is not enabled. Normally you don't have to use this yourself as
|
||||
importing the ``floats`` library is required anyway and that will enable it for you automatically.
|
||||
- There's also ``no_sysinit`` which cause the resulting program to *not* include
|
||||
the system re-initialization logic of clearing the screen, resetting I/O config etc. You'll have to
|
||||
take care of that yourself. The program will just start running from whatever state the machine is in when the
|
||||
program was launched.
|
||||
- When used in a block with the ``force_output`` option, it will force the block to be outputted
|
||||
in the final program. Can be useful to make sure some
|
||||
data is generated that would otherwise be discarded because it's not referenced (such as sprite data).
|
||||
|
||||
|
||||
.. data:: %asmbinary "<filename>" [, <offset>[, <length>]]
|
||||
|
||||
Level: block.
|
||||
This directive can only be used inside a block.
|
||||
The assembler will include the file as binary bytes at this point, prog8 will not process this at all.
|
||||
The optional offset and length can be used to select a particular piece of the file.
|
||||
The file is located relative to the current working directory!
|
||||
Level: block.
|
||||
This directive can only be used inside a block.
|
||||
The assembler will include the file as binary bytes at this point, prog8 will not process this at all.
|
||||
The optional offset and length can be used to select a particular piece of the file.
|
||||
The file is located relative to the current working directory!
|
||||
|
||||
.. data:: %asminclude "<filename>", "scopelabel"
|
||||
|
||||
Level: block.
|
||||
This directive can only be used inside a block.
|
||||
The assembler will include the file as raw assembly source text at this point,
|
||||
prog8 will not process this at all, with one exception: the labels.
|
||||
The scopelabel argument will be used as a prefix to access the labels from the included source code,
|
||||
otherwise you would risk symbol redefinitions or duplications.
|
||||
If you know what you are doing you can leave it as an empty string to not have a scope prefix.
|
||||
The compiler first looks for the file relative to the same directory as the module containing this statement is in,
|
||||
if the file can't be found there it is searched relative to the current directory.
|
||||
Level: block.
|
||||
This directive can only be used inside a block.
|
||||
The assembler will include the file as raw assembly source text at this point,
|
||||
prog8 will not process this at all, with one exception: the labels.
|
||||
The scopelabel argument will be used as a prefix to access the labels from the included source code,
|
||||
otherwise you would risk symbol redefinitions or duplications.
|
||||
If you know what you are doing you can leave it as an empty string to not have a scope prefix.
|
||||
The compiler first looks for the file relative to the same directory as the module containing this statement is in,
|
||||
if the file can't be found there it is searched relative to the current directory.
|
||||
|
||||
.. data:: %breakpoint
|
||||
|
||||
@ -157,7 +172,7 @@ Directives
|
||||
Identifiers
|
||||
-----------
|
||||
|
||||
Naming things in Prog8 is done via valid *identifiers*. They start with a letter or underscore,
|
||||
Naming things in Prog8 is done via valid *identifiers*. They start with a letter,
|
||||
and after that, a combination of letters, numbers, or underscores. Examples of valid identifiers::
|
||||
|
||||
a
|
||||
@ -165,7 +180,7 @@ and after that, a combination of letters, numbers, or underscores. Examples of v
|
||||
monkey
|
||||
COUNTER
|
||||
Better_Name_2
|
||||
_something_strange_
|
||||
something_strange__
|
||||
|
||||
|
||||
Code blocks
|
||||
@ -267,6 +282,7 @@ type identifier type storage size example var declara
|
||||
``word[]`` signed word array depends on value ``word[] myvar = [1, 2, 3, 4]``
|
||||
``uword[]`` unsigned word array depends on value ``uword[] myvar = [1, 2, 3, 4]``
|
||||
``float[]`` floating-point array depends on value ``float[] myvar = [1.1, 2.2, 3.3, 4.4]``
|
||||
``str[]`` array with string ptrs 2*x bytes + strs ``str[] names = ["ally", "pete"]``
|
||||
``str`` string (petscii) varies ``str myvar = "hello."``
|
||||
implicitly terminated by a 0-byte
|
||||
=============== ======================= ================= =========================================
|
||||
@ -289,7 +305,8 @@ of something with an operand starting with 1 or 0, you'll have to add a space in
|
||||
- When an integer value ranges from 256..65535 the compiler sees it as a ``uword``. For -32768..32767 it's a ``word``.
|
||||
- When a hex number has 3 or 4 digits, for example ``$0004``, it is seen as a ``word`` otherwise as a ``byte``.
|
||||
- When a binary number has 9 to 16 digits, for example ``%1100110011``, it is seen as a ``word`` otherwise as a ``byte``.
|
||||
- You can force a byte value into a word value by adding the ``.w`` datatype suffix to the number: ``$2a.w`` is equivalent to ``$002a``.
|
||||
- If the number fits in a byte but you really require it as a word value, you'll have to explicitly cast it: ``60 as uword``
|
||||
or you can use the full word hexadecimal notation ``$003c``.
|
||||
|
||||
|
||||
Data type conversion
|
||||
@ -306,6 +323,7 @@ should be allocated by the compiler. Instead, the (mandatory) value assigned to
|
||||
should be the *memory address* where the value is located::
|
||||
|
||||
&byte BORDERCOLOR = $d020
|
||||
&ubyte[5*40] top5screenrows = $0400 ; works for array as well
|
||||
|
||||
|
||||
Direct access to memory locations
|
||||
@ -313,7 +331,7 @@ Direct access to memory locations
|
||||
Instead of defining a memory mapped name for a specific memory location, you can also
|
||||
directly access the memory. Enclose a numeric expression or literal with ``@(...)`` to do that::
|
||||
|
||||
A = @($d020) ; set the A register to the current c64 screen border color ("peek(53280)")
|
||||
color = @($d020) ; set the variable 'color' to the current c64 screen border color ("peek(53280)")
|
||||
@($d020) = 0 ; set the c64 screen border to black ("poke 53280,0")
|
||||
@(vic+$20) = 6 ; a dynamic expression to 'calculate' the address
|
||||
|
||||
@ -333,8 +351,6 @@ Reserved names
|
||||
|
||||
The following names are reserved, they have a special meaning::
|
||||
|
||||
A X Y ; 6502 hardware registers
|
||||
Pc Pz Pn Pv ; 6502 status register flags
|
||||
true false ; boolean values 1 and 0
|
||||
|
||||
|
||||
@ -384,7 +400,25 @@ After defining a struct you can use the name of the struct as a data type to dec
|
||||
|
||||
Struct variables can be assigned a struct literal value (also in their declaration as initial value)::
|
||||
|
||||
Color rgb = {255, 100, 0} ; curly braces instead of brackets
|
||||
Color rgb = [255, 100, 0] ; note that the value is an array
|
||||
|
||||
|
||||
String
|
||||
^^^^^^
|
||||
|
||||
``"hello"`` is a string translated into the default character encoding (PETSCII)
|
||||
|
||||
``@"hello"`` is a string translated into the alternate character encoding (Screencodes/pokes)
|
||||
|
||||
There are several escape sequences available to put special characters into your string value:
|
||||
|
||||
- ``\\`` - the backslash itself, has to be escaped because it is the escape symbol by itself
|
||||
- ``\n`` - newline character (move cursor down and to beginning of next line)
|
||||
- ``\r`` - carriage return character (more or less the same as newline if printing to the screen)
|
||||
- ``\"`` - quote character (otherwise it would terminate the string)
|
||||
- ``\'`` - apostrophe character (has to be escaped in character literals, is okay inside a string)
|
||||
- ``\uHHHH`` - a unicode codepoint \u0000 - \uffff (16-bit hexadecimal)
|
||||
- ``\xHH`` - 8-bit hex value that will be copied verbatim *without encoding*
|
||||
|
||||
|
||||
Operators
|
||||
@ -406,10 +440,10 @@ assignment: ``=``
|
||||
Note that an assignment sometimes is not possible or supported.
|
||||
|
||||
augmented assignment: ``+=`` ``-=`` ``*=`` ``/=`` ``**=`` ``&=`` ``|=`` ``^=`` ``<<=`` ``>>=``
|
||||
Syntactic sugar; ``A += X`` is equivalent to ``A = A + X``
|
||||
This is syntactic sugar; ``aa += xx`` is equivalent to ``aa = aa + xx``
|
||||
|
||||
postfix increment and decrement: ``++`` ``--``
|
||||
Syntactic sugar; ``A++`` is equivalent to ``A = A + 1``, and ``A--`` is equivalent to ``A = A - 1``.
|
||||
Syntactic sugar; ``aa++`` is equivalent to ``aa = aa + 1``, and ``aa--`` is equivalent to ``aa = aa - 1``.
|
||||
Because these operations are so common, we have these short forms.
|
||||
|
||||
comparison: ``!=`` ``<`` ``>`` ``<=`` ``>=``
|
||||
@ -427,9 +461,9 @@ range creation: ``to``
|
||||
|
||||
0 to 7 ; range of values 0, 1, 2, 3, 4, 5, 6, 7 (constant)
|
||||
|
||||
A = 5
|
||||
X = 10
|
||||
A to X ; range of 5, 6, 7, 8, 9, 10
|
||||
aa = 5
|
||||
aa = 10
|
||||
aa to xx ; range of 5, 6, 7, 8, 9, 10
|
||||
|
||||
byte[] array = 10 to 13 ; sets the array to [1, 2, 3, 4]
|
||||
|
||||
@ -471,6 +505,8 @@ takes no parameters. If the subroutine returns a value, usually you assign it t
|
||||
If you're not interested in the return value, prefix the function call with the ``void`` keyword.
|
||||
Otherwise the compiler will warn you about discarding the result of the call.
|
||||
|
||||
Multiple return values
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
Normal subroutines can only return zero or one return values.
|
||||
However, the special ``asmsub`` routines (implemented in assembly code) or ``romsub`` routines
|
||||
(referencing a routine in kernel ROM) can return more than one return value.
|
||||
@ -478,9 +514,16 @@ For example a status in the carry bit and a number in A, or a 16-bit value in A/
|
||||
It is not possible to process the results of a call to these kind of routines
|
||||
directly from the language, because only single value assignments are possible.
|
||||
You can still call the subroutine and not store the results.
|
||||
But if you want to do something with the values it returns, you'll have to write
|
||||
a small block of custom inline assembly that does the call and stores the values
|
||||
appropriately. Don't forget to save/restore the registers if required.
|
||||
|
||||
**There is an exception:** if there's just one return value in a register, and one or more others that are returned
|
||||
as bits in the status register (such as the Carry bit), the compiler allows you to call the subroutine.
|
||||
It will then store the result value in a variable if required, and *keep the status register untouched
|
||||
after the call* so you can use a conditional branch statement for that.
|
||||
Note that this makes no sense inside an expression, so the compiler will still give an error for that.
|
||||
|
||||
If there really are multiple return values (other than a combined 16 bit return value in 2 registers),
|
||||
you'll have to write a small block of custom inline assembly that does the call and stores the values
|
||||
appropriately. Don't forget to save/restore any registers that are modified.
|
||||
|
||||
|
||||
Subroutine definitions
|
||||
@ -515,18 +558,20 @@ and returning stuff in several registers as well. The ``clobbers`` clause is use
|
||||
what CPU registers are clobbered by the call instead of being unchanged or returning a meaningful result value.
|
||||
|
||||
|
||||
Subroutines that are implemented purely in assembly code and which have an assembly calling convention (i.e.
|
||||
the parameters are strictly passed via cpu registers), are defined like this::
|
||||
User subroutines in the program source code that are implemented purely in assembly and which have an assembly calling convention (i.e.
|
||||
the parameters are strictly passed via cpu registers), are defined with ``asmsub`` like this::
|
||||
|
||||
asmsub FREADS32() clobbers(A,X,Y) {
|
||||
asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
|
||||
%asm {{
|
||||
lda $62
|
||||
eor #$ff
|
||||
asl a
|
||||
lda #0
|
||||
ldx #$a0
|
||||
jmp $bc4f
|
||||
}}
|
||||
ldy #0
|
||||
_loop sta c64.Screen,y
|
||||
sta c64.Screen+$0100,y
|
||||
sta c64.Screen+$0200,y
|
||||
sta c64.Screen+$02e8,y
|
||||
iny
|
||||
bne _loop
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
the statement body of such a subroutine should consist of just an inline assembly block.
|
||||
@ -551,7 +596,7 @@ Loops
|
||||
for loop
|
||||
^^^^^^^^
|
||||
|
||||
The loop variable must be a register or a byte/word variable,
|
||||
The loop variable must be a byte or word variable,
|
||||
and must be defined first in the local scope of the for loop.
|
||||
The expression that you loop over can be anything that supports iteration (such as ranges like ``0 to 100``,
|
||||
array variables and strings) *except* floating-point arrays (because a floating-point loop variable is not supported).
|
||||
@ -561,7 +606,6 @@ You can use a single statement, or a statement block like in the example below::
|
||||
for <loopvar> in <expression> [ step <amount> ] {
|
||||
; do something...
|
||||
break ; break out of the loop
|
||||
continue ; immediately enter next iteration
|
||||
}
|
||||
|
||||
For example, this is a for loop using a byte variable ``i``, defined before, to loop over a certain range of numbers::
|
||||
@ -594,35 +638,35 @@ You can use a single statement, or a statement block like in the example below::
|
||||
while <condition> {
|
||||
; do something...
|
||||
break ; break out of the loop
|
||||
continue ; immediately enter next iteration
|
||||
}
|
||||
|
||||
|
||||
repeat-until loop
|
||||
^^^^^^^^^^^^^^^^^
|
||||
do-until loop
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
Until the given condition is true (1), repeat the given statement(s).
|
||||
You can use a single statement, or a statement block like in the example below::
|
||||
|
||||
repeat {
|
||||
do {
|
||||
; do something...
|
||||
break ; break out of the loop
|
||||
continue ; immediately enter next iteration
|
||||
} until <condition>
|
||||
|
||||
|
||||
forever loop
|
||||
^^^^^^^^^^^^
|
||||
repeat loop
|
||||
^^^^^^^^^^^
|
||||
|
||||
Simply run the code in a loop, forever. It's the same as a while true or until false loop,
|
||||
or just a jump back to a previous label. You can still break out of this loop as well, if you want::
|
||||
When you're only interested in repeating something a given number of times.
|
||||
It's a short hand for a for loop without an explicit loop variable::
|
||||
|
||||
forever {
|
||||
; .. do stuff
|
||||
if something
|
||||
break ; you can exit the loop if you want
|
||||
repeat 15 {
|
||||
; do something...
|
||||
break ; you can break out of the loop
|
||||
}
|
||||
|
||||
If you omit the iteration count, it simply loops forever.
|
||||
You can still ``break`` out of such a loop if you want though.
|
||||
|
||||
|
||||
Conditional Execution and Jumps
|
||||
-------------------------------
|
||||
@ -702,3 +746,4 @@ case you have to use { } to enclose them::
|
||||
}
|
||||
else -> c64scr.print("don't know")
|
||||
}
|
||||
|
||||
|
@ -4,12 +4,21 @@ Target system specification
|
||||
|
||||
Prog8 targets the following hardware:
|
||||
|
||||
- 8 bit MOS 6502/6510 CPU
|
||||
- 8 bit MOS 6502/65c02/6510 CPU
|
||||
- 64 Kb addressable memory (RAM or ROM)
|
||||
- memory mapped I/O registers
|
||||
- optional use of memory mapped I/O registers
|
||||
- optional use of system ROM routines
|
||||
|
||||
The main target machine is the Commodore-64, which is an example of this.
|
||||
This chapter explains the relevant system details of such a machine.
|
||||
Currently there are two machines that are supported as compiler target (selectable via the ``-target`` compiler argument):
|
||||
|
||||
- 'c64': the well-known Commodore-64, premium support
|
||||
- 'cx16': the `CommanderX16 <https://www.commanderx16.com/>`_ a project from the 8-Bit Guy. Support for this is still experimental.
|
||||
|
||||
This chapter explains the relevant system details of these machines.
|
||||
|
||||
.. hint::
|
||||
If you only use standard kernel and prog8 library routines,
|
||||
it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
|
||||
|
||||
|
||||
Memory Model
|
||||
@ -113,22 +122,14 @@ CPU
|
||||
Directly Usable Registers
|
||||
-------------------------
|
||||
|
||||
The following 6502 CPU hardware registers are directly usable in program code (and are reserved symbols):
|
||||
The hardware CPU registers are not directly accessible from regular Prog8 code.
|
||||
If you need to mess with them, you'll have to use inline assembly.
|
||||
Be extra wary of the ``X`` register because it is used as an evaluation stack pointer and
|
||||
changing its value you will destroy the evaluation stack and likely crash the program.
|
||||
|
||||
- ``A``, ``X``, ``Y`` the three main cpu registers (8 bits)
|
||||
- the status register (P) carry flag and interrupt disable flag can be written via a couple of special
|
||||
builtin functions (``set_carry()``, ``clear_carry()``, ``set_irqd()``, ``clear_irqd()``),
|
||||
and read via the ``read_flags()`` function.
|
||||
|
||||
However, you must assume that the 3 hardware registers ``A``, ``X`` and ``Y``
|
||||
are volatile. Their values cannot be depended upon, the compiler will use them as required.
|
||||
Even simple assignments may require modification of one or more of the registers (for instance, when using arrays).
|
||||
|
||||
Even more important, the ``X`` register is used as an evaluation stack pointer.
|
||||
If you mess with it, you will destroy the evaluation stack and likely crash your program.
|
||||
In some cases the compiler will warn you about this, but you should really avoid to use
|
||||
this register. It's possible to store/restore the register's value (using special built in functions)
|
||||
for the cases you really really need to use it directly.
|
||||
The status register (P) carry flag and interrupt disable flag can be written via a couple of special
|
||||
builtin functions (``set_carry()``, ``clear_carry()``, ``set_irqd()``, ``clear_irqd()``),
|
||||
and read via the ``read_flags()`` function.
|
||||
|
||||
|
||||
Subroutine Calling Conventions
|
||||
@ -155,15 +156,15 @@ You can however install your own IRQ handler.
|
||||
This is possible ofcourse by doing it all using customized inline assembly,
|
||||
but there are a few library routines available to make setting up C-64 IRQs and raster IRQs a lot easier (no assembly code required).
|
||||
|
||||
These routines are::
|
||||
For the C64 these routines are::
|
||||
|
||||
c64utils.set_irqvec()
|
||||
c64utils.set_irqvec_excl()
|
||||
c64.set_irqvec()
|
||||
c64.set_irqvec_excl()
|
||||
|
||||
c64utils.set_rasterirq( <raster line> )
|
||||
c64utils.set_rasterirq_excl( <raster line> )
|
||||
c64.set_rasterirq( <raster line> )
|
||||
c64.set_rasterirq_excl( <raster line> )
|
||||
|
||||
c64utils.restore_irqvec() ; set it back to the systems default irq handler
|
||||
c64.restore_irqvec() ; set it back to the systems default irq handler
|
||||
|
||||
If you activate an IRQ handler with one of these, it expects the handler to be defined
|
||||
as a subroutine ``irq`` in the module ``irq`` so like this::
|
||||
@ -173,3 +174,4 @@ as a subroutine ``irq`` in the module ``irq`` so like this::
|
||||
; ... irq handling here ...
|
||||
}
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user