mirror of
https://github.com/irmen/prog8.git
synced 2025-06-17 16:23:42 +00:00
Compare commits
332 Commits
Author | SHA1 | Date | |
---|---|---|---|
9d4ec4a9b2 | |||
cdc6d9aa65 | |||
997bc21feb | |||
975af4764d | |||
bf69219f98 | |||
f34f9329f1 | |||
90271d0dcd | |||
195cd7597d | |||
4a81406262 | |||
f9fd426843 | |||
e612056ecd | |||
6f0103398b | |||
afb60db382 | |||
5731b876ff | |||
055f917a2e | |||
4ed7fb771c | |||
c328e9018c | |||
b270f6f713 | |||
5c13918f11 | |||
40cc216557 | |||
1481f92cb0 | |||
76d54fbe5c | |||
9f72779cdc | |||
3dcef89a74 | |||
46373717b6 | |||
7277c08fa6 | |||
04e75455c4 | |||
8ac17ae14e | |||
cb5d6ddf80 | |||
e0794db33a | |||
b128b79132 | |||
79e6d4b8dd | |||
b9ddde0f12 | |||
a0ec37b35b | |||
506ac8014c | |||
72b4198301 | |||
24eee0cb34 | |||
9fc0c3f849 | |||
db314ed903 | |||
1ef9b8be61 | |||
79782ad547 | |||
4b6d045df1 | |||
b4d1d545a8 | |||
f61682cdc7 | |||
d61420f1c6 | |||
3d09d605e1 | |||
025dde264a | |||
87cee7a0fd | |||
61784a03bb | |||
9d9ca0f08d | |||
58f37513e7 | |||
ee7f9d457d | |||
bec2224c3d | |||
4305984168 | |||
07dd64958f | |||
76101d7f8d | |||
7d6a0ab256 | |||
4309a0dc68 | |||
dde6919446 | |||
54fc9c91ac | |||
41658c97a3 | |||
45c9cc97d9 | |||
6fa7debee5 | |||
ee9f662016 | |||
3550e1214c | |||
8dcb43ad1c | |||
e6a1442296 | |||
cb65480c6c | |||
3e7c7ab497 | |||
f0930d8a18 | |||
5a846bdeb5 | |||
baf9dfb46c | |||
edd3a22848 | |||
583428b19c | |||
08d44ae553 | |||
b3b2541c1e | |||
8e927e0b73 | |||
8e3e996f4a | |||
b6fa361bcc | |||
ca83092aed | |||
3cda92331e | |||
c989abe265 | |||
89230ade7a | |||
b4931c9a1f | |||
ddfcf45d40 | |||
ee12236d53 | |||
df6698c98f | |||
c3b82f2cfa | |||
64c89f1c8f | |||
e09b65ea94 | |||
c81952c356 | |||
f80e462d25 | |||
51f32677b7 | |||
4b366358c4 | |||
3378586098 | |||
6777d952c1 | |||
6c8b18ddbd | |||
69780ecde9 | |||
9e2c52e1ec | |||
6cb0e6a936 | |||
dd82e550d5 | |||
cdcda27d07 | |||
ffffcdd50a | |||
d37d62574c | |||
f2380457d6 | |||
efa42d5d96 | |||
e17c18b653 | |||
7607d3d64a | |||
d7d7147d43 | |||
b40e1eabb9 | |||
3b8e18004c | |||
4c03950c28 | |||
170a0183f8 | |||
c62ff16f8b | |||
ab495fe6e1 | |||
c2a8dc23d0 | |||
6734ae3c88 | |||
4c1c595f14 | |||
9002c67639 | |||
b91aabd3c0 | |||
3307f673f6 | |||
07b00bec61 | |||
e0d2b60d8b | |||
45bfecee73 | |||
80e3a11268 | |||
38a6c6a866 | |||
8f224afed9 | |||
48a4c46a6c | |||
7d08380c7f | |||
b3b3cf3807 | |||
f0f6150e18 | |||
dc600cc3ed | |||
ae648b8a0a | |||
583af3bd4f | |||
d65cfbf093 | |||
118aed2e31 | |||
85abf4d123 | |||
44b8291540 | |||
d6444bba66 | |||
5a2f8fdfe1 | |||
bba4f84503 | |||
684e081399 | |||
96c700ee46 | |||
5f15794c3b | |||
a40b3134f4 | |||
c70b4daf87 | |||
928611eb20 | |||
f1d55c688a | |||
d22df22f7d | |||
061e1be0a4 | |||
950bc4b937 | |||
dcb81e6bea | |||
daaa83ee7d | |||
b7c1450121 | |||
787f52d1f8 | |||
50213f146a | |||
7f2aea60c9 | |||
168621f7c2 | |||
8b630798d8 | |||
52e8a44517 | |||
59f33658ad | |||
e0315bffdc | |||
cd28d0c0e0 | |||
0baa2c8b23 | |||
4977d1fbd5 | |||
3b7a92f1b4 | |||
f6920172dd | |||
93bfc8f5f4 | |||
39b7655264 | |||
8b75ceb412 | |||
c39fc4010d | |||
8df778a515 | |||
5134ea76bf | |||
3ba37df29d | |||
e221d674d9 | |||
251f947293 | |||
41e1e1cbb0 | |||
da1bc351d2 | |||
43c0afdea0 | |||
add5bfa2ec | |||
34babfb5de | |||
4f6c45c86c | |||
e6220a464c | |||
8dcd49934a | |||
bedc3bdb56 | |||
83ceb0fde9 | |||
1d299c56e0 | |||
0d735c2ccc | |||
4094f89d4a | |||
cf1e8b194a | |||
74e5644f55 | |||
b5dc5fc615 | |||
7a7270d769 | |||
7549ddcd2b | |||
08f0303178 | |||
0d7a291b81 | |||
2265ae9600 | |||
cba502e87a | |||
ac94236614 | |||
ddf1be2a13 | |||
b7694686c2 | |||
63332c0530 | |||
8a504f8eee | |||
106fc5daa4 | |||
7accb73993 | |||
e9aa6a0956 | |||
df20467e03 | |||
ecbd9d739e | |||
8af17c295a | |||
329b28cad1 | |||
452c29574d | |||
5bedc1b333 | |||
0bf6d2f72c | |||
c09b8af491 | |||
260bcd3a55 | |||
6b5211ad12 | |||
a92ec14989 | |||
b3348eb22b | |||
bec5a261e5 | |||
4b53641e1d | |||
00071d53d5 | |||
6902834568 | |||
fa2d87f3dd | |||
44019d1a61 | |||
6f74fb49bd | |||
a303b39cf0 | |||
3e63a29c59 | |||
261c0fc9b6 | |||
895b30f7e5 | |||
b985604e22 | |||
f7953e4ef3 | |||
63483d1f0e | |||
8b981f03bf | |||
d0d0910bf2 | |||
57ac820767 | |||
b8bda867b6 | |||
05d3a2450c | |||
d40788adfa | |||
83fbf86b1c | |||
e876008427 | |||
2b43353eb4 | |||
a74403c347 | |||
2f4c6c8697 | |||
238d8197f5 | |||
53a600d87b | |||
2a0ffaf45d | |||
936b046ed9 | |||
378dcfe351 | |||
0a330b9288 | |||
a88b40d6c1 | |||
09f25ffbd9 | |||
ab1232d742 | |||
a7f56fe0fc | |||
58a9452c36 | |||
6d8c4f403f | |||
88b80fed90 | |||
acdbd0c391 | |||
d9a8cfed8c | |||
122796fbba | |||
510ca042c9 | |||
125f6205f2 | |||
8136f3df5c | |||
38d06a7e94 | |||
49db10539a | |||
8efe4c6267 | |||
04d8bb8fbf | |||
08aa13c90c | |||
d1febc0208 | |||
5980e58ac6 | |||
e1dc283d4b | |||
8be234973c | |||
7def8ff2cd | |||
340b1c2e42 | |||
7e0f7ba438 | |||
fefd9b52a8 | |||
afd155ac4f | |||
ee724eb4f1 | |||
2f1f20ea11 | |||
063bcf17d8 | |||
72509eef44 | |||
2da28864e9 | |||
4278f64682 | |||
59ae3c3fcd | |||
7fa21fbdff | |||
e95af7498e | |||
79c75adac1 | |||
d212f69d89 | |||
edf5e69d39 | |||
574eb0d174 | |||
8bd4914e2f | |||
5ebaaff64b | |||
5c9e0c9f51 | |||
8132edbb08 | |||
d29ce78c86 | |||
94bc9d7a69 | |||
e8faec0932 | |||
69ca4fe304 | |||
cd99fe46fd | |||
4825b4dc68 | |||
8d0607ef58 | |||
225295a7d8 | |||
4cd74daf53 | |||
6eb9118197 | |||
d0bd2f522c | |||
661c757236 | |||
aaa20093ef | |||
1eecdd6fa3 | |||
800b5b2a43 | |||
9d17421c66 | |||
0edd50e956 | |||
288d4f08b3 | |||
526e4b8bdc | |||
e0c5ccc16b | |||
ebc2c614d7 | |||
29f5a85158 | |||
8af2380a47 | |||
431f2a2088 | |||
e05ea887f6 | |||
95c0425151 | |||
47cbc7b1f9 | |||
e7b75d591c | |||
99f7d469f4 | |||
8a6ef17fbf | |||
5f337a0bd9 | |||
87862f772a | |||
3ab641aa21 | |||
3efa8da8e0 | |||
3e28ed4fe4 | |||
44949460ed | |||
83cc19ad6f | |||
66bb98c479 | |||
ff3f985658 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -29,3 +29,5 @@ parsetab.py
|
||||
|
||||
.gradle
|
||||
/prog8compiler.jar
|
||||
sd*.img
|
||||
|
||||
|
9
.idea/libraries/antlr_4_7_2_complete.xml
generated
9
.idea/libraries/antlr_4_7_2_complete.xml
generated
@ -1,9 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="antlr-4.7.2-complete">
|
||||
<CLASSES>
|
||||
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-4.7.2-complete.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
@ -1,7 +1,7 @@
|
||||
<component name="libraryTable">
|
||||
<library name="antlr-4.8-complete">
|
||||
<library name="antlr-4.9-complete">
|
||||
<CLASSES>
|
||||
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-4.8-complete.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-4.9-complete.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
9
.idea/libraries/antlr_runtime_4_7_2.xml
generated
9
.idea/libraries/antlr_runtime_4_7_2.xml
generated
@ -1,9 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="antlr-runtime-4.7.2">
|
||||
<CLASSES>
|
||||
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-runtime-4.7.2.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
@ -1,7 +1,7 @@
|
||||
<component name="libraryTable">
|
||||
<library name="antlr-runtime-4.8">
|
||||
<library name="antlr-runtime-4.9">
|
||||
<CLASSES>
|
||||
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-runtime-4.8.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-runtime-4.9.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
9
.idea/libraries/dbus_java_3_2_4.xml
generated
Normal file
9
.idea/libraries/dbus_java_3_2_4.xml
generated
Normal file
@ -0,0 +1,9 @@
|
||||
<component name="libraryTable">
|
||||
<library name="dbus-java-3.2.4">
|
||||
<CLASSES>
|
||||
<root url="jar://$PROJECT_DIR$/dbusCompilerService/lib/dbus-java-3.2.4.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
10
.idea/libraries/javax_json_api_1_1_4.xml
generated
Normal file
10
.idea/libraries/javax_json_api_1_1_4.xml
generated
Normal file
@ -0,0 +1,10 @@
|
||||
<component name="libraryTable">
|
||||
<library name="javax.json-api-1.1.4">
|
||||
<CLASSES>
|
||||
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/javax.json-api-1.1.4.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/javax.json-1.1.4.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
@ -1,7 +1,7 @@
|
||||
<component name="libraryTable">
|
||||
<library name="kotlinx-cli-jvm-0.1.0-dev-5">
|
||||
<library name="kotlinx-cli-jvm">
|
||||
<CLASSES>
|
||||
<root url="jar://$PROJECT_DIR$/compiler/lib/kotlinx-cli-jvm-0.1.0-dev-5.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/compiler/lib/kotlinx-cli-jvm-0.3.1.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
10
.idea/libraries/slf4j_api_1_7_30.xml
generated
Normal file
10
.idea/libraries/slf4j_api_1_7_30.xml
generated
Normal file
@ -0,0 +1,10 @@
|
||||
<component name="libraryTable">
|
||||
<library name="slf4j-api-1.7.30">
|
||||
<CLASSES>
|
||||
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/slf4j-api-1.7.30.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/slf4j-simple-1.7.30.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
12
.idea/libraries/takes_http.xml
generated
Normal file
12
.idea/libraries/takes_http.xml
generated
Normal file
@ -0,0 +1,12 @@
|
||||
<component name="libraryTable">
|
||||
<library name="takes-http">
|
||||
<CLASSES>
|
||||
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/cactoos-0.42.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/commons-lang3-3.7.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/commons-text-1.4.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/takes-1.19.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@ -16,7 +16,7 @@
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="false" project-jdk-name="Kotlin SDK" project-jdk-type="KotlinSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="false" project-jdk-name="Kotlin SDK" project-jdk-type="KotlinSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
2
.idea/modules.xml
generated
2
.idea/modules.xml
generated
@ -3,8 +3,10 @@
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.iml" filepath="$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/httpCompilerService/httpCompilerService.iml" filepath="$PROJECT_DIR$/httpCompilerService/httpCompilerService.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
|
44
README.md
44
README.md
@ -20,36 +20,38 @@ Full documentation (syntax reference, how to use the language and the compiler,
|
||||
https://prog8.readthedocs.io/
|
||||
|
||||
|
||||
What use Prog8 provide?
|
||||
-----------------------
|
||||
What does Prog8 provide?
|
||||
------------------------
|
||||
|
||||
- reduction of source code length over raw assembly
|
||||
- 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 an input- and output parameter signature
|
||||
- constant folding in expressions
|
||||
- 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
|
||||
- floating point operations (requires the C64 Basic ROM routines for this)
|
||||
- 'when' statement to provide a concise jump table alternative to if/elseif chains
|
||||
- structs to group together sets of variables and manipulate them at once
|
||||
- floating point operations (requires the C64 Basic ROM routines for this)
|
||||
- abstracting away low level aspects such as ZeroPage handling, program startup, explicit memory addresses
|
||||
- various code optimizations (code structure, logical and numerical expressions, unused code removal...)
|
||||
- many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``sort`` and ``reverse``
|
||||
- various powerful built-in libraries to do I/O, number conversions, graphics and more
|
||||
- convenience abstractions for low level aspects such as ZeroPage handling, program startup, explicit memory addresses
|
||||
- fast execution speed due to compilation to native assembly code
|
||||
- inline assembly allows you to have full control when every cycle or byte matters
|
||||
- many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``, ``sort`` and ``reverse``
|
||||
- supports the sixteen 'virtual' 16-bit registers R0 .. R15 from the Commander X16, and provides them also on the C64.
|
||||
|
||||
*Rapid edit-compile-run-debug cycle:*
|
||||
|
||||
- use a modern PC to do the work on
|
||||
- very quick compilation times
|
||||
- use a modern PC to do the work on, use nice editors and enjoy quick compilation times
|
||||
- can automatically run the program in the Vice emulator after succesful compilation
|
||||
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them
|
||||
- source code labels automatically loaded in Vice emulator so it can show them in disassembly
|
||||
|
||||
*Two supported compiler targets* (contributions to improve these or to add support for other machines are welcome!):
|
||||
|
||||
- "c64": Commodore-64 (6510 CPU = almost a 6502) premium support.
|
||||
- "cx16": [CommanderX16](https://www.commanderx16.com) (65c02 CPU) experimental support.
|
||||
- "c64": Commodore-64 (6510 CPU = almost a 6502)
|
||||
- "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)!
|
||||
|
||||
|
||||
@ -61,7 +63,7 @@ Additional required tools
|
||||
A recent .exe version of this tool for Windows can be obtained from my [clone](https://github.com/irmen/64tass/releases) of this project.
|
||||
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).
|
||||
|
||||
@ -84,9 +86,7 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
||||
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)
|
||||
; calculate primes
|
||||
sys.memset(sieve, 256, false) ; clear the sieve
|
||||
txt.print("prime numbers up to 255:\n\n")
|
||||
ubyte amount=0
|
||||
repeat {
|
||||
@ -97,17 +97,17 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
||||
txt.print(", ")
|
||||
amount++
|
||||
}
|
||||
txt.chrout('\n')
|
||||
txt.nl()
|
||||
txt.print("number of primes (expected 54): ")
|
||||
txt.print_ub(amount)
|
||||
txt.chrout('\n')
|
||||
txt.nl()
|
||||
}
|
||||
|
||||
sub find_next_prime() -> ubyte {
|
||||
while sieve[candidate_prime] {
|
||||
candidate_prime++
|
||||
if candidate_prime==0
|
||||
return 0 ; we wrapped; no more primes available in the sieve
|
||||
return 0 ; we wrapped; no more primes
|
||||
}
|
||||
|
||||
; found next one, mark the multiples and return it.
|
||||
@ -124,6 +124,7 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
||||
|
||||
|
||||
|
||||
|
||||
when compiled an ran on a C-64 you'll get:
|
||||
|
||||

|
||||
@ -140,7 +141,8 @@ If you want to play a video game, a fully working Tetris clone is included in th
|
||||
|
||||

|
||||
|
||||
The CommanderX16 compiler target is quite capable already too, here's a well known space ship
|
||||
animated in 3D with hidden line removal, in the CommanderX16 emulator:
|
||||
There are a couple of examples specially made for the CommanderX16 compiler target.
|
||||
For instance here's a well known space ship animated in 3D with hidden line removal,
|
||||
in the CommanderX16 emulator:
|
||||
|
||||

|
||||
|
@ -1,28 +1,18 @@
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.10"
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
// id "org.jetbrains.kotlin.jvm" version "1.4.10"
|
||||
id 'application'
|
||||
id 'org.jetbrains.dokka' version "0.9.18"
|
||||
id 'com.github.johnrengelman.shadow' version '5.2.0'
|
||||
id 'java'
|
||||
id 'application'
|
||||
id "org.jetbrains.kotlin.jvm" version "1.4.21"
|
||||
id 'org.jetbrains.dokka' version "0.9.18"
|
||||
id 'com.github.johnrengelman.shadow' version '6.1.0'
|
||||
}
|
||||
|
||||
apply plugin: "kotlin"
|
||||
apply plugin: "java"
|
||||
|
||||
targetCompatibility = 1.8
|
||||
sourceCompatibility = 1.8
|
||||
targetCompatibility = 11
|
||||
sourceCompatibility = 11
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
maven { url "https://dl.bintray.com/orangy/maven/" }
|
||||
maven { url "https://kotlin.bintray.com/kotlinx" }
|
||||
}
|
||||
|
||||
def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
|
||||
@ -32,9 +22,9 @@ dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation 'org.antlr:antlr4-runtime:4.8'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-cli-jvm:0.1.0-dev-5'
|
||||
// implementation 'net.razorvine:ksim65:1.6'
|
||||
// implementation "com.github.hypfvieh:dbus-java:3.2.0"
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.1'
|
||||
// implementation 'net.razorvine:ksim65:1.8'
|
||||
// implementation "com.github.hypfvieh:dbus-java:3.2.4"
|
||||
implementation project(':parser')
|
||||
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
|
||||
@ -45,7 +35,7 @@ dependencies {
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
jvmTarget = "11"
|
||||
// verbose = true
|
||||
// freeCompilerArgs += "-XXLanguage:+NewInference"
|
||||
}
|
||||
@ -53,7 +43,7 @@ compileKotlin {
|
||||
|
||||
compileTestKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
jvmTarget = "11"
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,7 +66,8 @@ sourceSets {
|
||||
startScripts.enabled = true
|
||||
|
||||
application {
|
||||
mainClassName = 'prog8.CompilerMainKt'
|
||||
mainClass = 'prog8.CompilerMainKt'
|
||||
mainClassName = 'prog8.CompilerMainKt' // deprecated
|
||||
applicationName = 'p8compile'
|
||||
}
|
||||
|
||||
@ -112,5 +103,5 @@ dokka {
|
||||
}
|
||||
|
||||
task wrapper(type: Wrapper) {
|
||||
gradleVersion = '6.1.1'
|
||||
gradleVersion = '6.7'
|
||||
}
|
||||
|
@ -13,7 +13,7 @@
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="module" module-name="parser" />
|
||||
<orderEntry type="library" name="unittest-libs" level="project" />
|
||||
<orderEntry type="library" name="kotlinx-cli-jvm-0.1.0-dev-5" level="project" />
|
||||
<orderEntry type="library" name="antlr-runtime-4.8" level="project" />
|
||||
<orderEntry type="library" name="kotlinx-cli-jvm" level="project" />
|
||||
<orderEntry type="library" name="antlr-runtime-4.9" level="project" />
|
||||
</component>
|
||||
</module>
|
Binary file not shown.
Binary file not shown.
BIN
compiler/lib/kotlinx-cli-jvm-0.3.1.jar
Normal file
BIN
compiler/lib/kotlinx-cli-jvm-0.3.1.jar
Normal file
Binary file not shown.
20
compiler/res/.editorconfig
Normal file
20
compiler/res/.editorconfig
Normal file
@ -0,0 +1,20 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
max_line_length = 120
|
||||
tab_width = 8
|
||||
trim_trailing_whitespace = true
|
||||
ij_smart_tabs = true
|
||||
|
||||
[*.p8]
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
|
||||
[*.asm]
|
||||
indent_size = 8
|
||||
indent_style = tab
|
@ -2,6 +2,8 @@
|
||||
|
||||
FL_ONE_const .byte 129 ; 1.0
|
||||
FL_ZERO_const .byte 0,0,0,0,0 ; 0.0
|
||||
FL_LOG2_const .byte $80, $31, $72, $17, $f8 ; log(2)
|
||||
|
||||
|
||||
floats_store_reg .byte 0 ; temp storage
|
||||
|
||||
@ -55,13 +57,76 @@ w2float .proc
|
||||
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_as_result
|
||||
jmp push_fac1._internal
|
||||
.pend
|
||||
|
||||
stack_w2float .proc
|
||||
@ -71,7 +136,7 @@ stack_w2float .proc
|
||||
lda P8ESTACK_HI,x
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr GIVAYF
|
||||
jmp push_fac1_as_result
|
||||
jmp push_fac1._internal
|
||||
.pend
|
||||
|
||||
stack_ub2float .proc
|
||||
@ -82,7 +147,7 @@ stack_ub2float .proc
|
||||
tay
|
||||
lda #0
|
||||
jsr GIVAYF
|
||||
jmp push_fac1_as_result
|
||||
jmp push_fac1._internal
|
||||
.pend
|
||||
|
||||
stack_uw2float .proc
|
||||
@ -92,7 +157,7 @@ stack_uw2float .proc
|
||||
ldy P8ESTACK_HI,x
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr GIVUAYFAY
|
||||
jmp push_fac1_as_result
|
||||
jmp push_fac1._internal
|
||||
.pend
|
||||
|
||||
stack_float2w .proc ; also used for float2b
|
||||
@ -146,22 +211,6 @@ push_float .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_rndf .proc
|
||||
; -- put a random floating point value on the stack
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #1
|
||||
jsr FREADSA
|
||||
jsr RND ; rng into fac1
|
||||
ldx #<_rndf_rnum5
|
||||
ldy #>_rndf_rnum5
|
||||
jsr MOVMF ; fac1 to mem X/Y
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
lda #<_rndf_rnum5
|
||||
ldy #>_rndf_rnum5
|
||||
jmp push_float
|
||||
_rndf_rnum5 .byte 0,0,0,0,0
|
||||
.pend
|
||||
|
||||
pop_float .proc
|
||||
; ---- pops mflpt5 from stack to memory A/Y
|
||||
; (frees 3 stack positions = 6 bytes of which 1 is padding)
|
||||
@ -198,16 +247,6 @@ pop_float_fac1 .proc
|
||||
jmp MOVFM
|
||||
.pend
|
||||
|
||||
pop_float_fac2 .proc
|
||||
; -- pops float from stack into FAC2
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr pop_float
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jmp CONUPK
|
||||
.pend
|
||||
|
||||
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.
|
||||
@ -273,9 +312,11 @@ pop_2_floats_f2_in_fac1 .proc
|
||||
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
|
||||
|
||||
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
|
||||
@ -284,6 +325,7 @@ push_fac1_as_result .proc
|
||||
jmp push_float
|
||||
.pend
|
||||
|
||||
|
||||
pow_f .proc
|
||||
; -- push f1 ** f2 on stack
|
||||
lda #<fmath_float2
|
||||
@ -299,8 +341,7 @@ pow_f .proc
|
||||
lda #<fmath_float2
|
||||
ldy #>fmath_float2
|
||||
jsr FPWR
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
jmp push_fac1_as_result
|
||||
jmp push_fac1._internal
|
||||
.pend
|
||||
|
||||
div_f .proc
|
||||
@ -310,7 +351,7 @@ div_f .proc
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr FDIV
|
||||
jmp push_fac1_as_result
|
||||
jmp push_fac1._internal
|
||||
.pend
|
||||
|
||||
add_f .proc
|
||||
@ -320,7 +361,7 @@ add_f .proc
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr FADD
|
||||
jmp push_fac1_as_result
|
||||
jmp push_fac1._internal
|
||||
.pend
|
||||
|
||||
sub_f .proc
|
||||
@ -330,7 +371,7 @@ sub_f .proc
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr FSUB
|
||||
jmp push_fac1_as_result
|
||||
jmp push_fac1._internal
|
||||
.pend
|
||||
|
||||
mul_f .proc
|
||||
@ -340,7 +381,7 @@ mul_f .proc
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr FMULT
|
||||
jmp push_fac1_as_result
|
||||
jmp push_fac1._internal
|
||||
.pend
|
||||
|
||||
neg_f .proc
|
||||
@ -351,11 +392,96 @@ neg_f .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
abs_f .proc
|
||||
; -- strip the sign bit on the stack
|
||||
lda P8ESTACK_HI+3,x
|
||||
and #$7f
|
||||
sta P8ESTACK_HI+3,x
|
||||
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
|
||||
|
||||
@ -396,6 +522,40 @@ notequal_f .proc
|
||||
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
|
||||
@ -457,240 +617,22 @@ _return_true lda #1
|
||||
bne _return_result
|
||||
.pend
|
||||
|
||||
func_sin .proc
|
||||
; -- push sin(f) back onto stack
|
||||
jsr pop_float_fac1
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr SIN
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
func_cos .proc
|
||||
; -- push cos(f) back onto stack
|
||||
jsr pop_float_fac1
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr COS
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
func_tan .proc
|
||||
; -- push tan(f) back onto stack
|
||||
jsr pop_float_fac1
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr TAN
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
func_atan .proc
|
||||
; -- push atan(f) back onto stack
|
||||
jsr pop_float_fac1
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr ATN
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
func_ln .proc
|
||||
; -- push ln(f) back onto stack
|
||||
jsr pop_float_fac1
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr LOG
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
func_log2 .proc
|
||||
; -- push log base 2, ln(f)/ln(2), back onto stack
|
||||
jsr pop_float_fac1
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr LOG
|
||||
jsr MOVEF
|
||||
lda #<c64.FL_LOG2
|
||||
ldy #>c64.FL_LOG2
|
||||
jsr MOVFM
|
||||
jsr FDIVT
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
func_sqrt .proc
|
||||
jsr pop_float_fac1
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr SQR
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
func_rad .proc
|
||||
; -- convert degrees to radians (d * pi / 180)
|
||||
jsr pop_float_fac1
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #<_pi_div_180
|
||||
ldy #>_pi_div_180
|
||||
jsr FMULT
|
||||
jmp push_fac1_as_result
|
||||
_pi_div_180 .byte 123, 14, 250, 53, 18 ; pi / 180
|
||||
.pend
|
||||
|
||||
func_deg .proc
|
||||
; -- convert radians to degrees (d * (1/ pi * 180))
|
||||
jsr pop_float_fac1
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #<_one_over_pi_div_180
|
||||
ldy #>_one_over_pi_div_180
|
||||
jsr FMULT
|
||||
jmp push_fac1_as_result
|
||||
_one_over_pi_div_180 .byte 134, 101, 46, 224, 211 ; 1 / (pi * 180)
|
||||
.pend
|
||||
|
||||
func_round .proc
|
||||
jsr pop_float_fac1
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr FADDH
|
||||
jsr INT
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
func_floor .proc
|
||||
jsr pop_float_fac1
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr INT
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
func_ceil .proc
|
||||
; -- ceil: tr = int(f); if tr==f -> return else return tr+1
|
||||
jsr pop_float_fac1
|
||||
stx P8ZP_SCRATCH_REG
|
||||
ldx #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr MOVMF
|
||||
jsr INT
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr FCOMP
|
||||
cmp #0
|
||||
beq +
|
||||
lda #<FL_ONE_const
|
||||
ldy #>FL_ONE_const
|
||||
jsr FADD
|
||||
+ jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
func_any_f .proc
|
||||
inx
|
||||
lda P8ESTACK_LO,x ; array size
|
||||
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 ; times 5 because of float
|
||||
jmp prog8_lib.func_any_b._entry
|
||||
.pend
|
||||
|
||||
func_all_f .proc
|
||||
inx
|
||||
jsr prog8_lib.peek_address
|
||||
lda P8ESTACK_LO,x ; array size
|
||||
sta P8ZP_SCRATCH_B1
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc P8ZP_SCRATCH_B1 ; times 5 because of float
|
||||
tay
|
||||
dey
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
clc
|
||||
dey
|
||||
adc (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
adc (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
adc (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
adc (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
cmp #0
|
||||
beq +
|
||||
cpy #255
|
||||
bne -
|
||||
lda #1
|
||||
sta P8ESTACK_LO+1,x
|
||||
rts
|
||||
+ sta P8ESTACK_LO+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_max_f .proc
|
||||
lda #255
|
||||
sta _minmax_cmp+1
|
||||
lda #<_largest_neg_float
|
||||
ldy #>_largest_neg_float
|
||||
_minmax_entry jsr MOVFM
|
||||
jsr prog8_lib.pop_array_and_lengthmin1Y
|
||||
stx floats_store_reg
|
||||
- sty P8ZP_SCRATCH_REG
|
||||
lda P8ZP_SCRATCH_W1
|
||||
adc P8ZP_SCRATCH_B1
|
||||
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
|
||||
adc P8ZP_SCRATCH_W1
|
||||
bcc +
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
+ ldy P8ZP_SCRATCH_REG
|
||||
dey
|
||||
cpy #255
|
||||
bne -
|
||||
iny
|
||||
+ stx floats_store_reg
|
||||
tax
|
||||
jsr MOVMF
|
||||
ldx floats_store_reg
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jmp push_fac1_as_result
|
||||
_largest_neg_float .byte 255,255,255,255,255 ; largest negative float -1.7014118345e+38
|
||||
.pend
|
||||
|
||||
func_min_f .proc
|
||||
lda #1
|
||||
sta func_max_f._minmax_cmp+1
|
||||
lda #<_largest_pos_float
|
||||
ldy #>_largest_pos_float
|
||||
jmp func_max_f._minmax_entry
|
||||
_largest_pos_float .byte 255,127,255,255,255 ; largest positive float
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sum_f .proc
|
||||
lda #<FL_ZERO_const
|
||||
ldy #>FL_ZERO_const
|
||||
jsr MOVFM
|
||||
jsr prog8_lib.pop_array_and_lengthmin1Y
|
||||
stx floats_store_reg
|
||||
- sty P8ZP_SCRATCH_REG
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jsr FADD
|
||||
ldy P8ZP_SCRATCH_REG
|
||||
dey
|
||||
cpy #255
|
||||
beq +
|
||||
lda P8ZP_SCRATCH_W1
|
||||
clc
|
||||
adc #5
|
||||
sta P8ZP_SCRATCH_W1
|
||||
bcc -
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
bne -
|
||||
+ ldx floats_store_reg
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
sign_f .proc
|
||||
jsr pop_float_fac1
|
||||
jsr SIGN
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
@ -734,16 +676,3 @@ set_array_float .proc
|
||||
.pend
|
||||
|
||||
|
||||
swap_floats .proc
|
||||
; -- swap floats pointed to by SCRATCH_ZPWORD1, SCRATCH_ZPWORD2
|
||||
ldy #4
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
pha
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
pla
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
dey
|
||||
bpl -
|
||||
rts
|
||||
.pend
|
||||
|
@ -19,23 +19,6 @@ floats {
|
||||
; note: the fac1 and fac2 are working registers and take 6 bytes each,
|
||||
; floats in memory (and rom) are stored in 5-byte MFLPT packed format.
|
||||
|
||||
; constants in five-byte "mflpt" format in the BASIC ROM
|
||||
&float FL_PIVAL = $aea8 ; 3.1415926...
|
||||
&float FL_N32768 = $b1a5 ; -32768
|
||||
&float FL_FONE = $b9bc ; 1
|
||||
&float FL_SQRHLF = $b9d6 ; SQR(2) / 2
|
||||
&float FL_SQRTWO = $b9db ; SQR(2)
|
||||
&float FL_NEGHLF = $b9e0 ; -.5
|
||||
&float FL_LOG2 = $b9e5 ; LOG(2)
|
||||
&float FL_TENC = $baf9 ; 10
|
||||
&float FL_NZMIL = $bdbd ; 1e9 (1 billion)
|
||||
&float FL_FHALF = $bf11 ; .5
|
||||
&float FL_LOGEB2 = $bfbf ; 1 / LOG(2)
|
||||
&float FL_PIHALF = $e2e0 ; PI / 2
|
||||
&float FL_TWOPI = $e2e5 ; 2 * PI
|
||||
&float FL_FR4 = $e2ea ; .25
|
||||
; oddly enough, 0.0 isn't available in the kernel.
|
||||
|
||||
|
||||
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
|
||||
; note: for subtraction and division, the left operand is in fac2, the right operand in fac1.
|
||||
@ -213,5 +196,6 @@ sub print_f (float value) {
|
||||
}
|
||||
|
||||
%asminclude "library:c64/floats.asm", ""
|
||||
%asminclude "library:c64/floats_funcs.asm", ""
|
||||
|
||||
}
|
||||
|
437
compiler/res/prog8lib/c64/floats_funcs.asm
Normal file
437
compiler/res/prog8lib/c64/floats_funcs.asm
Normal file
@ -0,0 +1,437 @@
|
||||
; --- floating point builtin functions
|
||||
|
||||
|
||||
abs_f_stack .proc
|
||||
; -- push abs(AY) on stack
|
||||
jsr floats.MOVFM
|
||||
jsr floats.ABS
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
abs_f_fac1 .proc
|
||||
; -- FAC1 = abs(AY)
|
||||
jsr floats.MOVFM
|
||||
jmp floats.ABS
|
||||
.pend
|
||||
|
||||
func_atan_stack .proc
|
||||
jsr func_atan_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_atan_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr ATN
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_ceil_stack .proc
|
||||
jsr func_ceil_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_ceil_fac1 .proc
|
||||
; -- ceil: tr = int(f); if tr==f -> return else return tr+1
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
ldx #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr MOVMF
|
||||
jsr INT
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr FCOMP
|
||||
cmp #0
|
||||
beq +
|
||||
lda #<FL_ONE_const
|
||||
ldy #>FL_ONE_const
|
||||
jsr FADD
|
||||
+ ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_floor_stack .proc
|
||||
jsr func_floor_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_floor_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr INT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_round_stack .proc
|
||||
jsr func_round_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_round_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr FADDH
|
||||
jsr INT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sin_stack .proc
|
||||
jsr func_sin_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_sin_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr SIN
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cos_stack .proc
|
||||
jsr func_cos_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_cos_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr COS
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_tan_stack .proc
|
||||
jsr func_tan_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_tan_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr TAN
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_rad_stack .proc
|
||||
jsr func_rad_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_rad_fac1 .proc
|
||||
; -- convert degrees to radians (d * pi / 180)
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #<_pi_div_180
|
||||
ldy #>_pi_div_180
|
||||
jsr FMULT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
_pi_div_180 .byte 123, 14, 250, 53, 18 ; pi / 180
|
||||
.pend
|
||||
|
||||
func_deg_stack .proc
|
||||
jsr func_deg_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_deg_fac1 .proc
|
||||
; -- convert radians to degrees (d * (1/ pi * 180))
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #<_one_over_pi_div_180
|
||||
ldy #>_one_over_pi_div_180
|
||||
jsr FMULT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
_one_over_pi_div_180 .byte 134, 101, 46, 224, 211 ; 1 / (pi * 180)
|
||||
.pend
|
||||
|
||||
func_ln_stack .proc
|
||||
jsr func_ln_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_ln_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr LOG
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_log2_stack .proc
|
||||
jsr func_log2_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_log2_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr LOG
|
||||
jsr MOVEF
|
||||
lda #<FL_LOG2_const
|
||||
ldy #>FL_LOG2_const
|
||||
jsr MOVFM
|
||||
jsr FDIVT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sign_f_stack .proc
|
||||
jsr func_sign_f_into_A
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sign_f_into_A .proc
|
||||
jsr MOVFM
|
||||
jmp SIGN
|
||||
.pend
|
||||
|
||||
func_sqrt_stack .proc
|
||||
jsr func_sqrt_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_sqrt_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr SQR
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_rndf_stack .proc
|
||||
jsr func_rndf_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_rndf_fac1 .proc
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #1
|
||||
jsr FREADSA
|
||||
jsr RND ; rng into fac1
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_swap_f .proc
|
||||
; -- swap floats pointed to by SCRATCH_ZPWORD1, SCRATCH_ZPWORD2
|
||||
ldy #4
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
pha
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
pla
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
dey
|
||||
bpl -
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_reverse_f .proc
|
||||
; --- reverse an array of floats (array in P8ZP_SCRATCH_W1, num elements in A)
|
||||
_left_index = P8ZP_SCRATCH_W2
|
||||
_right_index = P8ZP_SCRATCH_W2+1
|
||||
_loop_count = P8ZP_SCRATCH_REG
|
||||
pha
|
||||
jsr a_times_5
|
||||
sec
|
||||
sbc #5
|
||||
sta _right_index
|
||||
lda #0
|
||||
sta _left_index
|
||||
pla
|
||||
lsr a
|
||||
sta _loop_count
|
||||
_loop ; push the left indexed float on the stack
|
||||
ldy _left_index
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
pha
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
pha
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
pha
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
pha
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
pha
|
||||
; copy right index float to left index float
|
||||
ldy _right_index
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
ldy _left_index
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
inc _left_index
|
||||
inc _right_index
|
||||
ldy _right_index
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
ldy _left_index
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
inc _left_index
|
||||
inc _right_index
|
||||
ldy _right_index
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
ldy _left_index
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
inc _left_index
|
||||
inc _right_index
|
||||
ldy _right_index
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
ldy _left_index
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
inc _left_index
|
||||
inc _right_index
|
||||
ldy _right_index
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
ldy _left_index
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
; pop the float off the stack into the right index float
|
||||
ldy _right_index
|
||||
pla
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
pla
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
pla
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
pla
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
pla
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
inc _left_index
|
||||
lda _right_index
|
||||
sec
|
||||
sbc #9
|
||||
sta _right_index
|
||||
dec _loop_count
|
||||
bne _loop
|
||||
rts
|
||||
|
||||
.pend
|
||||
|
||||
|
||||
|
||||
a_times_5 .proc
|
||||
sta P8ZP_SCRATCH_B1
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc P8ZP_SCRATCH_B1
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_any_f_into_A .proc
|
||||
jsr a_times_5
|
||||
jmp prog8_lib.func_any_b_into_A
|
||||
.pend
|
||||
|
||||
func_all_f_into_A .proc
|
||||
jsr a_times_5
|
||||
jmp prog8_lib.func_all_b_into_A
|
||||
.pend
|
||||
|
||||
func_any_f_stack .proc
|
||||
jsr a_times_5
|
||||
jmp prog8_lib.func_any_b_stack
|
||||
.pend
|
||||
|
||||
func_all_f_stack .proc
|
||||
jsr a_times_5
|
||||
jmp prog8_lib.func_all_b_stack
|
||||
.pend
|
||||
|
||||
func_max_f_stack .proc
|
||||
jsr func_max_f_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_max_f_fac1 .proc
|
||||
; -- max(array) -> fac1, array in P8ZP_SCRATCH_W1, num elts in A
|
||||
_loop_count = P8ZP_SCRATCH_REG
|
||||
stx floats_store_reg
|
||||
sta _loop_count
|
||||
lda #255
|
||||
sta _minmax_cmp+1 ; modifying
|
||||
lda #<_largest_neg_float
|
||||
ldy #>_largest_neg_float
|
||||
_minmax_entry jsr MOVFM
|
||||
- lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jsr FCOMP
|
||||
_minmax_cmp cmp #255 ; modified
|
||||
bne +
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jsr MOVFM
|
||||
+ lda P8ZP_SCRATCH_W1
|
||||
clc
|
||||
adc #5
|
||||
sta P8ZP_SCRATCH_W1
|
||||
bcc +
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
+ dec _loop_count
|
||||
bne -
|
||||
ldx floats_store_reg
|
||||
rts
|
||||
_largest_neg_float .byte 255,255,255,255,255 ; largest negative float -1.7014118345e+38
|
||||
.pend
|
||||
|
||||
func_min_f_stack .proc
|
||||
jsr func_min_f_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_min_f_fac1 .proc
|
||||
; -- min(array) -> fac1, array in P8ZP_SCRATCH_W1, num elts in A
|
||||
sta func_max_f_fac1._loop_count
|
||||
lda #1
|
||||
sta func_max_f_fac1._minmax_cmp+1
|
||||
lda #<_largest_pos_float
|
||||
ldy #>_largest_pos_float
|
||||
jmp func_max_f_fac1._minmax_entry
|
||||
_largest_pos_float .byte 255,127,255,255,255 ; largest positive float
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
func_sum_f_stack .proc
|
||||
jsr func_sum_f_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_sum_f_fac1 .proc
|
||||
; -- sum(array) -> fac1, array in P8ZP_SCRATCH_W1, num elts in A
|
||||
_loop_count = P8ZP_SCRATCH_REG
|
||||
stx floats_store_reg
|
||||
sta _loop_count
|
||||
lda #<FL_ZERO_const
|
||||
ldy #>FL_ZERO_const
|
||||
jsr MOVFM
|
||||
- lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jsr FADD
|
||||
lda P8ZP_SCRATCH_W1
|
||||
clc
|
||||
adc #5
|
||||
sta P8ZP_SCRATCH_W1
|
||||
bcc +
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
+ dec _loop_count
|
||||
bne -
|
||||
ldx floats_store_reg
|
||||
rts
|
||||
.pend
|
@ -12,29 +12,51 @@ graphics {
|
||||
|
||||
sub enable_bitmap_mode() {
|
||||
; enable bitmap screen, erase it and set colors to black/white.
|
||||
c64.SCROLY |= %00100000
|
||||
c64.SCROLY = %00111011
|
||||
c64.SCROLX = %00001000
|
||||
c64.VMCSB = (c64.VMCSB & %11110000) | %00001000 ; $2000-$3fff
|
||||
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)
|
||||
sys.memset(BITMAP_ADDRESS, 320*200/8, 0)
|
||||
txt.fill_screen(pixelcolor << 4 | bgcolor, 0)
|
||||
}
|
||||
|
||||
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
|
||||
; Bresenham algorithm.
|
||||
; This code special cases various quadrant loops to allow simple ++ and -- operations.
|
||||
; TODO rewrite this in optimized assembly
|
||||
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
|
||||
if y1>y2 {
|
||||
; make sure dy is always positive to avoid 8 instead of just 4 special cases
|
||||
; make sure dy is always positive to have only 4 instead of 8 special cases
|
||||
swap(x1, x2)
|
||||
swap(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 {
|
||||
vertical_line(x1, y1, abs(dy)+1 as ubyte)
|
||||
return
|
||||
}
|
||||
if dy==0 {
|
||||
if x1>x2
|
||||
x1=x2
|
||||
horizontal_line(x1, y1, abs(dx)+1 as uword)
|
||||
return
|
||||
}
|
||||
|
||||
; TODO rewrite the rest in optimized assembly
|
||||
|
||||
word @zp d = 0
|
||||
ubyte positive_ix = true
|
||||
if dx < 0 {
|
||||
dx = -dx
|
||||
positive_ix = false
|
||||
@ -99,78 +121,173 @@ graphics {
|
||||
}
|
||||
}
|
||||
|
||||
sub rect(uword x, ubyte y, uword width, ubyte height) {
|
||||
if width==0 or height==0
|
||||
return
|
||||
horizontal_line(x, y, width)
|
||||
if height==1
|
||||
return
|
||||
horizontal_line(x, y+height-1, width)
|
||||
vertical_line(x, y+1, height-2)
|
||||
if width==1
|
||||
return
|
||||
vertical_line(x+width-1, y+1, height-2)
|
||||
}
|
||||
|
||||
sub fillrect(uword x, ubyte y, uword width, ubyte height) {
|
||||
if width==0
|
||||
return
|
||||
repeat height {
|
||||
horizontal_line(x, y, width)
|
||||
y++
|
||||
}
|
||||
}
|
||||
|
||||
sub horizontal_line(uword x, ubyte y, uword length) {
|
||||
if length<8 {
|
||||
internal_plotx=x
|
||||
repeat lsb(length) {
|
||||
internal_plot(y)
|
||||
internal_plotx++
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ubyte separate_pixels = lsb(x) & 7
|
||||
uword addr = get_y_lookup(y) + (x&$fff8)
|
||||
|
||||
if separate_pixels {
|
||||
%asm {{
|
||||
lda addr
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda addr+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
ldy separate_pixels
|
||||
lda _filled_right,y
|
||||
eor #255
|
||||
ldy #0
|
||||
ora (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
}}
|
||||
addr += 8
|
||||
length += separate_pixels
|
||||
length -= 8
|
||||
}
|
||||
|
||||
if length {
|
||||
%asm {{
|
||||
lda length
|
||||
and #7
|
||||
sta separate_pixels
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lsr length+1
|
||||
ror length
|
||||
lsr length+1
|
||||
ror length
|
||||
lsr length+1
|
||||
ror length
|
||||
lda addr
|
||||
sta _modified+1
|
||||
lda addr+1
|
||||
sta _modified+2
|
||||
lda length
|
||||
ora length+1
|
||||
beq _zero
|
||||
ldy length
|
||||
ldx #$ff
|
||||
_modified stx $ffff ; modified
|
||||
lda _modified+1
|
||||
clc
|
||||
adc #8
|
||||
sta _modified+1
|
||||
bcc +
|
||||
inc _modified+2
|
||||
+ dey
|
||||
bne _modified
|
||||
_zero ldx P8ZP_SCRATCH_REG
|
||||
|
||||
ldy separate_pixels
|
||||
beq _zero2
|
||||
lda _modified+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda _modified+2
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda _filled_right,y
|
||||
ldy #0
|
||||
ora (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
jmp _zero2
|
||||
_filled_right .byte 0, %10000000, %11000000, %11100000, %11110000, %11111000, %11111100, %11111110
|
||||
_zero2
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
sub vertical_line(uword x, ubyte y, ubyte height) {
|
||||
internal_plotx = x
|
||||
repeat height {
|
||||
internal_plot(y)
|
||||
y++
|
||||
}
|
||||
}
|
||||
|
||||
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||
; Midpoint algorithm
|
||||
if radius==0
|
||||
return
|
||||
ubyte @zp ploty
|
||||
ubyte @zp xx = radius
|
||||
ubyte @zp yy = 0
|
||||
byte @zp decisionOver2 = 1-xx as byte
|
||||
word @zp decisionOver2 = (1 as word)-radius
|
||||
|
||||
while xx>=yy {
|
||||
internal_plotx = xcenter + xx
|
||||
while radius>=yy {
|
||||
internal_plotx = xcenter + radius
|
||||
ploty = ycenter + yy
|
||||
internal_plot(ploty)
|
||||
internal_plotx = xcenter - xx
|
||||
internal_plotx = xcenter - radius
|
||||
internal_plot(ploty)
|
||||
internal_plotx = xcenter + xx
|
||||
internal_plotx = xcenter + radius
|
||||
ploty = ycenter - yy
|
||||
internal_plot(ploty)
|
||||
internal_plotx = xcenter - xx
|
||||
internal_plotx = xcenter - radius
|
||||
internal_plot(ploty)
|
||||
internal_plotx = xcenter + yy
|
||||
ploty = ycenter + xx
|
||||
ploty = ycenter + radius
|
||||
internal_plot(ploty)
|
||||
internal_plotx = xcenter - yy
|
||||
internal_plot(ploty)
|
||||
internal_plotx = xcenter + yy
|
||||
ploty = ycenter - xx
|
||||
ploty = ycenter - radius
|
||||
internal_plot(ploty)
|
||||
internal_plotx = xcenter - yy
|
||||
internal_plot(ploty)
|
||||
yy++
|
||||
if decisionOver2<=0
|
||||
decisionOver2 += 2*yy+1
|
||||
decisionOver2 += (yy as word)*2+1
|
||||
else {
|
||||
xx--
|
||||
decisionOver2 += 2*(yy-xx)+1
|
||||
radius--
|
||||
decisionOver2 += (yy as word -radius)*2+1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||
; Midpoint algorithm, filled
|
||||
ubyte xx = radius
|
||||
ubyte yy = 0
|
||||
byte decisionOver2 = 1-xx as byte
|
||||
if radius==0
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word decisionOver2 = (1 as word)-radius
|
||||
|
||||
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)
|
||||
}
|
||||
while radius>=yy {
|
||||
horizontal_line(xcenter-radius, ycenter+yy, radius*2+1)
|
||||
horizontal_line(xcenter-radius, ycenter-yy, radius*2+1)
|
||||
horizontal_line(xcenter-yy, ycenter+radius, yy*2+1)
|
||||
horizontal_line(xcenter-yy, ycenter-radius, yy*2+1)
|
||||
yy++
|
||||
if decisionOver2<=0
|
||||
decisionOver2 += 2*yy+1
|
||||
decisionOver2 += (yy as word)*2+1
|
||||
else {
|
||||
xx--
|
||||
decisionOver2 += 2*(yy-xx)+1
|
||||
radius--
|
||||
decisionOver2 += (yy as word -radius)*2+1
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -183,11 +300,11 @@ graphics {
|
||||
; @(addr) |= ormask[lsb(px) & 7]
|
||||
; }
|
||||
|
||||
asmsub plot(uword plotx @XY, ubyte ploty @A) clobbers (A, X, Y) {
|
||||
inline asmsub plot(uword plotx @XY, ubyte ploty @A) clobbers (A, X, Y) {
|
||||
%asm {{
|
||||
stx internal_plotx
|
||||
sty internal_plotx+1
|
||||
jmp internal_plot
|
||||
stx graphics.internal_plotx
|
||||
sty graphics.internal_plotx+1
|
||||
jsr graphics.internal_plot
|
||||
}}
|
||||
}
|
||||
|
||||
@ -239,6 +356,17 @@ _y_lookup_hi .byte >_plot_y_values
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub get_y_lookup(ubyte y @Y) -> uword @AY {
|
||||
%asm {{
|
||||
lda internal_plot._y_lookup_lo,y
|
||||
pha
|
||||
lda internal_plot._y_lookup_hi,y
|
||||
tay
|
||||
pla
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -225,6 +225,41 @@ romsub $FFF3 = IOBASE() -> uword @ XY ; read base addr
|
||||
|
||||
; ---- end of C64 ROM kernal routines ----
|
||||
|
||||
; ---- utilities -----
|
||||
|
||||
asmsub STOP2() -> ubyte @A {
|
||||
; -- check if STOP key was pressed, returns true if so. More convenient to use than STOP() because that only sets the carry status flag.
|
||||
%asm {{
|
||||
txa
|
||||
pha
|
||||
jsr c64.STOP
|
||||
beq +
|
||||
pla
|
||||
tax
|
||||
lda #0
|
||||
rts
|
||||
+ pla
|
||||
tax
|
||||
lda #1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub RDTIM16() -> uword @AY {
|
||||
; -- like RDTIM() but only returning the lower 16 bits for convenience
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr c64.RDTIM
|
||||
pha
|
||||
txa
|
||||
tay
|
||||
pla
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
|
||||
; ---- C64 specific system utility routines: ----
|
||||
|
||||
@ -259,17 +294,7 @@ asmsub init_system() {
|
||||
}}
|
||||
}
|
||||
|
||||
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() {
|
||||
asmsub disable_runstop_and_charsetswitch() clobbers(A) {
|
||||
%asm {{
|
||||
lda #$80
|
||||
sta 657 ; disable charset switching
|
||||
@ -363,7 +388,7 @@ IRQ_SCRATCH_ZPWORD2 .word 0
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub restore_irqvec() {
|
||||
asmsub restore_irqvec() clobbers(A) {
|
||||
%asm {{
|
||||
sei
|
||||
lda #<c64.IRQDFRT
|
||||
@ -444,5 +469,214 @@ _raster_irq_handler
|
||||
|
||||
; ---- end of C64 specific system utility routines ----
|
||||
|
||||
}
|
||||
|
||||
sys {
|
||||
; ------- lowlevel system routines --------
|
||||
|
||||
const ubyte target = 64 ; compilation target specifier. 64 = C64, 16 = CommanderX16.
|
||||
|
||||
|
||||
asmsub reset_system() {
|
||||
; Soft-reset the system back to Basic prompt.
|
||||
%asm {{
|
||||
sei
|
||||
lda #14
|
||||
sta $01 ; bank the kernal in
|
||||
jmp (c64.RESET_VEC)
|
||||
}}
|
||||
}
|
||||
|
||||
sub wait(uword jiffies) {
|
||||
; --- wait approximately the given number of jiffies (1/60th seconds)
|
||||
repeat jiffies {
|
||||
ubyte jiff = lsb(c64.RDTIM16())
|
||||
while jiff==lsb(c64.RDTIM16()) {
|
||||
; wait until 1 jiffy has passed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
|
||||
%asm {{
|
||||
ldx cx16.r0
|
||||
stx P8ZP_SCRATCH_W1 ; source in ZP
|
||||
ldx cx16.r0+1
|
||||
stx P8ZP_SCRATCH_W1+1
|
||||
ldx cx16.r1
|
||||
stx P8ZP_SCRATCH_W2 ; target in ZP
|
||||
ldx cx16.r1+1
|
||||
stx P8ZP_SCRATCH_W2+1
|
||||
cpy #0
|
||||
bne _longcopy
|
||||
|
||||
; copy <= 255 bytes
|
||||
tay
|
||||
bne _copyshort
|
||||
rts ; nothing to copy
|
||||
|
||||
_copyshort
|
||||
; decrease source and target pointers so we can simply index by Y
|
||||
lda P8ZP_SCRATCH_W1
|
||||
bne +
|
||||
dec P8ZP_SCRATCH_W1+1
|
||||
+ dec P8ZP_SCRATCH_W1
|
||||
lda P8ZP_SCRATCH_W2
|
||||
bne +
|
||||
dec P8ZP_SCRATCH_W2+1
|
||||
+ dec P8ZP_SCRATCH_W2
|
||||
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
dey
|
||||
bne -
|
||||
rts
|
||||
|
||||
_longcopy
|
||||
sta P8ZP_SCRATCH_B1 ; lsb(count) = remainder in last page
|
||||
tya
|
||||
tax ; x = num pages (1+)
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
bne -
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
inc P8ZP_SCRATCH_W2+1
|
||||
dex
|
||||
bne -
|
||||
ldy P8ZP_SCRATCH_B1
|
||||
bne _copyshort
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memset(uword mem @R0, uword numbytes @R1, ubyte value @A) clobbers(A,X,Y) {
|
||||
%asm {{
|
||||
ldy cx16.r0
|
||||
sty P8ZP_SCRATCH_W1
|
||||
ldy cx16.r0+1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldx cx16.r1
|
||||
ldy cx16.r1+1
|
||||
jmp prog8_lib.memset
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memsetw(uword mem @R0, uword numwords @R1, uword value @AY) clobbers(A,X,Y) {
|
||||
%asm {{
|
||||
ldx cx16.r0
|
||||
stx P8ZP_SCRATCH_W1
|
||||
ldx cx16.r0+1
|
||||
stx P8ZP_SCRATCH_W1+1
|
||||
ldx cx16.r1
|
||||
stx P8ZP_SCRATCH_W2
|
||||
ldx cx16.r1+1
|
||||
stx P8ZP_SCRATCH_W2+1
|
||||
jmp prog8_lib.memsetw
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
inline asmsub rsave() {
|
||||
; save cpu status flag and all registers A, X, Y.
|
||||
; see http://6502.org/tutorials/register_preservation.html
|
||||
%asm {{
|
||||
php
|
||||
sta P8ZP_SCRATCH_REG
|
||||
pha
|
||||
txa
|
||||
pha
|
||||
tya
|
||||
pha
|
||||
lda P8ZP_SCRATCH_REG
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub rrestore() {
|
||||
; restore all registers and cpu status flag
|
||||
%asm {{
|
||||
pla
|
||||
tay
|
||||
pla
|
||||
tax
|
||||
pla
|
||||
plp
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub read_flags() -> ubyte @A {
|
||||
%asm {{
|
||||
php
|
||||
pla
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub clear_carry() {
|
||||
%asm {{
|
||||
clc
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub set_carry() {
|
||||
%asm {{
|
||||
sec
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub clear_irqd() {
|
||||
%asm {{
|
||||
cli
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub set_irqd() {
|
||||
%asm {{
|
||||
sei
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub exit(ubyte returnvalue @A) {
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
%asm {{
|
||||
jsr c64.CLRCHN ; reset i/o channels
|
||||
ldx prog8_lib.orig_stackpointer
|
||||
txs
|
||||
rts ; return to original caller
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub progend() -> uword @AY {
|
||||
%asm {{
|
||||
lda #<prog8_program_end
|
||||
ldy #>prog8_program_end
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
cx16 {
|
||||
|
||||
; the sixteen virtual 16-bit registers that the CX16 has defined in the zeropage
|
||||
; they are simulated on the C64 as well but their location in memory is different
|
||||
; (because there's no room for them in the zeropage)
|
||||
; they are allocated at the bottom of the eval-stack (should be ample space unless
|
||||
; you're doing insane nesting of expressions...)
|
||||
&uword r0 = $cf00
|
||||
&uword r1 = $cf02
|
||||
&uword r2 = $cf04
|
||||
&uword r3 = $cf06
|
||||
&uword r4 = $cf08
|
||||
&uword r5 = $cf0a
|
||||
&uword r6 = $cf0c
|
||||
&uword r7 = $cf0e
|
||||
&uword r8 = $cf10
|
||||
&uword r9 = $cf12
|
||||
&uword r10 = $cf14
|
||||
&uword r11 = $cf16
|
||||
&uword r12 = $cf18
|
||||
&uword r13 = $cf1a
|
||||
&uword r14 = $cf1c
|
||||
&uword r15 = $cf1e
|
||||
|
||||
}
|
||||
|
@ -16,9 +16,22 @@ const ubyte DEFAULT_HEIGHT = 25
|
||||
|
||||
|
||||
sub clear_screen() {
|
||||
clear_screenchars(' ')
|
||||
txt.chrout(147)
|
||||
}
|
||||
|
||||
sub home() {
|
||||
txt.chrout(19)
|
||||
}
|
||||
|
||||
sub nl() {
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
sub spc() {
|
||||
txt.chrout(' ')
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
@ -38,13 +51,13 @@ 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
|
||||
ldy #250
|
||||
- sta c64.Screen+250*0-1,y
|
||||
sta c64.Screen+250*1-1,y
|
||||
sta c64.Screen+250*2-1,y
|
||||
sta c64.Screen+250*3-1,y
|
||||
dey
|
||||
bne -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
@ -53,13 +66,13 @@ 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
|
||||
ldy #250
|
||||
- sta c64.Colors+250*0-1,y
|
||||
sta c64.Colors+250*1-1,y
|
||||
sta c64.Colors+250*2-1,y
|
||||
sta c64.Colors+250*3-1,y
|
||||
dey
|
||||
bne -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
@ -83,29 +96,31 @@ asmsub scroll_left (ubyte alsocolors @ Pc) clobbers(A, Y) {
|
||||
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
bcs +
|
||||
jmp _scroll_screen
|
||||
bcc _scroll_screen
|
||||
|
||||
+ ; scroll the color memory
|
||||
+ ; scroll the screen and the color memory
|
||||
ldx #0
|
||||
ldy #38
|
||||
-
|
||||
.for row=0, row<=24, row+=1
|
||||
lda c64.Colors + 40*row + 1,x
|
||||
sta c64.Colors + 40*row,x
|
||||
.next
|
||||
.for row=0, row<=24, row+=1
|
||||
lda c64.Screen + 40*row + 1,x
|
||||
sta c64.Screen + 40*row + 0,x
|
||||
lda c64.Colors + 40*row + 1,x
|
||||
sta c64.Colors + 40*row + 0,x
|
||||
.next
|
||||
inx
|
||||
dey
|
||||
bpl -
|
||||
rts
|
||||
|
||||
_scroll_screen ; scroll the screen memory
|
||||
_scroll_screen ; scroll only the screen memory
|
||||
ldx #0
|
||||
ldy #38
|
||||
-
|
||||
.for row=0, row<=24, row+=1
|
||||
lda c64.Screen + 40*row + 1,x
|
||||
sta c64.Screen + 40*row,x
|
||||
.next
|
||||
.for row=0, row<=24, row+=1
|
||||
lda c64.Screen + 40*row + 1,x
|
||||
sta c64.Screen + 40*row + 0,x
|
||||
.next
|
||||
inx
|
||||
dey
|
||||
bpl -
|
||||
@ -121,26 +136,28 @@ asmsub scroll_right (ubyte alsocolors @ Pc) clobbers(A) {
|
||||
; Carry flag determines if screen color data must be scrolled too
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
bcs +
|
||||
jmp _scroll_screen
|
||||
bcc _scroll_screen
|
||||
|
||||
+ ; scroll the color memory
|
||||
+ ; scroll the screen and 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
|
||||
.for row=0, row<=24, row+=1
|
||||
lda c64.Screen + 40*row + 0,x
|
||||
sta c64.Screen + 40*row + 1,x
|
||||
lda c64.Colors + 40*row + 0,x
|
||||
sta c64.Colors + 40*row + 1,x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
rts
|
||||
|
||||
_scroll_screen ; scroll the screen memory
|
||||
_scroll_screen ; scroll only the screen memory
|
||||
ldx #38
|
||||
-
|
||||
.for row=0, row<=24, row+=1
|
||||
lda c64.Screen + 40*row + 0,x
|
||||
sta c64.Screen + 40*row + 1,x
|
||||
.next
|
||||
.for row=0, row<=24, row+=1
|
||||
lda c64.Screen + 40*row + 0,x
|
||||
sta c64.Screen + 40*row + 1,x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
|
||||
@ -155,26 +172,28 @@ asmsub scroll_up (ubyte alsocolors @ Pc) clobbers(A) {
|
||||
; Carry flag determines if screen color data must be scrolled too
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
bcs +
|
||||
jmp _scroll_screen
|
||||
bcc _scroll_screen
|
||||
|
||||
+ ; scroll the color memory
|
||||
+ ; scroll the screen and 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
|
||||
.for row=1, row<=24, row+=1
|
||||
lda c64.Screen + 40*row,x
|
||||
sta c64.Screen + 40*(row-1),x
|
||||
lda c64.Colors + 40*row,x
|
||||
sta c64.Colors + 40*(row-1),x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
rts
|
||||
|
||||
_scroll_screen ; scroll the screen memory
|
||||
_scroll_screen ; scroll only the screen memory
|
||||
ldx #39
|
||||
-
|
||||
.for row=1, row<=24, row+=1
|
||||
lda c64.Screen + 40*row,x
|
||||
sta c64.Screen + 40*(row-1),x
|
||||
.next
|
||||
.for row=1, row<=24, row+=1
|
||||
lda c64.Screen + 40*row,x
|
||||
sta c64.Screen + 40*(row-1),x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
|
||||
@ -189,26 +208,28 @@ asmsub scroll_down (ubyte alsocolors @ Pc) clobbers(A) {
|
||||
; Carry flag determines if screen color data must be scrolled too
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
bcs +
|
||||
jmp _scroll_screen
|
||||
bcc _scroll_screen
|
||||
|
||||
+ ; scroll the color memory
|
||||
+ ; scroll the screen and the color memory
|
||||
ldx #39
|
||||
-
|
||||
.for row=23, row>=0, row-=1
|
||||
lda c64.Colors + 40*row,x
|
||||
sta c64.Colors + 40*(row+1),x
|
||||
.next
|
||||
.for row=23, row>=0, row-=1
|
||||
lda c64.Colors + 40*row,x
|
||||
sta c64.Colors + 40*(row+1),x
|
||||
lda c64.Screen + 40*row,x
|
||||
sta c64.Screen + 40*(row+1),x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
rts
|
||||
|
||||
_scroll_screen ; scroll the screen memory
|
||||
_scroll_screen ; scroll only the screen memory
|
||||
ldx #39
|
||||
-
|
||||
.for row=23, row>=0, row-=1
|
||||
lda c64.Screen + 40*row,x
|
||||
sta c64.Screen + 40*(row+1),x
|
||||
.next
|
||||
.for row=23, row>=0, row-=1
|
||||
lda c64.Screen + 40*row,x
|
||||
sta c64.Screen + 40*(row+1),x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
|
||||
|
@ -1,30 +1,28 @@
|
||||
; Prog8 definitions for number conversions routines.
|
||||
; 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
|
||||
; ----- number conversions to decimal strings ----
|
||||
|
||||
asmsub ubyte2decimal (ubyte value @ A) -> ubyte @ Y, ubyte @ A, ubyte @ X {
|
||||
asmsub ubyte2decimal (ubyte value @A) -> ubyte @Y, ubyte @A, ubyte @X {
|
||||
; ---- A to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
|
||||
%asm {{
|
||||
ldy #uword2decimal.ASCII_0_OFFSET
|
||||
bne uword2decimal.hex_try200
|
||||
rts
|
||||
%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
|
||||
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 {{
|
||||
%asm {{
|
||||
|
||||
;Convert 16 bit Hex to Decimal (0-65535) Rev 2
|
||||
;By Omegamatrix Further optimizations by tepples
|
||||
@ -193,11 +191,7 @@ decOnes .byte 0
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
; ----- utility functions ----
|
||||
|
||||
|
||||
asmsub byte2decimal (byte value @ A) -> ubyte @ Y, ubyte @ A, ubyte @ X {
|
||||
asmsub byte2decimal (byte value @A) -> ubyte @Y, ubyte @A, ubyte @X {
|
||||
; ---- A (signed byte) to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
|
||||
; note: if the number is negative, you have to deal with the '-' yourself!
|
||||
%asm {{
|
||||
@ -210,7 +204,7 @@ asmsub byte2decimal (byte value @ A) -> ubyte @ Y, ubyte @ A, ubyte @ X {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub ubyte2hex (ubyte value @ A) -> ubyte @ A, ubyte @ Y {
|
||||
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
|
||||
@ -232,7 +226,7 @@ _hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub uword2hex (uword value @ AY) clobbers(A,Y) {
|
||||
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
|
||||
@ -245,51 +239,95 @@ asmsub uword2hex (uword value @ AY) clobbers(A,Y) {
|
||||
sta output+2
|
||||
sty output+3
|
||||
rts
|
||||
output .text "0000", $00 ; 0-terminated output buffer (to make printing easier)
|
||||
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
|
||||
; ---- string conversion to numbers -----
|
||||
|
||||
asmsub any2uword(str string @AY) clobbers(Y) -> ubyte @A {
|
||||
; -- parses a string into a 16 bit unsigned number. String may be in decimal, hex or binary format.
|
||||
; (the latter two require a $ or % prefix to be recognised)
|
||||
; (any non-digit character will terminate the number string that is parsed)
|
||||
; returns amount of processed characters in A, and the parsed number will be in cx16.r15.
|
||||
; if the string was invalid, 0 will be returned in A.
|
||||
%asm {{
|
||||
pha
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_W1)
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
cmp #'$'
|
||||
beq _hex
|
||||
cmp #'%'
|
||||
beq _bin
|
||||
pla
|
||||
jsr str2uword
|
||||
jmp _result
|
||||
_hex pla
|
||||
jsr hex2uword
|
||||
jmp _result
|
||||
_bin pla
|
||||
jsr bin2uword
|
||||
_result
|
||||
pha
|
||||
lda cx16.r15
|
||||
sta P8ZP_SCRATCH_B1 ; result value
|
||||
pla
|
||||
sta cx16.r15
|
||||
sty cx16.r15+1
|
||||
lda P8ZP_SCRATCH_B1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub str2ubyte(str string @AY) clobbers(Y) -> ubyte @A {
|
||||
; -- returns in A the unsigned byte value of the string number argument in AY
|
||||
; the number may NOT be preceded by a + sign and may NOT contain spaces
|
||||
; (any non-digit character will terminate the number string that is parsed)
|
||||
; TODO implement optimized custom version of this instead of simply reusing str2uword
|
||||
; result in A, number of characters processed also remains in cx16.r15 if you want to use it!! (0 = error)
|
||||
%asm {{
|
||||
jmp str2uword
|
||||
jsr conv.str2uword
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str2byte(str string @ AY) clobbers(Y) -> ubyte @A {
|
||||
; -- returns the signed byte value of the string number argument in AY
|
||||
inline asmsub str2byte(str string @AY) clobbers(Y) -> ubyte @A {
|
||||
; -- returns in A the signed byte value of the string number argument in AY
|
||||
; the number may be preceded by a + or - sign but may NOT contain spaces
|
||||
; (any non-digit character will terminate the number string that is parsed)
|
||||
; TODO implement optimized custom version of this instead of simply reusing str2word
|
||||
; result in A, number of characters processed also remains in cx16.r15 if you want to use it!! (0 = error)
|
||||
%asm {{
|
||||
jmp str2word
|
||||
jsr conv.str2word
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str2uword(str string @ AY) -> uword @ AY {
|
||||
asmsub str2uword(str string @AY) -> uword @AY {
|
||||
; -- returns the unsigned word value of the string number argument in AY
|
||||
; the number may NOT be preceded by a + sign and may NOT contain spaces
|
||||
; (any non-digit character will terminate the number string that is parsed)
|
||||
; result in AY, number of characters processed also remains in cx16.r15 if you want to use it!! (0 = error)
|
||||
%asm {{
|
||||
_result = P8ZP_SCRATCH_W2
|
||||
sta _mod+1
|
||||
sty _mod+2
|
||||
_result = P8ZP_SCRATCH_W1
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #0
|
||||
sty _result
|
||||
sty _result+1
|
||||
_mod lda $ffff,y ; modified
|
||||
sty cx16.r15+1
|
||||
_loop
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
sec
|
||||
sbc #48
|
||||
bpl +
|
||||
_done ; return result
|
||||
bpl _digit
|
||||
_done
|
||||
sty cx16.r15
|
||||
lda _result
|
||||
ldy _result+1
|
||||
rts
|
||||
+ cmp #10
|
||||
_digit
|
||||
cmp #10
|
||||
bcs _done
|
||||
; add digit to result
|
||||
pha
|
||||
@ -301,7 +339,7 @@ _done ; return result
|
||||
bcc +
|
||||
inc _result+1
|
||||
+ iny
|
||||
bne _mod
|
||||
bne _loop
|
||||
; never reached
|
||||
|
||||
_result_times_10 ; (W*4 + W)*2
|
||||
@ -324,19 +362,21 @@ _result_times_10 ; (W*4 + W)*2
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str2word(str string @ AY) -> word @ AY {
|
||||
asmsub str2word(str string @AY) -> word @AY {
|
||||
; -- returns the signed word value of the string number argument in AY
|
||||
; the number may be preceded by a + or - sign but may NOT contain spaces
|
||||
; (any non-digit character will terminate the number string that is parsed)
|
||||
; result in AY, number of characters processed also remains in cx16.r15 if you want to use it!! (0 = error)
|
||||
%asm {{
|
||||
_result = P8ZP_SCRATCH_W2
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
_result = P8ZP_SCRATCH_W1
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #0
|
||||
sty _result
|
||||
sty _result+1
|
||||
sty _negative
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sty cx16.r15+1
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
cmp #'+'
|
||||
bne +
|
||||
iny
|
||||
@ -344,11 +384,12 @@ _result = P8ZP_SCRATCH_W2
|
||||
bne _parse
|
||||
inc _negative
|
||||
iny
|
||||
_parse lda (P8ZP_SCRATCH_W1),y
|
||||
_parse lda (P8ZP_SCRATCH_W2),y
|
||||
sec
|
||||
sbc #48
|
||||
bpl _digit
|
||||
_done ; return result
|
||||
_done
|
||||
sty cx16.r15
|
||||
lda _negative
|
||||
beq +
|
||||
sec
|
||||
@ -361,7 +402,8 @@ _done ; return result
|
||||
+ lda _result
|
||||
ldy _result+1
|
||||
rts
|
||||
_digit cmp #10
|
||||
_digit
|
||||
cmp #10
|
||||
bcs _done
|
||||
; add digit to result
|
||||
pha
|
||||
@ -379,77 +421,103 @@ _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 hex2uword(str string @AY) -> uword @AY {
|
||||
; -- hexadecimal string (with or without '$') to uword.
|
||||
; string may be in petscii or c64-screencode encoding.
|
||||
; stops parsing at the first character that's not a hex digit (except leading $)
|
||||
; result in AY, number of characters processed also remains in cx16.r15 if you want to use it!! (0 = error)
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #0
|
||||
sty P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
sty cx16.r15+1
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
beq _stop
|
||||
cmp #'$'
|
||||
bne _loop
|
||||
iny
|
||||
_loop
|
||||
lda #0
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
beq _stop
|
||||
cmp #7 ; screencode letters A-F are 1-6
|
||||
bcc _add_letter
|
||||
cmp #'g'
|
||||
bcs _stop
|
||||
cmp #'a'
|
||||
bcs _add_letter
|
||||
cmp #'0'
|
||||
bcc _stop
|
||||
cmp #'9'+1
|
||||
bcs _stop
|
||||
_calc
|
||||
asl P8ZP_SCRATCH_W1
|
||||
rol P8ZP_SCRATCH_W1+1
|
||||
asl P8ZP_SCRATCH_W1
|
||||
rol P8ZP_SCRATCH_W1+1
|
||||
asl P8ZP_SCRATCH_W1
|
||||
rol P8ZP_SCRATCH_W1+1
|
||||
asl P8ZP_SCRATCH_W1
|
||||
rol P8ZP_SCRATCH_W1+1
|
||||
and #$0f
|
||||
clc
|
||||
adc P8ZP_SCRATCH_B1
|
||||
ora P8ZP_SCRATCH_W1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
iny
|
||||
bne _loop
|
||||
_stop
|
||||
sty cx16.r15
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
rts
|
||||
_add_letter
|
||||
pha
|
||||
lda #9
|
||||
sta P8ZP_SCRATCH_B1
|
||||
pla
|
||||
jmp _calc
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub bin2uword(str string @ AY) -> uword @AY {
|
||||
; -- binary string with or without '%' to uword.
|
||||
%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
|
||||
}}
|
||||
asmsub bin2uword(str string @AY) -> uword @AY {
|
||||
; -- binary string (with or without '%') to uword.
|
||||
; stops parsing at the first character that's not a 0 or 1. (except leading %)
|
||||
; result in AY, number of characters processed also remains in cx16.r15 if you want to use it!! (0 = error)
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #0
|
||||
sty P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
sty cx16.r15+1
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
beq _stop
|
||||
cmp #'%'
|
||||
bne _loop
|
||||
iny
|
||||
_loop
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
cmp #'0'
|
||||
bcc _stop
|
||||
cmp #'2'
|
||||
bcs _stop
|
||||
_first asl P8ZP_SCRATCH_W1
|
||||
rol P8ZP_SCRATCH_W1+1
|
||||
and #1
|
||||
ora P8ZP_SCRATCH_W1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
iny
|
||||
bne _loop
|
||||
_stop
|
||||
sty cx16.r15
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -64,6 +64,7 @@ romsub $fe7e = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += sign
|
||||
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
|
||||
; note: there is no FPWR() on the Cx16
|
||||
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
|
||||
@ -98,9 +99,9 @@ _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
|
||||
sta P8ZP_SCRATCH_B1
|
||||
tya
|
||||
ldy P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_B1
|
||||
jmp GIVAYF ; this uses the inverse order, Y/A
|
||||
}}
|
||||
}
|
||||
@ -109,9 +110,9 @@ 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
|
||||
sta P8ZP_SCRATCH_B1
|
||||
tya
|
||||
ldy P8ZP_SCRATCH_REG
|
||||
ldy P8ZP_SCRATCH_B1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
@ -149,5 +150,6 @@ sub print_f (float value) {
|
||||
}
|
||||
|
||||
%asminclude "library:c64/floats.asm", ""
|
||||
%asminclude "library:c64/floats_funcs.asm", ""
|
||||
|
||||
}
|
||||
|
756
compiler/res/prog8lib/cx16/gfx2.p8
Normal file
756
compiler/res/prog8lib/cx16/gfx2.p8
Normal file
@ -0,0 +1,756 @@
|
||||
%target cx16
|
||||
|
||||
; Bitmap pixel graphics module for the CommanderX16
|
||||
; Custom routines to use the full-screen 640x480 and 320x240 screen modes.
|
||||
; This only works on the Cx16. No text layer is currently shown, text can be drawn as part of the bitmap itself.
|
||||
; Note: for compatible graphics code that words on C64 too, use the "graphics" module instead.
|
||||
; Note: there is no color palette manipulation here, you have to do that yourself or use the "palette" module.
|
||||
|
||||
; TODO can we make a FB vector table and emulation routines for the Cx16s' GRAPH_init() call? to replace the builtin 320x200 fb driver?
|
||||
|
||||
gfx2 {
|
||||
|
||||
; read-only control variables:
|
||||
ubyte active_mode = 255
|
||||
uword width = 0
|
||||
uword height = 0
|
||||
ubyte bpp = 0
|
||||
ubyte monochrome_dont_stipple_flag = false ; set to false to enable stippling mode in monochrome displaymodes
|
||||
|
||||
sub screen_mode(ubyte mode) {
|
||||
; mode 0 = bitmap 320 x 240 x 1c monochrome
|
||||
; mode 1 = bitmap 320 x 240 x 256c
|
||||
; mode 128 = bitmap 640 x 480 x 1c monochrome
|
||||
; ...other modes?
|
||||
|
||||
; copy the lower-case charset to the upper part of the vram, so we can use it later to plot text
|
||||
|
||||
when mode {
|
||||
0 -> {
|
||||
; 320 x 240 x 1c
|
||||
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
|
||||
cx16.VERA_DC_HSCALE = 64
|
||||
cx16.VERA_DC_VSCALE = 64
|
||||
cx16.VERA_L1_CONFIG = %00000100
|
||||
cx16.VERA_L1_MAPBASE = 0
|
||||
cx16.VERA_L1_TILEBASE = 0
|
||||
width = 320
|
||||
height = 240
|
||||
bpp = 1
|
||||
}
|
||||
1 -> {
|
||||
; 320 x 240 x 256c
|
||||
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
|
||||
cx16.VERA_DC_HSCALE = 64
|
||||
cx16.VERA_DC_VSCALE = 64
|
||||
cx16.VERA_L1_CONFIG = %00000111
|
||||
cx16.VERA_L1_MAPBASE = 0
|
||||
cx16.VERA_L1_TILEBASE = 0
|
||||
width = 320
|
||||
height = 240
|
||||
bpp = 8
|
||||
}
|
||||
128 -> {
|
||||
; 640 x 480 x 1c
|
||||
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
|
||||
cx16.VERA_DC_HSCALE = 128
|
||||
cx16.VERA_DC_VSCALE = 128
|
||||
cx16.VERA_L1_CONFIG = %00000100
|
||||
cx16.VERA_L1_MAPBASE = 0
|
||||
cx16.VERA_L1_TILEBASE = %00000001
|
||||
width = 640
|
||||
height = 480
|
||||
bpp = 1
|
||||
}
|
||||
255 -> {
|
||||
; back to default text mode and colors
|
||||
cx16.VERA_CTRL = %10000000 ; reset VERA and palette
|
||||
c64.CINT() ; back to text mode
|
||||
width = 0
|
||||
height = 0
|
||||
bpp = 0
|
||||
}
|
||||
}
|
||||
|
||||
active_mode = mode
|
||||
if bpp
|
||||
clear_screen()
|
||||
}
|
||||
|
||||
sub clear_screen() {
|
||||
monochrome_stipple(false)
|
||||
position(0, 0)
|
||||
when active_mode {
|
||||
0 -> {
|
||||
; 320 x 240 x 1c
|
||||
repeat 240/2/8
|
||||
cs_innerloop640()
|
||||
}
|
||||
1 -> {
|
||||
; 320 x 240 x 256c
|
||||
repeat 240/2
|
||||
cs_innerloop640()
|
||||
}
|
||||
128 -> {
|
||||
; 640 x 480 x 1c
|
||||
repeat 480/8
|
||||
cs_innerloop640()
|
||||
}
|
||||
}
|
||||
position(0, 0)
|
||||
}
|
||||
|
||||
sub monochrome_stipple(ubyte enable) {
|
||||
monochrome_dont_stipple_flag = not enable
|
||||
}
|
||||
|
||||
sub rect(uword x, uword y, uword width, uword height, ubyte color) {
|
||||
if width==0 or height==0
|
||||
return
|
||||
horizontal_line(x, y, width, color)
|
||||
if height==1
|
||||
return
|
||||
horizontal_line(x, y+height-1, width, color)
|
||||
vertical_line(x, y+1, height-2, color)
|
||||
if width==1
|
||||
return
|
||||
vertical_line(x+width-1, y+1, height-2, color)
|
||||
}
|
||||
|
||||
sub fillrect(uword x, uword y, uword width, uword height, ubyte color) {
|
||||
if width==0
|
||||
return
|
||||
repeat height {
|
||||
horizontal_line(x, y, width, color)
|
||||
y++
|
||||
}
|
||||
}
|
||||
|
||||
sub horizontal_line(uword x, uword y, uword length, ubyte color) {
|
||||
if length==0
|
||||
return
|
||||
when active_mode {
|
||||
1 -> {
|
||||
; 8bpp mode
|
||||
position(x, y)
|
||||
%asm {{
|
||||
lda color
|
||||
phx
|
||||
ldx length+1
|
||||
beq +
|
||||
ldy #0
|
||||
- sta cx16.VERA_DATA0
|
||||
iny
|
||||
bne -
|
||||
dex
|
||||
bne -
|
||||
+ ldy length ; remaining
|
||||
beq +
|
||||
- sta cx16.VERA_DATA0
|
||||
dey
|
||||
bne -
|
||||
+ plx
|
||||
}}
|
||||
}
|
||||
0, 128 -> {
|
||||
; 1 bpp mode
|
||||
ubyte separate_pixels = (8-lsb(x)) & 7
|
||||
if separate_pixels as uword > length
|
||||
separate_pixels = lsb(length)
|
||||
repeat separate_pixels {
|
||||
; this could be optimized by setting this byte in 1 go but probably not worth it due to code size
|
||||
plot(x, y, color)
|
||||
x++
|
||||
}
|
||||
length -= separate_pixels
|
||||
if length {
|
||||
position(x, y)
|
||||
separate_pixels = lsb(length) & 7
|
||||
x += length & $fff8
|
||||
%asm {{
|
||||
lsr length+1
|
||||
ror length
|
||||
lsr length+1
|
||||
ror length
|
||||
lsr length+1
|
||||
ror length
|
||||
lda color
|
||||
bne +
|
||||
ldy #0 ; black
|
||||
bra _loop
|
||||
+ lda monochrome_dont_stipple_flag
|
||||
beq _stipple
|
||||
ldy #255 ; don't stipple
|
||||
bra _loop
|
||||
_stipple lda y
|
||||
and #1 ; determine stipple pattern to use
|
||||
bne +
|
||||
ldy #%01010101
|
||||
bra _loop
|
||||
+ ldy #%10101010
|
||||
_loop lda length
|
||||
ora length+1
|
||||
beq _done
|
||||
sty cx16.VERA_DATA0
|
||||
lda length
|
||||
bne +
|
||||
dec length+1
|
||||
+ dec length
|
||||
bra _loop
|
||||
_done
|
||||
}}
|
||||
repeat separate_pixels {
|
||||
; this could be optimized by setting this byte in 1 go but probably not worth it due to code size
|
||||
plot(x, y, color)
|
||||
x++
|
||||
}
|
||||
}
|
||||
cx16.VERA_ADDR_H = (cx16.VERA_ADDR_H & %00000111) ; vera auto-increment off again
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub vertical_line(uword x, uword y, uword height, ubyte color) {
|
||||
position(x,y)
|
||||
if active_mode==1 {
|
||||
; set vera auto-increment to 320 pixel increment (=next line)
|
||||
cx16.VERA_ADDR_H = (cx16.VERA_ADDR_H & %00000111) | (14<<4)
|
||||
%asm {{
|
||||
ldy height
|
||||
beq +
|
||||
lda color
|
||||
- sta cx16.VERA_DATA0
|
||||
dey
|
||||
bne -
|
||||
+
|
||||
}}
|
||||
return
|
||||
}
|
||||
|
||||
; note for the 1 bpp modes we can't use vera's auto increment mode because we have to 'or' the pixel data in place.
|
||||
cx16.VERA_ADDR_H = (cx16.VERA_ADDR_H & %00000111) ; no auto advance
|
||||
cx16.r15 = gfx2.plot.bits[x as ubyte & 7] ; bitmask
|
||||
if active_mode>=128
|
||||
cx16.r14 = 640/8
|
||||
else
|
||||
cx16.r14 = 320/8
|
||||
if color {
|
||||
if monochrome_dont_stipple_flag {
|
||||
repeat height {
|
||||
%asm {{
|
||||
lda cx16.VERA_DATA0
|
||||
ora cx16.r15
|
||||
sta cx16.VERA_DATA0
|
||||
lda cx16.VERA_ADDR_L
|
||||
clc
|
||||
adc cx16.r14 ; advance vera ptr to go to the next line
|
||||
sta cx16.VERA_ADDR_L
|
||||
lda cx16.VERA_ADDR_M
|
||||
adc #0
|
||||
sta cx16.VERA_ADDR_M
|
||||
; lda cx16.VERA_ADDR_H ; the bitmap size is small enough to not have to deal with the _H part.
|
||||
; adc #0
|
||||
; sta cx16.VERA_ADDR_H
|
||||
}}
|
||||
}
|
||||
} else {
|
||||
; stippling.
|
||||
height = (height+1)/2
|
||||
%asm {{
|
||||
lda x
|
||||
eor y
|
||||
and #1
|
||||
bne +
|
||||
lda cx16.VERA_ADDR_L
|
||||
clc
|
||||
adc cx16.r14 ; advance vera ptr to go to the next line for correct stipple pattern
|
||||
sta cx16.VERA_ADDR_L
|
||||
lda cx16.VERA_ADDR_M
|
||||
adc #0
|
||||
sta cx16.VERA_ADDR_M
|
||||
+
|
||||
asl cx16.r14
|
||||
ldy height
|
||||
beq +
|
||||
- lda cx16.VERA_DATA0
|
||||
ora cx16.r15
|
||||
sta cx16.VERA_DATA0
|
||||
lda cx16.VERA_ADDR_L
|
||||
clc
|
||||
adc cx16.r14 ; advance vera data ptr to go to the next-next line
|
||||
sta cx16.VERA_ADDR_L
|
||||
lda cx16.VERA_ADDR_M
|
||||
adc #0
|
||||
sta cx16.VERA_ADDR_M
|
||||
; lda cx16.VERA_ADDR_H ; the bitmap size is small enough to not have to deal with the _H part.
|
||||
; adc #0
|
||||
; sta cx16.VERA_ADDR_H
|
||||
dey
|
||||
bne -
|
||||
+
|
||||
}}
|
||||
}
|
||||
} else {
|
||||
cx16.r15 = ~cx16.r15
|
||||
repeat height {
|
||||
%asm {{
|
||||
lda cx16.VERA_DATA0
|
||||
and cx16.r15
|
||||
sta cx16.VERA_DATA0
|
||||
lda cx16.VERA_ADDR_L
|
||||
clc
|
||||
adc cx16.r14 ; advance vera data ptr to go to the next line
|
||||
sta cx16.VERA_ADDR_L
|
||||
lda cx16.VERA_ADDR_M
|
||||
adc #0
|
||||
sta cx16.VERA_ADDR_M
|
||||
; lda cx16.VERA_ADDR_H ; the bitmap size is small enough to not have to deal with the _H part.
|
||||
; adc #0
|
||||
; sta cx16.VERA_ADDR_H
|
||||
}}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub line(uword @zp x1, uword @zp y1, uword @zp x2, uword @zp y2, ubyte color) {
|
||||
; Bresenham algorithm.
|
||||
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
|
||||
if y1>y2 {
|
||||
; make sure dy is always positive to have only 4 instead of 8 special cases
|
||||
swap(x1, x2)
|
||||
swap(y1, y2)
|
||||
}
|
||||
word @zp dx = x2-x1 as word
|
||||
word @zp dy = y2-y1 as word
|
||||
|
||||
if dx==0 {
|
||||
vertical_line(x1, y1, abs(dy)+1 as uword, color)
|
||||
return
|
||||
}
|
||||
if dy==0 {
|
||||
if x1>x2
|
||||
x1=x2
|
||||
horizontal_line(x1, y1, abs(dx)+1 as uword, color)
|
||||
return
|
||||
}
|
||||
|
||||
; TODO rewrite the rest in optimized assembly (or reuse GRAPH_draw_line if we can get the FB replacement vector layer working)
|
||||
word @zp d = 0
|
||||
ubyte positive_ix = true
|
||||
if dx < 0 {
|
||||
dx = -dx
|
||||
positive_ix = false
|
||||
}
|
||||
dx *= 2
|
||||
dy *= 2
|
||||
cx16.r14 = x1 ; internal plot X
|
||||
|
||||
if dx >= dy {
|
||||
if positive_ix {
|
||||
repeat {
|
||||
plot(cx16.r14, y1, color)
|
||||
if cx16.r14==x2
|
||||
return
|
||||
cx16.r14++
|
||||
d += dy
|
||||
if d > dx {
|
||||
y1++
|
||||
d -= dx
|
||||
}
|
||||
}
|
||||
} else {
|
||||
repeat {
|
||||
plot(cx16.r14, y1, color)
|
||||
if cx16.r14==x2
|
||||
return
|
||||
cx16.r14--
|
||||
d += dy
|
||||
if d > dx {
|
||||
y1++
|
||||
d -= dx
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if positive_ix {
|
||||
repeat {
|
||||
plot(cx16.r14, y1, color)
|
||||
if y1 == y2
|
||||
return
|
||||
y1++
|
||||
d += dx
|
||||
if d > dy {
|
||||
cx16.r14++
|
||||
d -= dy
|
||||
}
|
||||
}
|
||||
} else {
|
||||
repeat {
|
||||
plot(cx16.r14, y1, color)
|
||||
if y1 == y2
|
||||
return
|
||||
y1++
|
||||
d += dx
|
||||
if d > dy {
|
||||
cx16.r14--
|
||||
d -= dy
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub circle(uword @zp xcenter, uword @zp ycenter, ubyte radius, ubyte color) {
|
||||
; Midpoint algorithm.
|
||||
if radius==0
|
||||
return
|
||||
|
||||
ubyte @zp xx = radius
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-xx
|
||||
; R14 = internal plot X
|
||||
; R15 = internal plot Y
|
||||
|
||||
while xx>=yy {
|
||||
cx16.r14 = xcenter + xx
|
||||
cx16.r15 = ycenter + yy
|
||||
plot(cx16.r14, cx16.r15, color)
|
||||
cx16.r14 = xcenter - xx
|
||||
plot(cx16.r14, cx16.r15, color)
|
||||
cx16.r14 = xcenter + xx
|
||||
cx16.r15 = ycenter - yy
|
||||
plot(cx16.r14, cx16.r15, color)
|
||||
cx16.r14 = xcenter - xx
|
||||
plot(cx16.r14, cx16.r15, color)
|
||||
cx16.r14 = xcenter + yy
|
||||
cx16.r15 = ycenter + xx
|
||||
plot(cx16.r14, cx16.r15, color)
|
||||
cx16.r14 = xcenter - yy
|
||||
plot(cx16.r14, cx16.r15, color)
|
||||
cx16.r14 = xcenter + yy
|
||||
cx16.r15 = ycenter - xx
|
||||
plot(cx16.r14, cx16.r15, color)
|
||||
cx16.r14 = xcenter - yy
|
||||
plot(cx16.r14, cx16.r15, color)
|
||||
|
||||
yy++
|
||||
if decisionOver2<=0
|
||||
decisionOver2 += (yy as word)*2+1
|
||||
else {
|
||||
xx--
|
||||
decisionOver2 += (yy as word -xx)*2+1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, ubyte color) {
|
||||
; Midpoint algorithm, filled
|
||||
if radius==0
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-radius
|
||||
|
||||
while radius>=yy {
|
||||
horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, color)
|
||||
horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, color)
|
||||
horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, color)
|
||||
horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, color)
|
||||
yy++
|
||||
if decisionOver2<=0
|
||||
decisionOver2 += (yy as word)*2+1
|
||||
else {
|
||||
radius--
|
||||
decisionOver2 += (yy as word -radius)*2+1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub plot(uword @zp x, uword y, ubyte color) {
|
||||
ubyte[8] bits = [128, 64, 32, 16, 8, 4, 2, 1]
|
||||
uword addr
|
||||
ubyte value
|
||||
|
||||
when active_mode {
|
||||
0 -> {
|
||||
%asm {{
|
||||
lda x
|
||||
eor y
|
||||
ora monochrome_dont_stipple_flag
|
||||
and #1
|
||||
}}
|
||||
if_nz {
|
||||
addr = x/8 + y*(320/8)
|
||||
value = bits[lsb(x)&7]
|
||||
if color
|
||||
cx16.vpoke_or(0, addr, value)
|
||||
else {
|
||||
value = ~value
|
||||
cx16.vpoke_and(0, addr, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
128 -> {
|
||||
%asm {{
|
||||
lda x
|
||||
eor y
|
||||
ora monochrome_dont_stipple_flag
|
||||
and #1
|
||||
}}
|
||||
if_nz {
|
||||
addr = x/8 + y*(640/8)
|
||||
value = bits[lsb(x)&7]
|
||||
if color
|
||||
cx16.vpoke_or(0, addr, value)
|
||||
else {
|
||||
value = ~value
|
||||
cx16.vpoke_and(0, addr, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
1 -> {
|
||||
void addr_mul_320_add_24(y, x) ; 24 bits result is in r0 and r1L
|
||||
value = lsb(cx16.r1)
|
||||
cx16.vpoke(value, cx16.r0, color)
|
||||
; activate vera auto-increment mode so next_pixel() can be used after this
|
||||
cx16.VERA_ADDR_H = (cx16.VERA_ADDR_H & %00000111) | %00010000
|
||||
color = cx16.VERA_DATA0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub position(uword @zp x, uword y) {
|
||||
when active_mode {
|
||||
0 -> {
|
||||
cx16.r0 = y*(320/8) + x/8
|
||||
cx16.vaddr(0, cx16.r0, 0, 1)
|
||||
}
|
||||
128 -> {
|
||||
cx16.r0 = y*(640/8) + x/8
|
||||
cx16.vaddr(0, cx16.r0, 0, 1)
|
||||
}
|
||||
1 -> {
|
||||
void addr_mul_320_add_24(y, x) ; 24 bits result is in r0 and r1L
|
||||
ubyte bank = lsb(cx16.r1)
|
||||
cx16.vaddr(bank, cx16.r0, 0, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline asmsub next_pixel(ubyte color @A) {
|
||||
; -- sets the next pixel byte to the graphics chip.
|
||||
; for 8 bpp screens this will plot 1 pixel.
|
||||
; for 1 bpp screens it will plot 8 pixels at once (color = bit pattern).
|
||||
%asm {{
|
||||
sta cx16.VERA_DATA0
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub next_pixels(uword pixels @AY, uword amount @R0) clobbers(A, Y) {
|
||||
; -- sets the next bunch of pixels from a prepared array of bytes.
|
||||
; for 8 bpp screens this will plot 1 pixel per byte.
|
||||
; for 1 bpp screens it will plot 8 pixels at once (colors are the bit patterns per byte).
|
||||
%asm {{
|
||||
phx
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldx cx16.r0+1
|
||||
beq +
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
sta cx16.VERA_DATA0
|
||||
iny
|
||||
bne -
|
||||
inc P8ZP_SCRATCH_W1+1 ; next page of 256 pixels
|
||||
dex
|
||||
bne -
|
||||
|
||||
+ ldx cx16.r0 ; remaining pixels
|
||||
beq +
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
sta cx16.VERA_DATA0
|
||||
iny
|
||||
dex
|
||||
bne -
|
||||
+ plx
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_8_pixels_from_bits(ubyte bits @R0, ubyte oncolor @A, ubyte offcolor @Y) {
|
||||
; this is only useful in 256 color mode where one pixel equals one byte value.
|
||||
%asm {{
|
||||
phx
|
||||
ldx #8
|
||||
- asl cx16.r0
|
||||
bcc +
|
||||
sta cx16.VERA_DATA0
|
||||
bra ++
|
||||
+ sty cx16.VERA_DATA0
|
||||
+ dex
|
||||
bne -
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
const ubyte charset_orig_bank = $0
|
||||
const uword charset_orig_addr = $f800 ; in bank 0, so $0f800
|
||||
const ubyte charset_bank = $1
|
||||
const uword charset_addr = $f000 ; in bank 1, so $1f000
|
||||
|
||||
sub text_charset(ubyte charset) {
|
||||
; -- make a copy of the selected character set to use with text()
|
||||
; the charset number is the same as for the cx16.screen_set_charset() ROM function.
|
||||
; 1 = ISO charset, 2 = PETSCII uppercase+graphs, 3= PETSCII uppercase+lowercase.
|
||||
cx16.screen_set_charset(charset, 0)
|
||||
cx16.vaddr(charset_orig_bank, charset_orig_addr, 0, 1)
|
||||
cx16.vaddr(charset_bank, charset_addr, 1, 1)
|
||||
repeat 256*8 {
|
||||
cx16.VERA_DATA1 = cx16.VERA_DATA0
|
||||
}
|
||||
}
|
||||
|
||||
sub text(uword @zp x, uword y, ubyte color, uword sctextptr) {
|
||||
; -- Write some text at the given pixel position. The text string must be in screencode encoding (not petscii!).
|
||||
; You must also have called text_charset() first to select and prepare the character set to use.
|
||||
; NOTE: in monochrome (1bpp) screen modes, x position is currently constrained to mulitples of 8 !
|
||||
uword chardataptr
|
||||
when active_mode {
|
||||
0, 128 -> {
|
||||
; 1-bitplane modes
|
||||
cx16.r2 = 40
|
||||
if active_mode>=128
|
||||
cx16.r2 = 80
|
||||
while @(sctextptr) {
|
||||
chardataptr = charset_addr + (@(sctextptr) as uword)*8
|
||||
cx16.vaddr(charset_bank, chardataptr, 1, 1)
|
||||
position(x,y)
|
||||
%asm {{
|
||||
lda cx16.VERA_ADDR_H
|
||||
and #%111 ; don't auto-increment, we have to do that manually because of the ora
|
||||
sta cx16.VERA_ADDR_H
|
||||
lda color
|
||||
sta P8ZP_SCRATCH_B1
|
||||
ldy #8
|
||||
- lda P8ZP_SCRATCH_B1
|
||||
bne + ; white color, plot normally
|
||||
lda cx16.VERA_DATA1
|
||||
eor #255 ; black color, keep only the other pixels
|
||||
and cx16.VERA_DATA0
|
||||
bra ++
|
||||
+ lda cx16.VERA_DATA0
|
||||
ora cx16.VERA_DATA1
|
||||
+ sta cx16.VERA_DATA0
|
||||
lda cx16.VERA_ADDR_L
|
||||
clc
|
||||
adc cx16.r2
|
||||
sta cx16.VERA_ADDR_L
|
||||
bcc +
|
||||
inc cx16.VERA_ADDR_M
|
||||
+ lda x
|
||||
clc
|
||||
adc #1
|
||||
sta x
|
||||
bcc +
|
||||
inc x+1
|
||||
+ dey
|
||||
bne -
|
||||
}}
|
||||
sctextptr++
|
||||
}
|
||||
}
|
||||
1 -> {
|
||||
; 320 x 240 x 256c
|
||||
while @(sctextptr) {
|
||||
chardataptr = charset_addr + (@(sctextptr) as uword)*8
|
||||
cx16.vaddr(charset_bank, chardataptr, 1, 1)
|
||||
repeat 8 {
|
||||
position(x,y)
|
||||
y++
|
||||
%asm {{
|
||||
phx
|
||||
ldx #1
|
||||
lda cx16.VERA_DATA1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
ldy #8
|
||||
- asl P8ZP_SCRATCH_B1
|
||||
bcc +
|
||||
stx cx16.VERA_DATA0 ; write a pixel
|
||||
bra ++
|
||||
+ lda cx16.VERA_DATA0 ; don't write a pixel, but do advance to the next address
|
||||
+ dey
|
||||
bne -
|
||||
plx
|
||||
}}
|
||||
}
|
||||
x+=8
|
||||
y-=8
|
||||
sctextptr++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
asmsub cs_innerloop640() clobbers(Y) {
|
||||
%asm {{
|
||||
ldy #80
|
||||
- stz cx16.VERA_DATA0
|
||||
stz cx16.VERA_DATA0
|
||||
stz cx16.VERA_DATA0
|
||||
stz cx16.VERA_DATA0
|
||||
stz cx16.VERA_DATA0
|
||||
stz cx16.VERA_DATA0
|
||||
stz cx16.VERA_DATA0
|
||||
stz cx16.VERA_DATA0
|
||||
dey
|
||||
bne -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub addr_mul_320_add_24(uword address @R0, uword value @AY) clobbers(A) -> uword @R0, ubyte @R1 {
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda cx16.r0
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda cx16.r0+1
|
||||
sta cx16.r1
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda cx16.r0
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
sta cx16.r0
|
||||
lda P8ZP_SCRATCH_B1
|
||||
clc
|
||||
adc P8ZP_SCRATCH_REG
|
||||
sta cx16.r0+1
|
||||
bcc +
|
||||
inc cx16.r1
|
||||
+ ; now add the value to this 24-bits number
|
||||
lda cx16.r0
|
||||
clc
|
||||
adc P8ZP_SCRATCH_W1
|
||||
sta cx16.r0
|
||||
lda cx16.r0+1
|
||||
adc P8ZP_SCRATCH_W1+1
|
||||
sta cx16.r0+1
|
||||
bcc +
|
||||
inc cx16.r1
|
||||
+ lda cx16.r1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
@ -1,9 +1,13 @@
|
||||
%target cx16
|
||||
%import syslib
|
||||
%import textio
|
||||
|
||||
; bitmap pixel graphics module for the CommanderX16
|
||||
; Bitmap pixel graphics module for the CommanderX16
|
||||
; wraps the graphics functions that are in ROM.
|
||||
; only black/white monchrome 320x200 for now.
|
||||
; only black/white monchrome 320x200 for now. (i.e. truncated at the bottom)
|
||||
; For full-screen 640x480 or 320x240 graphics, use the "gfx2" module instead. (but that is Cx16-specific)
|
||||
; Note: there is no color palette manipulation here, you have to do that yourself or use the "palette" module.
|
||||
|
||||
|
||||
graphics {
|
||||
const uword WIDTH = 320
|
||||
@ -12,22 +16,42 @@ graphics {
|
||||
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()
|
||||
cx16.GRAPH_init(0)
|
||||
clear_screen(1, 0)
|
||||
}
|
||||
|
||||
sub disable_bitmap_mode() {
|
||||
; enables text mode, erase the text screen, color white
|
||||
void cx16.screen_set_mode(2)
|
||||
txt.fill_screen(' ', 1) ; doesn't seem to fully clear the text screen after returning from gfx mode
|
||||
}
|
||||
|
||||
|
||||
sub clear_screen(ubyte pixelcolor, ubyte bgcolor) {
|
||||
cx16.GRAPH_set_colors(pixelcolor, pixelcolor, bgcolor)
|
||||
cx16.GRAPH_clear()
|
||||
}
|
||||
|
||||
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
|
||||
cx16.r0 = x1
|
||||
cx16.r1 = y1
|
||||
cx16.r2 = x2
|
||||
cx16.r3 = y2
|
||||
cx16.GRAPH_draw_line()
|
||||
cx16.GRAPH_draw_line(x1, y1, x2, y2)
|
||||
}
|
||||
|
||||
sub fillrect(uword x, uword y, uword width, uword height) {
|
||||
cx16.GRAPH_draw_rect(x, y, width, height, 0, 1)
|
||||
}
|
||||
|
||||
sub rect(uword x, uword y, uword width, uword height) {
|
||||
cx16.GRAPH_draw_rect(x, y, width, height, 0, 0)
|
||||
}
|
||||
|
||||
sub horizontal_line(uword x, uword y, uword length) {
|
||||
if length
|
||||
cx16.GRAPH_draw_line(x, y, x+length-1, y)
|
||||
}
|
||||
|
||||
sub vertical_line(uword x, uword y, uword height) {
|
||||
if height
|
||||
cx16.GRAPH_draw_line(x, y, x, y+height-1)
|
||||
}
|
||||
|
||||
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||
@ -35,122 +59,80 @@ graphics {
|
||||
;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
|
||||
;cx16.GRAPH_draw_oval(false) ; currently this call is not implemented on cx16, does a BRK
|
||||
|
||||
; Midpoint algorithm
|
||||
if radius==0
|
||||
return
|
||||
ubyte @zp xx = radius
|
||||
ubyte @zp yy = 0
|
||||
byte @zp decisionOver2 = 1-xx as byte
|
||||
word @zp decisionOver2 = (1 as word)-xx
|
||||
|
||||
while xx>=yy {
|
||||
cx16.r0 = xcenter + xx
|
||||
cx16.r1 = ycenter + yy
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_cursor_position2()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r0 = xcenter - xx
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_cursor_position2()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r0 = xcenter + xx
|
||||
cx16.r1 = ycenter - yy
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_cursor_position2()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r0 = xcenter - xx
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_cursor_position2()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r0 = xcenter + yy
|
||||
cx16.r1 = ycenter + xx
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_cursor_position2()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r0 = xcenter - yy
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_cursor_position2()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r0 = xcenter + yy
|
||||
cx16.r1 = ycenter - xx
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_cursor_position2()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r0 = xcenter - yy
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_cursor_position2()
|
||||
cx16.FB_set_pixel(1)
|
||||
yy++
|
||||
if decisionOver2<=0 {
|
||||
decisionOver2 += 2*yy+1
|
||||
decisionOver2 += (yy as word)*2+1
|
||||
} else {
|
||||
xx--
|
||||
decisionOver2 += 2*(yy-xx)+1
|
||||
decisionOver2 += (yy as word -xx)*2+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
|
||||
if radius==0
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word decisionOver2 = (1 as word)-radius
|
||||
|
||||
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)
|
||||
}
|
||||
while radius>=yy {
|
||||
horizontal_line(xcenter-radius, ycenter+yy, radius*2+1)
|
||||
horizontal_line(xcenter-radius, ycenter-yy, radius*2+1)
|
||||
horizontal_line(xcenter-yy, ycenter+radius, yy*2+1)
|
||||
horizontal_line(xcenter-yy, ycenter-radius, yy*2+1)
|
||||
yy++
|
||||
if decisionOver2<=0
|
||||
decisionOver2 += 2*yy+1
|
||||
decisionOver2 += (yy as word)*2+1
|
||||
else {
|
||||
xx--
|
||||
decisionOver2 += 2*(yy-xx)+1
|
||||
radius--
|
||||
decisionOver2 += (yy as word -radius)*2+1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub plot(uword plotx, ubyte ploty) {
|
||||
cx16.r0 = plotx
|
||||
cx16.r1 = ploty
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
inline asmsub plot(uword plotx @R0, uword ploty @R1) clobbers(A, X, Y) {
|
||||
%asm {{
|
||||
jsr cx16.FB_cursor_position
|
||||
lda #1
|
||||
jsr cx16.FB_set_pixel
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
171
compiler/res/prog8lib/cx16/palette.p8
Normal file
171
compiler/res/prog8lib/cx16/palette.p8
Normal file
@ -0,0 +1,171 @@
|
||||
%target cx16
|
||||
|
||||
; Manipulate the Commander X16's display color palette.
|
||||
; Should you want to restore the default palette, you have to reinitialize the Vera yourself.
|
||||
|
||||
palette {
|
||||
|
||||
uword vera_palette_ptr
|
||||
ubyte c
|
||||
|
||||
sub set_color(ubyte index, uword color) {
|
||||
cx16.vpoke(1, $fa00+index*2, lsb(color))
|
||||
cx16.vpoke(1, $fa01+index*2, msb(color))
|
||||
}
|
||||
|
||||
sub set_rgb4(uword palletteptr, uword num_colors) {
|
||||
; 2 bytes per color entry, the Vera uses this, but the R/GB bytes order is swapped
|
||||
vera_palette_ptr = $fa00
|
||||
repeat num_colors {
|
||||
cx16.vpoke(1, vera_palette_ptr+1, @(palletteptr))
|
||||
palletteptr++
|
||||
cx16.vpoke(1, vera_palette_ptr, @(palletteptr))
|
||||
palletteptr++
|
||||
vera_palette_ptr+=2
|
||||
}
|
||||
}
|
||||
|
||||
sub set_rgb8(uword palletteptr, uword num_colors) {
|
||||
; 3 bytes per color entry, adjust color depth from 8 to 4 bits per channel.
|
||||
vera_palette_ptr = $fa00
|
||||
ubyte red
|
||||
ubyte greenblue
|
||||
repeat num_colors {
|
||||
red = @(palletteptr) >> 4
|
||||
palletteptr++
|
||||
greenblue = @(palletteptr) & %11110000
|
||||
palletteptr++
|
||||
greenblue |= @(palletteptr) >> 4 ; add Blue
|
||||
palletteptr++
|
||||
cx16.vpoke(1, vera_palette_ptr, greenblue)
|
||||
vera_palette_ptr++
|
||||
cx16.vpoke(1, vera_palette_ptr, red)
|
||||
vera_palette_ptr++
|
||||
}
|
||||
}
|
||||
|
||||
sub set_monochrome() {
|
||||
vera_palette_ptr = $fa00
|
||||
cx16.vpoke(1, vera_palette_ptr, 0)
|
||||
vera_palette_ptr++
|
||||
cx16.vpoke(1, vera_palette_ptr, 0)
|
||||
vera_palette_ptr++
|
||||
repeat 255 {
|
||||
cx16.vpoke(1, vera_palette_ptr, 255)
|
||||
vera_palette_ptr++
|
||||
cx16.vpoke(1, vera_palette_ptr, 255)
|
||||
vera_palette_ptr++
|
||||
}
|
||||
}
|
||||
|
||||
sub set_grayscale() {
|
||||
vera_palette_ptr = $fa00
|
||||
repeat 16 {
|
||||
c=0
|
||||
repeat 16 {
|
||||
cx16.vpoke(1, vera_palette_ptr, c)
|
||||
vera_palette_ptr++
|
||||
cx16.vpoke(1, vera_palette_ptr, c)
|
||||
vera_palette_ptr++
|
||||
c += $11
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uword[] C64_colorpalette_dark = [ ; this is a darker palette with more contrast
|
||||
$000, ; 0 = black
|
||||
$FFF, ; 1 = white
|
||||
$632, ; 2 = red
|
||||
$7AB, ; 3 = cyan
|
||||
$638, ; 4 = purple
|
||||
$584, ; 5 = green
|
||||
$327, ; 6 = blue
|
||||
$BC6, ; 7 = yellow
|
||||
$642, ; 8 = orange
|
||||
$430, ; 9 = brown
|
||||
$965, ; 10 = light red
|
||||
$444, ; 11 = dark grey
|
||||
$666, ; 12 = medium grey
|
||||
$9D8, ; 13 = light green
|
||||
$65B, ; 14 = light blue
|
||||
$999 ; 15 = light grey
|
||||
]
|
||||
|
||||
uword[] C64_colorpalette_pepto = [ ; # this is Pepto's Commodore-64 palette http://www.pepto.de/projects/colorvic/
|
||||
$000, ; 0 = black
|
||||
$FFF, ; 1 = white
|
||||
$833, ; 2 = red
|
||||
$7cc, ; 3 = cyan
|
||||
$839, ; 4 = purple
|
||||
$5a4, ; 5 = green
|
||||
$229, ; 6 = blue
|
||||
$ef7, ; 7 = yellow
|
||||
$852, ; 8 = orange
|
||||
$530, ; 9 = brown
|
||||
$c67, ; 10 = light red
|
||||
$444, ; 11 = dark grey
|
||||
$777, ; 12 = medium grey
|
||||
$af9, ; 13 = light green
|
||||
$76e, ; 14 = light blue
|
||||
$bbb ; 15 = light grey
|
||||
]
|
||||
|
||||
uword[] C64_colorpalette_light = [ ; this is a lighter palette
|
||||
$000, ; 0 = black
|
||||
$FFF, ; 1 = white
|
||||
$944, ; 2 = red
|
||||
$7CC, ; 3 = cyan
|
||||
$95A, ; 4 = purple
|
||||
$6A5, ; 5 = green
|
||||
$549, ; 6 = blue
|
||||
$CD8, ; 7 = yellow
|
||||
$963, ; 8 = orange
|
||||
$650, ; 9 = brown
|
||||
$C77, ; 10 = light red
|
||||
$666, ; 11 = dark grey
|
||||
$888, ; 12 = medium grey
|
||||
$AE9, ; 13 = light green
|
||||
$87C, ; 14 = light blue
|
||||
$AAA ; 15 = light grey
|
||||
]
|
||||
|
||||
sub set_c64pepto() {
|
||||
vera_palette_ptr = $fa00
|
||||
repeat 16 {
|
||||
for c in 0 to 15 {
|
||||
uword cc = C64_colorpalette_pepto[c]
|
||||
cx16.vpoke(1, vera_palette_ptr, lsb(cc)) ; G, B
|
||||
vera_palette_ptr++
|
||||
cx16.vpoke(1, vera_palette_ptr, msb(cc)) ; R
|
||||
vera_palette_ptr++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub set_c64light() {
|
||||
vera_palette_ptr = $fa00
|
||||
repeat 16 {
|
||||
for c in 0 to 15 {
|
||||
uword cc = C64_colorpalette_light[c]
|
||||
cx16.vpoke(1, vera_palette_ptr, lsb(cc)) ; G, B
|
||||
vera_palette_ptr++
|
||||
cx16.vpoke(1, vera_palette_ptr, msb(cc)) ; R
|
||||
vera_palette_ptr++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub set_c64dark() {
|
||||
vera_palette_ptr = $fa00
|
||||
repeat 16 {
|
||||
for c in 0 to 15 {
|
||||
uword cc = C64_colorpalette_dark[c]
|
||||
cx16.vpoke(1, vera_palette_ptr, lsb(cc)) ; G, B
|
||||
vera_palette_ptr++
|
||||
cx16.vpoke(1, vera_palette_ptr, msb(cc)) ; R
|
||||
vera_palette_ptr++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -24,7 +24,7 @@ romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) ; re
|
||||
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 $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set top of memory pointer. NOTE: as a Cx16 extension, also returns the number of RAM memory banks in register A ! See MEMTOP2
|
||||
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
|
||||
@ -56,6 +56,48 @@ romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of
|
||||
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
|
||||
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
|
||||
|
||||
; ---- utility
|
||||
|
||||
asmsub STOP2() -> ubyte @A {
|
||||
; -- check if STOP key was pressed, returns true if so. More convenient to use than STOP() because that only sets the carry status flag.
|
||||
%asm {{
|
||||
phx
|
||||
jsr c64.STOP
|
||||
beq +
|
||||
plx
|
||||
lda #0
|
||||
rts
|
||||
+ plx
|
||||
lda #1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub RDTIM16() -> uword @AY {
|
||||
; -- like RDTIM() but only returning the lower 16 bits for convenience
|
||||
%asm {{
|
||||
phx
|
||||
jsr c64.RDTIM
|
||||
pha
|
||||
txa
|
||||
tay
|
||||
pla
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub MEMTOP2() -> ubyte @A {
|
||||
; -- uses MEMTOP's cx16 extension to query the number of available RAM banks.
|
||||
%asm {{
|
||||
phx
|
||||
sec
|
||||
jsr c64.MEMTOP
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
cx16 {
|
||||
@ -86,50 +128,50 @@ cx16 {
|
||||
|
||||
; 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
|
||||
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
|
||||
const uword via1 = $9f60 ;VIA 6522 #1
|
||||
&ubyte d1prb = via1+0
|
||||
&ubyte d1pra = via1+1
|
||||
&ubyte d1ddrb = via1+2
|
||||
@ -147,23 +189,23 @@ cx16 {
|
||||
&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
|
||||
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 ----
|
||||
@ -190,61 +232,227 @@ 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
|
||||
romsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, ubyte jiffies @R3) clobbers(A, X, Y)
|
||||
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, ubyte @R3 ; result registers see clock_set_date_time()
|
||||
|
||||
; TODO specify the correct clobbers for alle these functions below, we now assume all 3 regs are clobbered
|
||||
|
||||
; high level graphics & fonts
|
||||
romsub $ff20 = GRAPH_init() clobbers(A,X,Y) ; uses vectors=r0
|
||||
romsub $ff20 = GRAPH_init(uword vectors @R0) clobbers(A,X,Y)
|
||||
romsub $ff23 = GRAPH_clear() clobbers(A,X,Y)
|
||||
romsub $ff26 = GRAPH_set_window() clobbers(A,X,Y) ; uses x=r0, y=r1, width=r2, height=r3
|
||||
romsub $ff26 = GRAPH_set_window(uword x @R0, uword y @R1, uword width @R2, uword height @R3) clobbers(A,X,Y)
|
||||
romsub $ff29 = GRAPH_set_colors(ubyte stroke @A, ubyte fill @X, ubyte background @Y) clobbers (A,X,Y)
|
||||
romsub $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 $ff2c = GRAPH_draw_line(uword x1 @R0, uword y1 @R1, uword x2 @R2, uword y2 @R3) clobbers(A,X,Y)
|
||||
romsub $ff2f = GRAPH_draw_rect(uword x @R0, uword y @R1, uword width @R2, uword height @R3, uword cornerradius @R4, ubyte fill @Pc) clobbers(A,X,Y)
|
||||
romsub $ff32 = GRAPH_move_rect(uword sx @R0, uword sy @R1, uword tx @R2, uword ty @R3, uword width @R4, uword height @R5) clobbers(A,X,Y)
|
||||
romsub $ff35 = GRAPH_draw_oval(uword x @R0, uword y @R1, uword width @R2, uword height @R3, ubyte fill @Pc) clobbers(A,X,Y)
|
||||
romsub $ff38 = GRAPH_draw_image(uword x @R0, uword y @R1, uword ptr @R2, uword width @R3, uword height @R4) clobbers(A,X,Y)
|
||||
romsub $ff3b = GRAPH_set_font(uword fontptr @R0) clobbers(A,X,Y)
|
||||
romsub $ff3e = GRAPH_get_char_size(ubyte baseline @A, ubyte width @X, ubyte height_or_style @Y, ubyte is_control @Pc) clobbers(A,X,Y)
|
||||
romsub $ff41 = GRAPH_put_char(ubyte char @A) clobbers(A,X,Y) ; uses x=r0, y=r1
|
||||
romsub $ff41 = GRAPH_put_char(uword x @R0, uword y @R1, ubyte char @A) clobbers(A,X,Y)
|
||||
romsub $ff41 = GRAPH_put_next_char(ubyte char @A) clobbers(A,X,Y) ; alias for the routine above that doesn't reset the position of the initial character
|
||||
|
||||
; framebuffer
|
||||
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 $fef9 = FB_get_info() clobbers(X,Y) -> byte @A, uword @R0, uword @R1 ; width=r0, height=r1
|
||||
romsub $fefc = FB_set_palette(uword pointer @R0, ubyte index @A, ubyte bytecount @X) clobbers(A,X,Y)
|
||||
romsub $feff = FB_cursor_position(uword x @R0, uword y @R1) clobbers(A,X,Y)
|
||||
romsub $feff = FB_cursor_position2() clobbers(A,X,Y) ; alias for the previous routine, but avoiding having to respecify both x and y every time
|
||||
romsub $ff02 = FB_cursor_next_line(uword x @R0) clobbers(A,X,Y)
|
||||
romsub $ff05 = FB_get_pixel() clobbers(X,Y) -> ubyte @A
|
||||
romsub $ff08 = FB_get_pixels() clobbers(A,X,Y) ; uses ptr=r0, count=r1
|
||||
romsub $ff08 = FB_get_pixels(uword pointer @R0, uword count @R1) clobbers(A,X,Y)
|
||||
romsub $ff0b = FB_set_pixel(ubyte color @A) clobbers(A,X,Y)
|
||||
romsub $ff0e = FB_set_pixels() clobbers(A,X,Y) ; uses ptr=r0, count=r1
|
||||
romsub $ff0e = FB_set_pixels(uword pointer @R0, uword count @R1) clobbers(A,X,Y)
|
||||
romsub $ff11 = FB_set_8_pixels(ubyte pattern @A, ubyte color @X) clobbers(A,X,Y)
|
||||
romsub $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
|
||||
romsub $ff14 = FB_set_8_pixels_opaque(ubyte pattern @R0, ubyte mask @A, ubyte color1 @X, ubyte color2 @Y) clobbers(A,X,Y)
|
||||
romsub $ff17 = FB_fill_pixels(uword count @R0, uword pstep @R1, ubyte color @A) clobbers(A,X,Y)
|
||||
romsub $ff1a = FB_filter_pixels(uword pointer @ R0, uword count @R1) clobbers(A,X,Y)
|
||||
romsub $ff1d = FB_move_pixels(uword sx @R0, uword sy @R1, uword tx @R2, uword ty @R3, uword count @R4) clobbers(A,X,Y)
|
||||
|
||||
; misc
|
||||
romsub $fef0 = sprite_set_image(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 $fef0 = sprite_set_image(uword pixels @R0, uword mask @R1, ubyte bpp @R2, ubyte number @A, ubyte width @X, ubyte height @Y, ubyte apply_mask @Pc) clobbers(A,X,Y) -> ubyte @Pc
|
||||
romsub $fef3 = sprite_set_position(uword x @R0, uword y @R1, ubyte number @A) clobbers(A,X,Y)
|
||||
romsub $fee4 = memory_fill(uword address @R0, uword num_bytes @R1, ubyte value @A) clobbers(A,X,Y)
|
||||
romsub $fee7 = memory_copy(uword source @R0, uword target @R1, uword num_bytes @R2) clobbers(A,X,Y)
|
||||
romsub $feea = memory_crc(uword address @R0, uword num_bytes @R1) clobbers(A,X,Y) -> uword @R2
|
||||
romsub $feed = memory_decompress(uword input @R0, uword output @R1) clobbers(A,X,Y) -> uword @R1 ; last address +1 is result in R1
|
||||
romsub $fedb = console_init(uword x @R0, uword y @R1, uword width @R2, uword height @R3) clobbers(A,X,Y)
|
||||
romsub $fede = console_put_char(ubyte char @A, ubyte wrapping @Pc) clobbers(A,X,Y)
|
||||
romsub $fee1 = console_get_char() clobbers(X,Y) -> ubyte @A
|
||||
romsub $fed8 = console_put_image() 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 $fed8 = console_put_image(uword pointer @R0, uword width @R1, uword height @R2) clobbers(A,X,Y)
|
||||
romsub $fed5 = console_set_paging_message(uword msgptr @R0) clobbers(A,X,Y)
|
||||
romsub $fed2 = kbdbuf_put(ubyte key @A) clobbers(A,X,Y)
|
||||
romsub $fecf = entropy_get() -> ubyte @A, ubyte @X, ubyte @Y
|
||||
romsub $fecc = monitor() clobbers(A,X,Y)
|
||||
|
||||
|
||||
|
||||
; ---- end of kernal routines ----
|
||||
|
||||
|
||||
; ---- utilities -----
|
||||
|
||||
inline asmsub rombank(ubyte rombank @A) {
|
||||
; -- set the rom banks
|
||||
%asm {{
|
||||
sta $01 ; rom bank register (new)
|
||||
sta cx16.d1prb ; rom bank register (old)
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub rambank(ubyte rambank @A) {
|
||||
; -- set the ram bank
|
||||
%asm {{
|
||||
sta $00 ; ram bank register (new)
|
||||
sta cx16.d1pra ; ram bank register (old)
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub vpeek(ubyte bank @A, uword address @XY) -> ubyte @A {
|
||||
; -- get a byte from VERA's video memory
|
||||
; note: inefficient when reading multiple sequential bytes!
|
||||
%asm {{
|
||||
pha
|
||||
lda #1
|
||||
sta cx16.VERA_CTRL
|
||||
pla
|
||||
and #1
|
||||
sta cx16.VERA_ADDR_H
|
||||
sty cx16.VERA_ADDR_M
|
||||
stx cx16.VERA_ADDR_L
|
||||
lda cx16.VERA_DATA1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub vaddr(ubyte bank @A, uword address @R0, ubyte addrsel @R1, byte autoIncrOrDecrByOne @Y) clobbers(A) {
|
||||
; -- setup the VERA's data address register 0 or 1
|
||||
%asm {{
|
||||
and #1
|
||||
pha
|
||||
lda cx16.r1
|
||||
and #1
|
||||
sta cx16.VERA_CTRL
|
||||
lda cx16.r0
|
||||
sta cx16.VERA_ADDR_L
|
||||
lda cx16.r0+1
|
||||
sta cx16.VERA_ADDR_M
|
||||
pla
|
||||
cpy #0
|
||||
bmi ++
|
||||
beq +
|
||||
ora #%00010000
|
||||
+ sta cx16.VERA_ADDR_H
|
||||
rts
|
||||
+ ora #%00011000
|
||||
sta cx16.VERA_ADDR_H
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub vpoke(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers(A) {
|
||||
; -- write a single byte to VERA's video memory
|
||||
; note: inefficient when writing multiple sequential bytes!
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
and #1
|
||||
sta cx16.VERA_ADDR_H
|
||||
lda cx16.r0
|
||||
sta cx16.VERA_ADDR_L
|
||||
lda cx16.r0+1
|
||||
sta cx16.VERA_ADDR_M
|
||||
sty cx16.VERA_DATA0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub vpoke_or(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers (A) {
|
||||
; -- or a single byte to the value already in the VERA's video memory at that location
|
||||
; note: inefficient when writing multiple sequential bytes!
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
and #1
|
||||
sta cx16.VERA_ADDR_H
|
||||
lda cx16.r0
|
||||
sta cx16.VERA_ADDR_L
|
||||
lda cx16.r0+1
|
||||
sta cx16.VERA_ADDR_M
|
||||
tya
|
||||
ora cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub vpoke_and(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers(A) {
|
||||
; -- and a single byte to the value already in the VERA's video memory at that location
|
||||
; note: inefficient when writing multiple sequential bytes!
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
and #1
|
||||
sta cx16.VERA_ADDR_H
|
||||
lda cx16.r0
|
||||
sta cx16.VERA_ADDR_L
|
||||
lda cx16.r0+1
|
||||
sta cx16.VERA_ADDR_M
|
||||
tya
|
||||
and cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub vpoke_xor(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers (A) {
|
||||
; -- xor a single byte to the value already in the VERA's video memory at that location
|
||||
; note: inefficient when writing multiple sequential bytes!
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
and #1
|
||||
sta cx16.VERA_ADDR_H
|
||||
lda cx16.r0
|
||||
sta cx16.VERA_ADDR_L
|
||||
lda cx16.r0+1
|
||||
sta cx16.VERA_ADDR_M
|
||||
tya
|
||||
eor cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub FB_set_pixels_from_buf(uword buffer, uword count) {
|
||||
%asm {{
|
||||
; -- This is replacement code for the normal FB_set_pixels subroutine in ROM
|
||||
; However that routine contains a bug in the current v38 ROM that makes it crash when count > 255.
|
||||
; So the code below replaces that. Once the ROM is patched this routine is no longer necessary.
|
||||
; See https://github.com/commanderx16/x16-rom/issues/179
|
||||
phx
|
||||
lda buffer
|
||||
ldy buffer+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
jsr _pixels
|
||||
plx
|
||||
rts
|
||||
|
||||
_pixels lda count+1
|
||||
beq +
|
||||
ldx #0
|
||||
- jsr _loop
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
dec count+1
|
||||
bne -
|
||||
|
||||
+ ldx count
|
||||
_loop ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
sta cx16.VERA_DATA0
|
||||
iny
|
||||
dex
|
||||
bne -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
; ---- system stuff -----
|
||||
asmsub init_system() {
|
||||
; Initializes the machine to a sane starting state.
|
||||
; Called automatically by the loader program logic.
|
||||
@ -277,15 +485,128 @@ asmsub init_system() {
|
||||
}}
|
||||
}
|
||||
|
||||
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)
|
||||
}}
|
||||
}
|
||||
|
||||
sys {
|
||||
; ------- lowlevel system routines --------
|
||||
|
||||
const ubyte target = 16 ; compilation target specifier. 64 = C64, 16 = CommanderX16.
|
||||
|
||||
|
||||
asmsub reset_system() {
|
||||
; Soft-reset the system back to Basic prompt.
|
||||
%asm {{
|
||||
sei
|
||||
stz $01 ; bank the kernal in (new rom bank register)
|
||||
stz cx16.d1prb ; bank the kernal in (old rom bank register)
|
||||
jmp (cx16.RESET_VEC)
|
||||
}}
|
||||
}
|
||||
|
||||
sub wait(uword jiffies) {
|
||||
; --- wait approximately the given number of jiffies (1/60th seconds)
|
||||
repeat jiffies {
|
||||
ubyte jiff = lsb(c64.RDTIM16())
|
||||
while jiff==lsb(c64.RDTIM16()) {
|
||||
; wait until 1 jiffy has passed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
|
||||
%asm {{
|
||||
sta cx16.r2
|
||||
sty cx16.r2+1
|
||||
jsr cx16.memory_copy
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub memset(uword mem @R0, uword numbytes @R1, ubyte value @A) clobbers(A,X,Y) {
|
||||
%asm {{
|
||||
jsr cx16.memory_fill
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memsetw(uword mem @R0, uword numwords @R1, uword value @AY) clobbers (A,X,Y) {
|
||||
%asm {{
|
||||
ldx cx16.r0
|
||||
stx P8ZP_SCRATCH_W1
|
||||
ldx cx16.r0+1
|
||||
stx P8ZP_SCRATCH_W1+1
|
||||
ldx cx16.r1
|
||||
stx P8ZP_SCRATCH_W2
|
||||
ldx cx16.r1+1
|
||||
stx P8ZP_SCRATCH_W2+1
|
||||
jmp prog8_lib.memsetw
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub rsave() {
|
||||
; save cpu status flag and all registers A, X, Y.
|
||||
; see http://6502.org/tutorials/register_preservation.html
|
||||
%asm {{
|
||||
php
|
||||
pha
|
||||
phy
|
||||
phx
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub rrestore() {
|
||||
; restore all registers and cpu status flag
|
||||
%asm {{
|
||||
plx
|
||||
ply
|
||||
pla
|
||||
plp
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub read_flags() -> ubyte @A {
|
||||
%asm {{
|
||||
php
|
||||
pla
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub clear_carry() {
|
||||
%asm {{
|
||||
clc
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub set_carry() {
|
||||
%asm {{
|
||||
sec
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub clear_irqd() {
|
||||
%asm {{
|
||||
cli
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub set_irqd() {
|
||||
%asm {{
|
||||
sei
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub exit(ubyte returnvalue @A) {
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
%asm {{
|
||||
jsr c64.CLRCHN ; reset i/o channels
|
||||
ldx prog8_lib.orig_stackpointer
|
||||
txs
|
||||
rts ; return to original caller
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub progend() -> uword @AY {
|
||||
%asm {{
|
||||
lda #<prog8_program_end
|
||||
ldy #>prog8_program_end
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
@ -15,10 +15,23 @@ const ubyte DEFAULT_WIDTH = 80
|
||||
const ubyte DEFAULT_HEIGHT = 60
|
||||
|
||||
|
||||
sub clear_screen() {
|
||||
clear_screenchars(' ')
|
||||
sub clear_screen() {
|
||||
txt.chrout(147)
|
||||
}
|
||||
|
||||
sub home() {
|
||||
txt.chrout(19)
|
||||
}
|
||||
|
||||
sub nl() {
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
sub spc() {
|
||||
txt.chrout(' ')
|
||||
}
|
||||
|
||||
|
||||
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
|
||||
; ---- fill the character screen with the given fill character and character color.
|
||||
%asm {{
|
||||
@ -154,10 +167,9 @@ sub uppercase() {
|
||||
cx16.screen_set_charset(2, 0) ; uppercase charset
|
||||
}
|
||||
|
||||
asmsub scroll_left (ubyte dummy @ Pc) clobbers(A, Y) {
|
||||
asmsub scroll_left() clobbers(A, Y) {
|
||||
; ---- scroll the whole screen 1 character to the left
|
||||
; 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
|
||||
@ -198,10 +210,9 @@ _lx ldx #0 ; modified
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub scroll_right (ubyte dummy @ Pc) clobbers(A) {
|
||||
asmsub scroll_right() clobbers(A) {
|
||||
; ---- scroll the whole screen 1 character to the right
|
||||
; 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
|
||||
@ -250,10 +261,9 @@ _lx ldx #0 ; modified
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub scroll_up (ubyte dummy @ Pc) clobbers(A, Y) {
|
||||
asmsub scroll_up() clobbers(A, Y) {
|
||||
; ---- scroll the whole screen 1 character up
|
||||
; 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
|
||||
@ -300,10 +310,9 @@ _nextline
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub scroll_down (ubyte dummy @ Pc) clobbers(A, Y) {
|
||||
asmsub scroll_down() clobbers(A, Y) {
|
||||
; ---- scroll the whole screen 1 character down
|
||||
; contents of the top row are unchanged, you should refill/clear this yourself
|
||||
; Carry flag is a dummy on the cx16
|
||||
%asm {{
|
||||
phx
|
||||
jsr c64.SCREEN
|
||||
|
@ -1,3 +1,5 @@
|
||||
; routine to draw the Commander X16's log in petscii.
|
||||
|
||||
%import textio
|
||||
|
||||
cx16logo {
|
||||
@ -14,7 +16,7 @@ cx16logo {
|
||||
uword strptr
|
||||
for strptr in logo_lines
|
||||
txt.print(strptr)
|
||||
txt.chrout('\n')
|
||||
txt.nl()
|
||||
}
|
||||
|
||||
str[] logo_lines = [
|
||||
|
@ -1,20 +1,22 @@
|
||||
%import textio
|
||||
%import syslib
|
||||
; C64 and Cx16 disk drive I/O routines.
|
||||
;
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
|
||||
; Note: this code is compatible with C64 and CX16.
|
||||
%import textio
|
||||
%import string
|
||||
%import syslib
|
||||
|
||||
diskio {
|
||||
|
||||
|
||||
sub directory(ubyte drivenumber) -> byte {
|
||||
; -- Shows the directory contents of disk drive 8-11 (provide as argument).
|
||||
sub directory(ubyte drivenumber) -> ubyte {
|
||||
; -- Prints the directory contents of disk drive 8-11 to the screen. Returns success.
|
||||
|
||||
c64.SETNAM(1, "$")
|
||||
c64.SETLFS(1, drivenumber, 0)
|
||||
void c64.OPEN() ; open 1,8,0,"$"
|
||||
c64.SETLFS(13, drivenumber, 0)
|
||||
void c64.OPEN() ; open 13,8,0,"$"
|
||||
if_cs
|
||||
goto io_error
|
||||
void c64.CHKIN(1) ; use #1 as input channel
|
||||
void c64.CHKIN(13) ; use #13 as input channel
|
||||
if_cs
|
||||
goto io_error
|
||||
|
||||
@ -25,31 +27,34 @@ diskio {
|
||||
; 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 low = c64.CHRIN()
|
||||
ubyte high = c64.CHRIN()
|
||||
txt.print_uw(mkword(high, low))
|
||||
txt.spc()
|
||||
ubyte @zp char
|
||||
do {
|
||||
repeat {
|
||||
char = c64.CHRIN()
|
||||
if char==0
|
||||
break
|
||||
txt.chrout(char)
|
||||
} until char==0
|
||||
txt.chrout('\n')
|
||||
}
|
||||
txt.nl()
|
||||
void c64.CHRIN() ; skip 2 bytes
|
||||
void c64.CHRIN()
|
||||
status = c64.READST()
|
||||
void c64.STOP()
|
||||
if_nz
|
||||
if c64.STOP2()
|
||||
break
|
||||
}
|
||||
|
||||
io_error:
|
||||
status = c64.READST()
|
||||
c64.CLRCHN() ; restore default i/o devices
|
||||
c64.CLOSE(1)
|
||||
c64.CLOSE(13)
|
||||
|
||||
if status and status != 64 { ; 64=end of file
|
||||
if status and status & $40 == 0 { ; bit 6=end of file
|
||||
txt.print("\ni/o error, status: ")
|
||||
txt.print_ub(status)
|
||||
txt.chrout('\n')
|
||||
txt.nl()
|
||||
return false
|
||||
}
|
||||
|
||||
@ -57,9 +62,284 @@ io_error:
|
||||
}
|
||||
|
||||
|
||||
sub status(ubyte drivenumber) {
|
||||
; -- display the disk drive's current status message
|
||||
c64.SETNAM(0, $0000)
|
||||
; internal variables for the iterative file lister / loader
|
||||
ubyte list_skip_disk_name
|
||||
uword list_pattern
|
||||
uword list_blocks
|
||||
ubyte iteration_in_progress = false
|
||||
ubyte @zp first_byte
|
||||
ubyte have_first_byte
|
||||
str list_filename = "?" * 32
|
||||
|
||||
|
||||
; ----- get a list of files (uses iteration functions internally) -----
|
||||
|
||||
sub list_files(ubyte drivenumber, uword pattern_ptr, uword name_ptrs, ubyte max_names) -> ubyte {
|
||||
; -- fill the array 'name_ptrs' with (pointers to) the names of the files requested.
|
||||
uword names_buffer = memory("filenames", 512)
|
||||
uword buffer_start = names_buffer
|
||||
ubyte files_found = 0
|
||||
if lf_start_list(drivenumber, pattern_ptr) {
|
||||
while lf_next_entry() {
|
||||
@(name_ptrs) = lsb(names_buffer)
|
||||
name_ptrs++
|
||||
@(name_ptrs) = msb(names_buffer)
|
||||
name_ptrs++
|
||||
names_buffer += string.copy(diskio.list_filename, names_buffer) + 1
|
||||
files_found++
|
||||
if names_buffer - buffer_start > 512-18
|
||||
break
|
||||
if files_found == max_names
|
||||
break
|
||||
}
|
||||
lf_end_list()
|
||||
}
|
||||
return files_found
|
||||
}
|
||||
|
||||
; ----- iterative file lister functions (uses io channel 12) -----
|
||||
|
||||
sub lf_start_list(ubyte drivenumber, uword pattern_ptr) -> ubyte {
|
||||
; -- start an iterative file listing with optional pattern matching.
|
||||
; note: only a single iteration loop can be active at a time!
|
||||
lf_end_list()
|
||||
list_pattern = pattern_ptr
|
||||
list_skip_disk_name = true
|
||||
iteration_in_progress = true
|
||||
|
||||
c64.SETNAM(1, "$")
|
||||
c64.SETLFS(12, drivenumber, 0)
|
||||
void c64.OPEN() ; open 12,8,0,"$"
|
||||
if_cs
|
||||
goto io_error
|
||||
void c64.CHKIN(12) ; use #12 as input channel
|
||||
if_cs
|
||||
goto io_error
|
||||
|
||||
repeat 4 {
|
||||
void c64.CHRIN() ; skip the 4 prologue bytes
|
||||
}
|
||||
|
||||
if c64.READST()==0
|
||||
return true
|
||||
|
||||
io_error:
|
||||
lf_end_list()
|
||||
return false
|
||||
}
|
||||
|
||||
sub lf_next_entry() -> ubyte {
|
||||
; -- retrieve the next entry from an iterative file listing session.
|
||||
; results will be found in list_blocks and list_filename.
|
||||
; if it returns false though, there are no more entries (or an error occurred).
|
||||
|
||||
if not iteration_in_progress
|
||||
return false
|
||||
|
||||
repeat {
|
||||
void c64.CHKIN(12) ; use #12 as input channel again
|
||||
|
||||
uword nameptr = &list_filename
|
||||
ubyte blocks_lsb = c64.CHRIN()
|
||||
ubyte blocks_msb = c64.CHRIN()
|
||||
|
||||
if c64.READST()
|
||||
goto close_end
|
||||
|
||||
list_blocks = mkword(blocks_msb, blocks_lsb)
|
||||
|
||||
; read until the filename starts after the first "
|
||||
while c64.CHRIN()!='\"' {
|
||||
if c64.READST()
|
||||
goto close_end
|
||||
}
|
||||
|
||||
; read the filename
|
||||
repeat {
|
||||
ubyte char = c64.CHRIN()
|
||||
if char==0
|
||||
break
|
||||
if char=='\"'
|
||||
break
|
||||
@(nameptr) = char
|
||||
nameptr++
|
||||
}
|
||||
|
||||
@(nameptr) = 0
|
||||
|
||||
while c64.CHRIN() {
|
||||
; read the rest of the entry until the end
|
||||
}
|
||||
|
||||
void c64.CHRIN() ; skip 2 bytes
|
||||
void c64.CHRIN()
|
||||
|
||||
if not list_skip_disk_name {
|
||||
if not list_pattern
|
||||
return true
|
||||
if prog8_lib.pattern_match(list_filename, list_pattern)
|
||||
return true
|
||||
}
|
||||
list_skip_disk_name = false
|
||||
}
|
||||
|
||||
close_end:
|
||||
lf_end_list()
|
||||
return false
|
||||
}
|
||||
|
||||
sub lf_end_list() {
|
||||
; -- end an iterative file listing session (close channels).
|
||||
if iteration_in_progress {
|
||||
c64.CLRCHN()
|
||||
c64.CLOSE(12)
|
||||
iteration_in_progress = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
; ----- iterative file loader functions (uses io channel 11) -----
|
||||
|
||||
sub f_open(ubyte drivenumber, uword filenameptr) -> ubyte {
|
||||
; -- open a file for iterative reading with f_read
|
||||
; note: only a single iteration loop can be active at a time!
|
||||
f_close()
|
||||
|
||||
c64.SETNAM(string.length(filenameptr), filenameptr)
|
||||
c64.SETLFS(11, drivenumber, 0)
|
||||
void c64.OPEN() ; open 11,8,0,"filename"
|
||||
if_cc {
|
||||
iteration_in_progress = true
|
||||
have_first_byte = false
|
||||
void c64.CHKIN(11) ; use #11 as input channel
|
||||
if_cc {
|
||||
first_byte = c64.CHRIN() ; read first byte to test for file not found
|
||||
if not c64.READST() {
|
||||
have_first_byte = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
f_close()
|
||||
return false
|
||||
}
|
||||
|
||||
sub f_read(uword bufferpointer, uword num_bytes) -> uword {
|
||||
; -- read from the currently open file, up to the given number of bytes.
|
||||
; returns the actual number of bytes read. (checks for End-of-file and error conditions)
|
||||
if not iteration_in_progress or not num_bytes
|
||||
return 0
|
||||
|
||||
list_blocks = 0 ; we reuse this variable for the total number of bytes read
|
||||
if have_first_byte {
|
||||
have_first_byte=false
|
||||
@(bufferpointer) = first_byte
|
||||
bufferpointer++
|
||||
list_blocks++
|
||||
num_bytes--
|
||||
}
|
||||
|
||||
void c64.CHKIN(11) ; use #11 as input channel again
|
||||
%asm {{
|
||||
lda bufferpointer
|
||||
sta _in_buffer+1
|
||||
lda bufferpointer+1
|
||||
sta _in_buffer+2
|
||||
}}
|
||||
repeat num_bytes {
|
||||
%asm {{
|
||||
jsr c64.CHRIN
|
||||
sta cx16.r5
|
||||
_in_buffer sta $ffff
|
||||
inc _in_buffer+1
|
||||
bne +
|
||||
inc _in_buffer+2
|
||||
+ inc list_blocks
|
||||
bne +
|
||||
inc list_blocks+1
|
||||
+
|
||||
}}
|
||||
|
||||
if cx16.r5==$0d { ; chance on I/o error status?
|
||||
first_byte = c64.READST()
|
||||
if first_byte & $40
|
||||
f_close() ; end of file, close it
|
||||
if first_byte
|
||||
return list_blocks
|
||||
}
|
||||
}
|
||||
return list_blocks
|
||||
}
|
||||
|
||||
sub f_read_all(uword bufferpointer) -> uword {
|
||||
; -- read the full contents of the file, returns number of bytes read.
|
||||
if not iteration_in_progress
|
||||
return 0
|
||||
|
||||
list_blocks = 0 ; we reuse this variable for the total number of bytes read
|
||||
if have_first_byte {
|
||||
have_first_byte=false
|
||||
@(bufferpointer) = first_byte
|
||||
bufferpointer++
|
||||
list_blocks++
|
||||
}
|
||||
|
||||
while not c64.READST() {
|
||||
list_blocks += f_read(bufferpointer, 256)
|
||||
bufferpointer += 256
|
||||
}
|
||||
return list_blocks
|
||||
}
|
||||
|
||||
asmsub f_readline(uword bufptr @AY) clobbers(X) -> ubyte @Y {
|
||||
; Routine to read text lines from a text file. Lines must be less than 255 characters.
|
||||
; Reads characters from the input file UNTIL a newline or return character (or EOF).
|
||||
; The line read will be 0-terminated in the buffer (and not contain the end of line character).
|
||||
; The length of the line is returned in Y. Note that an empty line is okay and is length 0!
|
||||
; I/O error status should be checked by the caller itself via READST() routine.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldx #11
|
||||
jsr c64.CHKIN ; use channel 11 again for input
|
||||
ldy #0
|
||||
lda have_first_byte
|
||||
beq _loop
|
||||
lda #0
|
||||
sta have_first_byte
|
||||
lda first_byte
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
iny
|
||||
_loop jsr c64.CHRIN
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
beq _end
|
||||
iny
|
||||
cmp #$0a
|
||||
beq _line_end
|
||||
cmp #$0d
|
||||
bne _loop
|
||||
_line_end dey ; get rid of the trailing end-of-line char
|
||||
lda #0
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
_end rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
sub f_close() {
|
||||
; -- end an iterative file loading session (close channels).
|
||||
if iteration_in_progress {
|
||||
c64.CLRCHN()
|
||||
c64.CLOSE(11)
|
||||
iteration_in_progress = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub status(ubyte drivenumber) -> uword {
|
||||
; -- retrieve the disk drive's current status message
|
||||
uword messageptr = &filename
|
||||
c64.SETNAM(0, filename)
|
||||
c64.SETLFS(15, drivenumber, 15)
|
||||
void c64.OPEN() ; open 15,8,15
|
||||
if_cs
|
||||
@ -68,17 +348,21 @@ io_error:
|
||||
if_cs
|
||||
goto io_error
|
||||
|
||||
while not c64.READST()
|
||||
txt.chrout(c64.CHRIN())
|
||||
while not c64.READST() {
|
||||
@(messageptr) = c64.CHRIN()
|
||||
messageptr++
|
||||
}
|
||||
|
||||
io_error:
|
||||
@(messageptr) = 0
|
||||
c64.CLRCHN() ; restore default i/o devices
|
||||
c64.CLOSE(15)
|
||||
return filename
|
||||
}
|
||||
|
||||
|
||||
sub save(ubyte drivenumber, uword filenameptr, uword address, uword size) -> byte {
|
||||
c64.SETNAM(strlen(filenameptr), filenameptr)
|
||||
sub save(ubyte drivenumber, uword filenameptr, uword address, uword size) -> ubyte {
|
||||
c64.SETNAM(string.length(filenameptr), filenameptr)
|
||||
c64.SETLFS(1, drivenumber, 0)
|
||||
uword end_address = address + size
|
||||
|
||||
@ -97,14 +381,18 @@ io_error:
|
||||
plp
|
||||
}}
|
||||
|
||||
first_byte = 0 ; result var reuse
|
||||
if_cc
|
||||
return c64.READST()==0
|
||||
first_byte = c64.READST()==0
|
||||
|
||||
return false
|
||||
c64.CLRCHN()
|
||||
c64.CLOSE(1)
|
||||
|
||||
return first_byte
|
||||
}
|
||||
|
||||
sub load(ubyte drivenumber, uword filenameptr, uword address_override) -> uword {
|
||||
c64.SETNAM(strlen(filenameptr), filenameptr)
|
||||
c64.SETNAM(string.length(filenameptr), filenameptr)
|
||||
ubyte secondary = 1
|
||||
uword end_of_load = 0
|
||||
if address_override
|
||||
@ -122,6 +410,9 @@ io_error:
|
||||
+ ldx P8ZP_SCRATCH_REG
|
||||
}}
|
||||
|
||||
c64.CLRCHN()
|
||||
c64.CLOSE(1)
|
||||
|
||||
if end_of_load
|
||||
return end_of_load - address_override
|
||||
|
||||
@ -133,10 +424,9 @@ io_error:
|
||||
|
||||
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)
|
||||
ubyte flen = string.copy(filenameptr, &filename+2)
|
||||
c64.SETNAM(flen+2, filename)
|
||||
c64.SETLFS(1, drivenumber, 15)
|
||||
void c64.OPEN()
|
||||
@ -146,14 +436,11 @@ io_error:
|
||||
|
||||
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)
|
||||
ubyte fis_ix = flen_new+2 ; TODO is temp var for array indexing
|
||||
filename[fis_ix] = '='
|
||||
memcopy(oldfileptr, &filename+3+flen_new, flen_old+1)
|
||||
ubyte flen_new = string.copy(newfileptr, &filename+2)
|
||||
filename[flen_new+2] = '='
|
||||
ubyte flen_old = string.copy(oldfileptr, &filename+3+flen_new)
|
||||
c64.SETNAM(3+flen_new+flen_old, filename)
|
||||
c64.SETLFS(1, drivenumber, 15)
|
||||
void c64.OPEN()
|
||||
|
@ -1,11 +1,8 @@
|
||||
; Prog8 internal Math library routines - always included by the compiler
|
||||
; Internal Math library routines - always included by the compiler
|
||||
; Generic machine independent 6502 code.
|
||||
;
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
|
||||
; some more interesting routines can be found here:
|
||||
; http://6502org.wikidot.com/software-math
|
||||
; http://codebase64.org/doku.php?id=base:6502_6510_maths
|
||||
@ -774,6 +771,31 @@ stack_mul_word_100 .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
stack_mul_word_320 .proc
|
||||
; stackW = stackLo * 256 + stackLo * 64 (stackHi doesn't matter)
|
||||
ldy P8ESTACK_LO+1,x
|
||||
lda #0
|
||||
sta P8ESTACK_HI+1,x
|
||||
tya
|
||||
asl a
|
||||
rol P8ESTACK_HI+1,x
|
||||
asl a
|
||||
rol P8ESTACK_HI+1,x
|
||||
asl a
|
||||
rol P8ESTACK_HI+1,x
|
||||
asl a
|
||||
rol P8ESTACK_HI+1,x
|
||||
asl a
|
||||
rol P8ESTACK_HI+1,x
|
||||
asl a
|
||||
rol P8ESTACK_HI+1,x
|
||||
sta P8ESTACK_LO+1,x
|
||||
tya
|
||||
clc
|
||||
adc P8ESTACK_HI+1,x
|
||||
sta P8ESTACK_HI+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
; ----------- optimized multiplications (in-place A (byte) and ?? (word)) : ---------
|
||||
mul_byte_3 .proc
|
||||
@ -1178,8 +1200,6 @@ mul_word_40 .proc
|
||||
rol a
|
||||
asl P8ZP_SCRATCH_W1
|
||||
rol a
|
||||
asl P8ZP_SCRATCH_W1
|
||||
rol a
|
||||
tay
|
||||
lda P8ZP_SCRATCH_W1
|
||||
rts
|
||||
@ -1241,48 +1261,35 @@ mul_word_100 .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
mul_word_320 .proc
|
||||
; AY = A * 256 + A * 64 (msb doesn't matter)
|
||||
sta P8ZP_SCRATCH_B1
|
||||
ldy #0
|
||||
sty P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
pha
|
||||
clc
|
||||
lda P8ZP_SCRATCH_B1
|
||||
adc P8ZP_SCRATCH_REG
|
||||
tay
|
||||
pla
|
||||
rts
|
||||
.pend
|
||||
|
||||
; ----------- end optimized multiplications -----------
|
||||
|
||||
|
||||
sign_b .proc
|
||||
lda P8ESTACK_LO+1,x
|
||||
beq _sign_zero
|
||||
bmi _sign_neg
|
||||
_sign_pos lda #1
|
||||
sta P8ESTACK_LO+1,x
|
||||
rts
|
||||
_sign_neg lda #-1
|
||||
_sign_zero sta P8ESTACK_LO+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
sign_ub .proc
|
||||
lda P8ESTACK_LO+1,x
|
||||
beq sign_b._sign_zero
|
||||
bne sign_b._sign_pos
|
||||
.pend
|
||||
|
||||
sign_w .proc
|
||||
lda P8ESTACK_HI+1,x
|
||||
bmi sign_b._sign_neg
|
||||
beq sign_ub
|
||||
bne sign_b._sign_pos
|
||||
.pend
|
||||
|
||||
sign_uw .proc
|
||||
lda P8ESTACK_HI+1,x
|
||||
beq _sign_possibly_zero
|
||||
_sign_pos lda #1
|
||||
sta P8ESTACK_LO+1,x
|
||||
rts
|
||||
_sign_possibly_zero lda P8ESTACK_LO+1,x
|
||||
bne _sign_pos
|
||||
sta P8ESTACK_LO+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
|
||||
; bit shifts.
|
||||
; anything below 3 is done inline. anything above 7 is done via other optimizations.
|
||||
|
||||
@ -1340,6 +1347,33 @@ shift_left_w_3 .proc
|
||||
jmp shift_left_w_7._shift3
|
||||
.pend
|
||||
|
||||
|
||||
shift_left_w .proc
|
||||
; -- variable number of shifts left
|
||||
inx
|
||||
ldy P8ESTACK_LO,x
|
||||
bne _shift
|
||||
rts
|
||||
_shift asl P8ESTACK_LO+1,x
|
||||
rol P8ESTACK_HI+1,x
|
||||
dey
|
||||
bne _shift
|
||||
rts
|
||||
.pend
|
||||
|
||||
shift_right_uw .proc
|
||||
; -- uword variable number of shifts right
|
||||
inx
|
||||
ldy P8ESTACK_LO,x
|
||||
bne _shift
|
||||
rts
|
||||
_shift lsr P8ESTACK_HI+1,x
|
||||
ror P8ESTACK_LO+1,x
|
||||
dey
|
||||
bne _shift
|
||||
rts
|
||||
.pend
|
||||
|
||||
shift_right_uw_7 .proc
|
||||
lda P8ESTACK_LO+1,x
|
||||
sta P8ZP_SCRATCH_B1
|
||||
@ -1470,6 +1504,21 @@ shift_right_w_3 .proc
|
||||
.pend
|
||||
|
||||
|
||||
shift_right_w .proc
|
||||
; -- signed word variable number of shifts right
|
||||
inx
|
||||
ldy P8ESTACK_LO,x
|
||||
bne _shift
|
||||
rts
|
||||
_shift lda P8ESTACK_HI+1,x
|
||||
asl a
|
||||
ror P8ESTACK_HI+1,x
|
||||
ror P8ESTACK_LO+1,x
|
||||
dey
|
||||
bne _shift
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
; support for bit shifting that is too large to be unrolled:
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
; Prog8 internal Math library routines - always included by the compiler
|
||||
; Internal Math library routines - always included by the compiler
|
||||
;
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
math {
|
||||
%asminclude "library:math.asm", ""
|
||||
|
1080
compiler/res/prog8lib/prog8_funcs.asm
Normal file
1080
compiler/res/prog8lib/prog8_funcs.asm
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,9 +1,84 @@
|
||||
; Prog8 internal library routines - always included by the compiler
|
||||
; Internal library routines - always included by the compiler
|
||||
;
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
prog8_lib {
|
||||
%asminclude "library:prog8_lib.asm", ""
|
||||
%asminclude "library:prog8_funcs.asm", ""
|
||||
|
||||
uword @zp retval_interm_uw ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
|
||||
word @zp retval_interm_w ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
|
||||
ubyte @zp retval_interm_ub ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
|
||||
byte @zp retval_interm_b ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
|
||||
|
||||
asmsub pattern_match(str string @AY, str pattern @R0) clobbers(Y) -> ubyte @A {
|
||||
%asm {{
|
||||
; pattern matching of a string.
|
||||
; Input: cx16.r0: A NUL-terminated, <255-length pattern
|
||||
; AY: A NUL-terminated, <255-length string
|
||||
;
|
||||
; Output: A = 1 if the string matches the pattern, A = 0 if not.
|
||||
;
|
||||
; Notes: Clobbers A, X, Y. Each * in the pattern uses 4 bytes of stack.
|
||||
;
|
||||
; see http://6502.org/source/strings/patmatch.htm
|
||||
|
||||
str = P8ZP_SCRATCH_W1
|
||||
|
||||
stx P8ZP_SCRATCH_REG
|
||||
sta str
|
||||
sty str+1
|
||||
lda cx16.r0
|
||||
sta modify_pattern1+1
|
||||
sta modify_pattern2+1
|
||||
lda cx16.r0+1
|
||||
sta modify_pattern1+2
|
||||
sta modify_pattern2+2
|
||||
jsr _match
|
||||
lda #0
|
||||
adc #0
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
|
||||
|
||||
_match
|
||||
ldx #$00 ; x is an index in the pattern
|
||||
ldy #$ff ; y is an index in the string
|
||||
modify_pattern1
|
||||
next lda $ffff,x ; look at next pattern character MODIFIED
|
||||
cmp #'*' ; is it a star?
|
||||
beq star ; yes, do the complicated stuff
|
||||
iny ; no, let's look at the string
|
||||
cmp #'?' ; is the pattern caracter a ques?
|
||||
bne reg ; no, it's a regular character
|
||||
lda (str),y ; yes, so it will match anything
|
||||
beq fail ; except the end of string
|
||||
reg cmp (str),y ; are both characters the same?
|
||||
bne fail ; no, so no match
|
||||
inx ; yes, keep checking
|
||||
cmp #0 ; are we at end of string?
|
||||
bne next ; not yet, loop
|
||||
found rts ; success, return with c=1
|
||||
|
||||
star inx ; skip star in pattern
|
||||
modify_pattern2
|
||||
cmp $ffff,x ; string of stars equals one star MODIFIED
|
||||
beq star ; so skip them also
|
||||
stloop txa ; we first try to match with * = ""
|
||||
pha ; and grow it by 1 character every
|
||||
tya ; time we loop
|
||||
pha ; save x and y on stack
|
||||
jsr next ; recursive call
|
||||
pla ; restore x and y
|
||||
tay
|
||||
pla
|
||||
tax
|
||||
bcs found ; we found a match, return with c=1
|
||||
iny ; no match yet, try to grow * string
|
||||
lda (str),y ; are we at the end of string?
|
||||
bne stloop ; not yet, add a character
|
||||
fail clc ; yes, no match found, return with c=0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
235
compiler/res/prog8lib/string.p8
Normal file
235
compiler/res/prog8lib/string.p8
Normal file
@ -0,0 +1,235 @@
|
||||
; 0-terminated string manipulation routines.
|
||||
;
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
|
||||
|
||||
string {
|
||||
|
||||
asmsub length(uword string @AY) clobbers(A) -> ubyte @Y {
|
||||
; Returns the number of bytes in the string.
|
||||
; This value is determined during runtime and counts upto the first terminating 0 byte in the string,
|
||||
; regardless of the size of the string during compilation time. Don’t confuse this with len and sizeof!
|
||||
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
beq +
|
||||
iny
|
||||
bne -
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub left(uword source @R0, ubyte length @A, uword target @R1) clobbers(A, Y) {
|
||||
; Copies the left side of the source string of the given length to target string.
|
||||
; It is assumed the target string buffer is large enough to contain the result.
|
||||
; Also, you have to make sure yourself that length is smaller or equal to the length of the source string.
|
||||
; Modifies in-place, doesn’t return a value (so can’t be used in an expression).
|
||||
%asm {{
|
||||
; need to copy the the cx16 virtual registers to zeropage to be compatible with C64...
|
||||
ldy cx16.r0
|
||||
sty P8ZP_SCRATCH_W1
|
||||
ldy cx16.r0+1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy cx16.r1
|
||||
sty P8ZP_SCRATCH_W2
|
||||
ldy cx16.r1+1
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
tay
|
||||
lda #0
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
cpy #0
|
||||
bne _loop
|
||||
rts
|
||||
_loop dey
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
cpy #0
|
||||
bne _loop
|
||||
+ rts
|
||||
}}
|
||||
; asmgen.out(" jsr prog8_lib.func_leftstr")
|
||||
}
|
||||
|
||||
asmsub right(uword source @R0, ubyte length @A, uword target @R1) clobbers(A,Y) {
|
||||
; Copies the right side of the source string of the given length to target string.
|
||||
; It is assumed the target string buffer is large enough to contain the result.
|
||||
; Also, you have to make sure yourself that length is smaller or equal to the length of the source string.
|
||||
; Modifies in-place, doesn’t return a value (so can’t be used in an expression).
|
||||
%asm {{
|
||||
; need to copy the the cx16 virtual registers to zeropage to be compatible with C64...
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jsr string.length
|
||||
tya
|
||||
sec
|
||||
sbc P8ZP_SCRATCH_B1
|
||||
clc
|
||||
adc cx16.r0
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda cx16.r0+1
|
||||
adc #0
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
ldy cx16.r1
|
||||
sty P8ZP_SCRATCH_W2
|
||||
ldy cx16.r1+1
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy P8ZP_SCRATCH_B1
|
||||
lda #0
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
cpy #0
|
||||
bne _loop
|
||||
rts
|
||||
_loop dey
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
cpy #0
|
||||
bne _loop
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub slice(uword source @R0, ubyte start @A, ubyte length @Y, uword target @R1) clobbers(A, Y) {
|
||||
; Copies a segment from the source string, starting at the given index,
|
||||
; and of the given length to target string.
|
||||
; It is assumed the target string buffer is large enough to contain the result.
|
||||
; Also, you have to make sure yourself that start and length are within bounds of the strings.
|
||||
; Modifies in-place, doesn’t return a value (so can’t be used in an expression).
|
||||
%asm {{
|
||||
; need to copy the the cx16 virtual registers to zeropage to be compatible with C64...
|
||||
; substr(source, target, start, length)
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda cx16.r0
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda cx16.r0+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda cx16.r1
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda cx16.r1+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
|
||||
; adjust src location
|
||||
clc
|
||||
lda P8ZP_SCRATCH_W1
|
||||
adc P8ZP_SCRATCH_B1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
bcc +
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
+ lda #0
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
beq _startloop
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
_startloop dey
|
||||
cpy #$ff
|
||||
bne -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub find(uword string @R0, ubyte character @A) -> uword @AY {
|
||||
; Locates the first position of the given character in the string,
|
||||
; returns the string starting with this character or $0000 if the character is not found.
|
||||
%asm {{
|
||||
; need to copy the the cx16 virtual registers to zeropage to be compatible with C64...
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
beq _notfound
|
||||
cmp P8ZP_SCRATCH_B1
|
||||
beq _found
|
||||
iny
|
||||
bne -
|
||||
_notfound lda #0
|
||||
ldy #0
|
||||
rts
|
||||
_found sty P8ZP_SCRATCH_B1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
lda P8ZP_SCRATCH_W1
|
||||
clc
|
||||
adc P8ZP_SCRATCH_B1
|
||||
bcc +
|
||||
iny
|
||||
+ rts
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub copy(uword source @R0, uword target @AY) clobbers(A) -> ubyte @Y {
|
||||
; Copy a string to another, overwriting that one.
|
||||
; Returns the length of the string that was copied.
|
||||
; Often you don’t have to call this explicitly and can just write string1 = string2
|
||||
; but this function is useful if you’re dealing with addresses for instance.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jmp prog8_lib.strcpy
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub compare(uword string1 @R0, uword string2 @AY) clobbers(Y) -> byte @A {
|
||||
; Compares two strings for sorting.
|
||||
; Returns -1 (255), 0 or 1 depeding on wether string1 sorts before, equal or after string2.
|
||||
; Note that you can also directly compare strings and string values with eachother using
|
||||
; comparison operators ==, < etcetera (it will use strcmp for you under water automatically).
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jmp prog8_lib.strcmp_mem
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub lower(uword st @AY) {
|
||||
; Lowercases the petscii string in-place.
|
||||
; (for efficiency, non-letter characters > 128 will also not be left intact,
|
||||
; but regular text doesn't usually contain those characters anyway.)
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
beq _done
|
||||
and #$7f
|
||||
cmp #97
|
||||
bcc +
|
||||
cmp #123
|
||||
bcs +
|
||||
and #%11011111
|
||||
+ sta (P8ZP_SCRATCH_W1),y
|
||||
iny
|
||||
bne -
|
||||
_done rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub upper(uword st @AY) {
|
||||
; Uppercases the petscii string in-place.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
beq _done
|
||||
cmp #65
|
||||
bcc +
|
||||
cmp #91
|
||||
bcs +
|
||||
ora #%00100000
|
||||
+ sta (P8ZP_SCRATCH_W1),y
|
||||
iny
|
||||
bne -
|
||||
_done rts
|
||||
}}
|
||||
}
|
||||
}
|
50
compiler/res/prog8lib/test_stack.p8
Normal file
50
compiler/res/prog8lib/test_stack.p8
Normal file
@ -0,0 +1,50 @@
|
||||
; utility debug code to print the X (evalstack) and SP (cpu stack) registers.
|
||||
|
||||
%import textio
|
||||
|
||||
test_stack {
|
||||
|
||||
asmsub test() {
|
||||
%asm {{
|
||||
stx _saveX
|
||||
lda #13
|
||||
jsr txt.chrout
|
||||
lda #'-'
|
||||
ldy #12
|
||||
- jsr txt.chrout
|
||||
dey
|
||||
bne -
|
||||
lda #13
|
||||
jsr txt.chrout
|
||||
lda #'x'
|
||||
jsr txt.chrout
|
||||
lda #'='
|
||||
jsr txt.chrout
|
||||
lda _saveX
|
||||
jsr txt.print_ub
|
||||
lda #' '
|
||||
jsr txt.chrout
|
||||
lda #'s'
|
||||
jsr txt.chrout
|
||||
lda #'p'
|
||||
jsr txt.chrout
|
||||
lda #'='
|
||||
jsr txt.chrout
|
||||
tsx
|
||||
txa
|
||||
jsr txt.print_ub
|
||||
lda #13
|
||||
jsr txt.chrout
|
||||
lda #'-'
|
||||
ldy #12
|
||||
- jsr txt.chrout
|
||||
dey
|
||||
bne -
|
||||
lda #13
|
||||
jsr txt.chrout
|
||||
ldx _saveX
|
||||
rts
|
||||
_saveX .byte 0
|
||||
}}
|
||||
}
|
||||
}
|
@ -1 +1 @@
|
||||
4.6
|
||||
6.0
|
||||
|
@ -32,20 +32,20 @@ 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 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 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)
|
||||
val cli = ArgParser("prog8compiler")
|
||||
val startEmulator by cli.option(ArgType.Boolean, shortName="emu", description = "auto-start emulator after successful compilation")
|
||||
val outputDir by cli.option(ArgType.String, shortName = "out", description = "directory for output files instead of current directory").default(".")
|
||||
val dontWriteAssembly by cli.option(ArgType.Boolean, shortName = "noasm", description="don't create assembly code")
|
||||
val dontOptimize by cli.option(ArgType.Boolean, shortName = "noopt", description = "don't perform any optimizations")
|
||||
val watchMode by cli.option(ArgType.Boolean, shortName = "watch", description = "continuous compilation mode (watches for file changes), greatly increases compilation speed")
|
||||
val slowCodegenWarnings by cli.option(ArgType.Boolean, shortName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation")
|
||||
val compilationTarget by cli.option(ArgType.String, shortName = "target", description = "target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available").default(C64Target.name)
|
||||
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
|
||||
|
||||
try {
|
||||
cli.parse(args)
|
||||
} catch (e: Exception) {
|
||||
} catch (e: IllegalStateException) {
|
||||
System.err.println(e.message)
|
||||
exitProcess(1)
|
||||
}
|
||||
|
||||
@ -55,32 +55,45 @@ private fun compileMain(args: Array<String>) {
|
||||
exitProcess(1)
|
||||
}
|
||||
|
||||
if(watchMode && moduleFiles.size<=1) {
|
||||
if(watchMode==true) {
|
||||
val watchservice = FileSystems.getDefault().newWatchService()
|
||||
val allImportedFiles = mutableSetOf<Path>()
|
||||
|
||||
while(true) {
|
||||
val filepath = pathFrom(moduleFiles.single()).normalize()
|
||||
println("Continuous watch mode active. Main module: $filepath")
|
||||
println("Continuous watch mode active. Modules: $moduleFiles")
|
||||
val results = mutableListOf<CompilationResult>()
|
||||
for(filepathRaw in moduleFiles) {
|
||||
val filepath = pathFrom(filepathRaw).normalize()
|
||||
val compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, outputPath)
|
||||
results.add(compilationResult)
|
||||
}
|
||||
|
||||
try {
|
||||
val compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, slowCodegenWarnings, compilationTarget, 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 allNewlyImportedFiles = results.flatMap { it.importedFiles }
|
||||
allImportedFiles.addAll(allNewlyImportedFiles)
|
||||
|
||||
println("Imported files (now watching:)")
|
||||
for (importedFile in allImportedFiles) {
|
||||
print(" ")
|
||||
println(importedFile)
|
||||
val watchDir = importedFile.parent ?: Path.of(".")
|
||||
watchDir.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY)
|
||||
}
|
||||
println("[${LocalDateTime.now().withNano(0)}] Waiting for file changes.")
|
||||
|
||||
var recompile=false
|
||||
while(!recompile) {
|
||||
val event = watchservice.take()
|
||||
for(changed in event.pollEvents()) {
|
||||
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 {
|
||||
@ -88,7 +101,7 @@ private fun compileMain(args: Array<String>) {
|
||||
val filepath = pathFrom(filepathRaw).normalize()
|
||||
val compilationResult: CompilationResult
|
||||
try {
|
||||
compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, slowCodegenWarnings, compilationTarget, outputPath)
|
||||
compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, outputPath)
|
||||
if(!compilationResult.success)
|
||||
exitProcess(1)
|
||||
} catch (x: ParsingFailedError) {
|
||||
@ -97,10 +110,10 @@ private fun compileMain(args: Array<String>) {
|
||||
exitProcess(1)
|
||||
}
|
||||
|
||||
if (startEmulator) {
|
||||
if (startEmulator==true) {
|
||||
if (compilationResult.programName.isEmpty())
|
||||
println("\nCan't start emulator because no program was assembled.")
|
||||
else if(startEmulator) {
|
||||
else {
|
||||
CompilationTarget.instance.machine.launchEmulator(compilationResult.programName)
|
||||
}
|
||||
}
|
||||
|
@ -143,7 +143,6 @@ 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 -> "?????"
|
||||
@ -173,8 +172,19 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
output(") ")
|
||||
}
|
||||
if(subroutine.returntypes.any()) {
|
||||
val rt = subroutine.returntypes.single()
|
||||
output("-> ${datatypeString(rt)} ")
|
||||
if(subroutine.asmReturnvaluesRegisters.isNotEmpty()) {
|
||||
val rts = subroutine.returntypes.zip(subroutine.asmReturnvaluesRegisters).joinToString(", ") {
|
||||
val dtstr = datatypeString(it.first)
|
||||
if(it.second.registerOrPair!=null)
|
||||
"$dtstr @${it.second.registerOrPair}"
|
||||
else
|
||||
"$dtstr @${it.second.statusflag}"
|
||||
}
|
||||
output("-> $rts ")
|
||||
} else {
|
||||
val rts = subroutine.returntypes.joinToString(", ") { datatypeString(it) }
|
||||
output("-> $rts ")
|
||||
}
|
||||
}
|
||||
if(subroutine.asmAddress!=null)
|
||||
outputln("= ${subroutine.asmAddress.toHex()}")
|
||||
|
@ -57,19 +57,11 @@ interface IFunctionCall {
|
||||
}
|
||||
|
||||
|
||||
class AsmGenInfo {
|
||||
var usedAutoArrayIndexerForStatements = mutableMapOf<String, MutableSet<Statement>>()
|
||||
var usedRegsaveA = false
|
||||
var usedRegsaveX = false
|
||||
var usedRegsaveY = false
|
||||
}
|
||||
|
||||
interface INameScope {
|
||||
val name: String
|
||||
val position: Position
|
||||
val statements: MutableList<Statement>
|
||||
val parent: Node
|
||||
val asmGenInfo: AsmGenInfo
|
||||
|
||||
fun linkParents(parent: Node)
|
||||
|
||||
@ -183,6 +175,7 @@ interface INameScope {
|
||||
}
|
||||
}
|
||||
|
||||
fun containsDefinedVariables() = statements.any { it is VarDecl && (it !is ParameterVarDecl) }
|
||||
fun containsCodeOrVars() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm"}
|
||||
fun containsNoCodeNorVars() = !containsCodeOrVars()
|
||||
|
||||
@ -224,6 +217,14 @@ interface INameScope {
|
||||
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)
|
||||
@ -288,7 +289,6 @@ class Module(override val name: String,
|
||||
|
||||
override lateinit var parent: Node
|
||||
lateinit var program: Program
|
||||
override val asmGenInfo = AsmGenInfo()
|
||||
val importedBy = mutableListOf<Module>()
|
||||
val imports = mutableSetOf<Module>()
|
||||
|
||||
@ -322,7 +322,6 @@ class GlobalNamespace(val modules: List<Module>): Node, INameScope {
|
||||
override val position = Position("<<<global>>>", 0, 0, 0)
|
||||
override val statements = mutableListOf<Statement>() // not used
|
||||
override var parent: Node = ParentSentinel
|
||||
override val asmGenInfo = AsmGenInfo()
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
modules.forEach { it.linkParents(this) }
|
||||
@ -375,7 +374,6 @@ object BuiltinFunctionScopePlaceholder : INameScope {
|
||||
override val position = Position("<<placeholder>>", 0, 0, 0)
|
||||
override var statements = mutableListOf<Statement>()
|
||||
override var parent: Node = ParentSentinel
|
||||
override val asmGenInfo = AsmGenInfo()
|
||||
override fun linkParents(parent: Node) {}
|
||||
}
|
||||
|
||||
|
@ -225,11 +225,12 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
|
||||
}
|
||||
|
||||
private fun prog8Parser.AsmsubroutineContext.toAst(): Subroutine {
|
||||
val inline = this.inline()!=null
|
||||
val subdecl = asmsub_decl().toAst()
|
||||
val statements = statement_block()?.toAst() ?: mutableListOf()
|
||||
return Subroutine(subdecl.name, subdecl.parameters, subdecl.returntypes,
|
||||
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
|
||||
subdecl.asmClobbers, null, true, statements, toPosition())
|
||||
subdecl.asmClobbers, null, true, inline, statements, toPosition())
|
||||
}
|
||||
|
||||
private fun prog8Parser.RomsubroutineContext.toAst(): Subroutine {
|
||||
@ -237,7 +238,8 @@ private fun prog8Parser.RomsubroutineContext.toAst(): Subroutine {
|
||||
val address = integerliteral().toAst().number.toInt()
|
||||
return Subroutine(subdecl.name, subdecl.parameters, subdecl.returntypes,
|
||||
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
|
||||
subdecl.asmClobbers, address, true, mutableListOf(), toPosition())
|
||||
subdecl.asmClobbers, address, true, inline = false, statements = mutableListOf(), position = toPosition()
|
||||
)
|
||||
}
|
||||
|
||||
private class AsmsubDecl(val name: String,
|
||||
@ -254,8 +256,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, false) }
|
||||
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)
|
||||
}
|
||||
|
||||
@ -263,24 +265,22 @@ private class AsmSubroutineParameter(name: String,
|
||||
type: DataType,
|
||||
val registerOrPair: RegisterOrPair?,
|
||||
val statusflag: Statusflag?,
|
||||
// TODO implement: 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.Asmsub_returnsContext.toAst(): List<AsmSubroutineReturn>
|
||||
= asmsub_return().map {
|
||||
val register = it.identifier()?.toAst()
|
||||
val register = it.register().text
|
||||
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)
|
||||
when (register) {
|
||||
in RegisterOrPair.names -> registerorpair = RegisterOrPair.valueOf(register)
|
||||
in Statusflag.names -> statusregister = Statusflag.valueOf(register)
|
||||
else -> throw FatalAstException("invalid register or status flag in $it")
|
||||
}
|
||||
}
|
||||
@ -288,21 +288,21 @@ private fun prog8Parser.Asmsub_returnsContext.toAst(): List<AsmSubroutineReturn>
|
||||
it.datatype().toAst(),
|
||||
registerorpair,
|
||||
statusregister,
|
||||
!it.stack?.text.isNullOrEmpty(), toPosition())
|
||||
toPosition())
|
||||
}
|
||||
|
||||
private fun prog8Parser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter>
|
||||
= asmsub_param().map {
|
||||
val vardecl = it.vardecl()
|
||||
val datatype = vardecl.datatype()?.toAst() ?: DataType.STRUCT
|
||||
val register = it.identifier()?.toAst()
|
||||
val register = it.register().text
|
||||
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'")
|
||||
when (register) {
|
||||
in RegisterOrPair.names -> registerorpair = RegisterOrPair.valueOf(register)
|
||||
in Statusflag.names -> statusregister = Statusflag.valueOf(register)
|
||||
else -> throw FatalAstException("invalid register or status flag '$register'")
|
||||
}
|
||||
}
|
||||
AsmSubroutineParameter(vardecl.varname.text, datatype, registerorpair, statusregister, toPosition())
|
||||
@ -342,15 +342,14 @@ private fun prog8Parser.LabeldefContext.toAst(): Statement =
|
||||
Label(children[0].text, toPosition())
|
||||
|
||||
private fun prog8Parser.SubroutineContext.toAst() : Subroutine {
|
||||
// non-asm subroutine
|
||||
val inline = inline()!=null
|
||||
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(),
|
||||
inline,
|
||||
toPosition())
|
||||
}
|
||||
|
||||
@ -376,7 +375,7 @@ private fun prog8Parser.Assign_targetContext.toAst() : AssignTarget {
|
||||
}
|
||||
|
||||
private fun prog8Parser.ClobberContext.toAst() : Set<CpuRegister> {
|
||||
val names = this.identifier().map { it.toAst().nameInSource.single() }
|
||||
val names = this.cpuregister().map { it.text }
|
||||
return names.map { CpuRegister.valueOf(it) }.toSet()
|
||||
}
|
||||
|
||||
@ -628,7 +627,7 @@ private fun prog8Parser.When_choiceContext.toAst(): WhenChoice {
|
||||
if(stmt!=null)
|
||||
stmtBlock.add(stmt)
|
||||
val scope = AnonymousScope(stmtBlock, toPosition())
|
||||
return WhenChoice(values, scope, toPosition())
|
||||
return WhenChoice(values?.toMutableList(), scope, toPosition())
|
||||
}
|
||||
|
||||
private fun prog8Parser.VardeclContext.toAst(): VarDecl {
|
||||
|
@ -35,8 +35,9 @@ enum class DataType {
|
||||
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 {
|
||||
@ -79,7 +80,12 @@ enum class RegisterOrPair {
|
||||
Y,
|
||||
AX,
|
||||
AY,
|
||||
XY;
|
||||
XY,
|
||||
FAC1,
|
||||
FAC2,
|
||||
// cx16 virtual registers:
|
||||
R0, R1, R2, R3, R4, R5, R6, R7,
|
||||
R8, R9, R10, R11, R12, R13, R14, R15;
|
||||
|
||||
companion object {
|
||||
val names by lazy { values().map { it.toString()} }
|
||||
@ -89,9 +95,9 @@ enum class RegisterOrPair {
|
||||
|
||||
enum class Statusflag {
|
||||
Pc,
|
||||
Pz,
|
||||
Pz, // don't use
|
||||
Pv,
|
||||
Pn;
|
||||
Pn; // don't use
|
||||
|
||||
companion object {
|
||||
val names by lazy { values().map { it.toString()} }
|
||||
@ -124,6 +130,7 @@ val WordDatatypes = setOf(DataType.UWORD, DataType.WORD)
|
||||
val IntegerDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD)
|
||||
val NumericDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT)
|
||||
val ArrayDatatypes = setOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F)
|
||||
val StringlyDatatypes = setOf(DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B, DataType.UWORD)
|
||||
val IterableDatatypes = setOf(
|
||||
DataType.STR,
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B,
|
||||
@ -145,6 +152,11 @@ val ElementArrayTypes = mapOf(
|
||||
DataType.UWORD to DataType.ARRAY_UW,
|
||||
DataType.FLOAT to DataType.ARRAY_F
|
||||
)
|
||||
val Cx16VirtualRegisters = listOf(RegisterOrPair.R0, RegisterOrPair.R1, RegisterOrPair.R2, RegisterOrPair.R3,
|
||||
RegisterOrPair.R4, RegisterOrPair.R5, RegisterOrPair.R6, RegisterOrPair.R7,
|
||||
RegisterOrPair.R8, RegisterOrPair.R9, RegisterOrPair.R10, RegisterOrPair.R11,
|
||||
RegisterOrPair.R12, RegisterOrPair.R13, RegisterOrPair.R14, RegisterOrPair.R15)
|
||||
|
||||
|
||||
// find the parent node of a specific type or interface
|
||||
// (useful to figure out in what namespace/block something is defined, etc)
|
||||
|
@ -17,7 +17,7 @@ import kotlin.math.abs
|
||||
|
||||
val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "==", "!=")
|
||||
val comparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
|
||||
val augmentAssignmentOperators = setOf("+", "-", "/", "*", "**", "&", "|", "^", "<<", ">>")
|
||||
val augmentAssignmentOperators = setOf("+", "-", "/", "*", "**", "&", "|", "^", "<<", ">>", "%", "and", "or", "xor")
|
||||
|
||||
sealed class Expression: Node {
|
||||
abstract fun constValue(program: Program): NumericLiteralValue?
|
||||
@ -779,6 +779,9 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
|
||||
// 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)
|
||||
@ -821,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)
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -11,6 +11,7 @@ import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8.functions.BuiltinFunctions
|
||||
import prog8.functions.builtinFunctionReturnType
|
||||
import java.io.File
|
||||
|
||||
internal class AstChecker(private val program: Program,
|
||||
@ -34,24 +35,6 @@ internal class AstChecker(private val program: Program,
|
||||
if (startSub.parameters.isNotEmpty() || startSub.returntypes.isNotEmpty())
|
||||
errors.err("program entrypoint subroutine can't have parameters and/or return values", startSub.position)
|
||||
}
|
||||
|
||||
// the main module cannot contain 'regular' statements (they will never be executed!)
|
||||
for (statement in mainBlock.statements) {
|
||||
val ok = when (statement) {
|
||||
is Block -> true
|
||||
is Directive -> true
|
||||
is Label -> true
|
||||
is VarDecl -> true
|
||||
is InlineAssembly -> true
|
||||
is INameScope -> true
|
||||
is NopStatement -> true
|
||||
else -> false
|
||||
}
|
||||
if (!ok) {
|
||||
errors.err("main block contains regular statements, this is not allowed (they'll never get executed). Use subroutines.", statement.position)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// there can be an optional single 'irq' block with a 'irq' subroutine in it,
|
||||
@ -185,6 +168,23 @@ internal class AstChecker(private val program: Program,
|
||||
errors.err("block memory address must be valid integer 0..\$ffff", block.position)
|
||||
}
|
||||
|
||||
for (statement in block.statements) {
|
||||
val ok = when (statement) {
|
||||
is Block,
|
||||
is Directive,
|
||||
is Label,
|
||||
is VarDecl,
|
||||
is InlineAssembly,
|
||||
is INameScope,
|
||||
is NopStatement -> true
|
||||
else -> false
|
||||
}
|
||||
if (!ok) {
|
||||
errors.err("statement occurs in a block, where it will never be executed. Use it in a subroutine instead.", statement.position)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
super.visit(block)
|
||||
}
|
||||
|
||||
@ -202,10 +202,20 @@ internal class AstChecker(private val program: Program,
|
||||
if(subroutine.name in BuiltinFunctions)
|
||||
err("cannot redefine a built-in function")
|
||||
|
||||
if(subroutine.parameters.size>16)
|
||||
err("subroutines are limited to 16 parameters")
|
||||
|
||||
val uniqueNames = subroutine.parameters.asSequence().map { it.name }.toSet()
|
||||
if(uniqueNames.size!=subroutine.parameters.size)
|
||||
err("parameter names must be unique")
|
||||
|
||||
if(subroutine.inline) {
|
||||
if (subroutine.containsDefinedVariables())
|
||||
err("can't inline a subroutine that defines variables")
|
||||
if (!subroutine.isAsmSubroutine && subroutine.parameters.isNotEmpty())
|
||||
err("can't inline a non-asm subroutine that has parameters")
|
||||
}
|
||||
|
||||
super.visit(subroutine)
|
||||
|
||||
// user-defined subroutines can only have zero or one return type
|
||||
@ -219,8 +229,8 @@ internal class AstChecker(private val program: Program,
|
||||
if (subroutine.amountOfRtsInAsm() == 0) {
|
||||
if (subroutine.returntypes.isNotEmpty()) {
|
||||
// for asm subroutines with an address, no statement check is possible.
|
||||
if (subroutine.asmAddress == null)
|
||||
err("subroutine has result value(s) and thus must have at least one 'return' or 'goto' in it (or 'rts' / 'jmp' in case of %asm)")
|
||||
if (subroutine.asmAddress == null && !subroutine.inline)
|
||||
err("non-inline subroutine has result value(s) and thus must have at least one 'return' or 'goto' in it (or 'rts' / 'jmp' in case of %asm)")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -250,19 +260,19 @@ 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")
|
||||
}
|
||||
}
|
||||
|
||||
@ -288,6 +298,23 @@ internal class AstChecker(private val program: Program,
|
||||
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 */ }
|
||||
RegisterOrPair.R0,
|
||||
RegisterOrPair.R1,
|
||||
RegisterOrPair.R2,
|
||||
RegisterOrPair.R3,
|
||||
RegisterOrPair.R4,
|
||||
RegisterOrPair.R5,
|
||||
RegisterOrPair.R6,
|
||||
RegisterOrPair.R7,
|
||||
RegisterOrPair.R8,
|
||||
RegisterOrPair.R9,
|
||||
RegisterOrPair.R10,
|
||||
RegisterOrPair.R11,
|
||||
RegisterOrPair.R12,
|
||||
RegisterOrPair.R13,
|
||||
RegisterOrPair.R14,
|
||||
RegisterOrPair.R15 -> { /* no sensible way to count this */ }
|
||||
null ->
|
||||
if(p.statusflag!=null)
|
||||
statusflagCounts[p.statusflag] = statusflagCounts.getValue(p.statusflag) + 1
|
||||
@ -340,15 +367,22 @@ internal class AstChecker(private val program: Program,
|
||||
super.visit(whileLoop)
|
||||
}
|
||||
|
||||
override fun visit(repeatLoop: RepeatLoop) {
|
||||
val iterations = repeatLoop.iterations?.constValue(program)
|
||||
if(iterations != null && iterations.number.toInt() > 65535)
|
||||
errors.err("repeat cannot go over 65535 iterations", iterations.position)
|
||||
super.visit(repeatLoop)
|
||||
}
|
||||
|
||||
override fun visit(assignment: Assignment) {
|
||||
if(assignment.value is FunctionCall) {
|
||||
val stmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace)
|
||||
if (stmt is Subroutine) {
|
||||
val idt = assignment.target.inferType(program, assignment)
|
||||
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()!=idt.typeOrElse(DataType.BYTE)) {
|
||||
if(stmt.returntypes.size <= 1 && stmt.returntypes.single() isNotAssignableTo idt.typeOrElse(DataType.BYTE)) {
|
||||
errors.err("return type mismatch", assignment.value.position)
|
||||
}
|
||||
}
|
||||
@ -378,12 +412,12 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
val targetDt = assignment.target.inferType(program, assignment)
|
||||
val targetDt = assignment.target.inferType(program)
|
||||
val valueDt = assignment.value.inferType(program)
|
||||
if(valueDt.isKnown && valueDt != targetDt) {
|
||||
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
|
||||
else if(!(valueDt.istype(DataType.STR) && targetDt.istype(DataType.UWORD)))
|
||||
errors.err("value's type doesn't match target", assignment.value.position)
|
||||
}
|
||||
|
||||
@ -433,7 +467,7 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
if (assignment is Assignment) {
|
||||
|
||||
val targetDatatype = assignTarget.inferType(program, assignment)
|
||||
val targetDatatype = assignTarget.inferType(program)
|
||||
if (targetDatatype.isKnown) {
|
||||
val constVal = assignment.value.constValue(program)
|
||||
if (constVal != null) {
|
||||
@ -454,15 +488,12 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
override fun visit(addressOf: AddressOf) {
|
||||
val variable=addressOf.identifier.targetVarDecl(program.namespace)
|
||||
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.type!=VarDeclType.MEMORY
|
||||
&& variable.struct == null
|
||||
&& variable.datatype != DataType.STR && variable.datatype!=DataType.STRUCT)
|
||||
if(variable!=null
|
||||
&& 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)
|
||||
}
|
||||
|
||||
@ -584,7 +615,7 @@ internal class AstChecker(private val program: Program,
|
||||
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)
|
||||
err("value of memory mapped variable can only be a fixed number, perhaps you meant to use an address pointer type instead?", decl.value?.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -593,8 +624,8 @@ internal class AstChecker(private val program: Program,
|
||||
if(declValue!=null && decl.type==VarDeclType.VAR) {
|
||||
if(decl.datatype==DataType.STRUCT) {
|
||||
val valueIdt = declValue.inferType(program)
|
||||
if(valueIdt.isUnknown)
|
||||
throw AstException("invalid value type")
|
||||
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)
|
||||
@ -603,23 +634,28 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
// array length limits
|
||||
// array length limits and constant lenghts
|
||||
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")
|
||||
val length = decl.arraysize!!.constIndex()
|
||||
if(length==null)
|
||||
err("array length must be a constant")
|
||||
else {
|
||||
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 -> {
|
||||
}
|
||||
}
|
||||
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 -> {}
|
||||
}
|
||||
}
|
||||
|
||||
@ -769,7 +805,11 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(expr: PrefixExpression) {
|
||||
val dt = expr.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
val idt = expr.inferType(program)
|
||||
if(!idt.isKnown)
|
||||
return // any error should be reported elsewhere
|
||||
|
||||
val dt = idt.typeOrElse(DataType.STRUCT)
|
||||
if(expr.operator=="-") {
|
||||
if (dt != DataType.BYTE && dt != DataType.WORD && dt != DataType.FLOAT) {
|
||||
errors.err("can only take negative of a signed number type", expr.position)
|
||||
@ -839,6 +879,10 @@ internal class AstChecker(private val program: Program,
|
||||
override fun visit(typecast: TypecastExpression) {
|
||||
if(typecast.type in IterableDatatypes)
|
||||
errors.err("cannot type cast to string or array type", typecast.position)
|
||||
|
||||
if(!typecast.expression.inferType(program).isKnown)
|
||||
errors.err("this expression doesn't return a value", typecast.expression.position)
|
||||
|
||||
super.visit(typecast)
|
||||
}
|
||||
|
||||
@ -924,11 +968,19 @@ internal class AstChecker(private val program: Program,
|
||||
val targetStatement = checkFunctionOrLabelExists(functionCallStatement.target, functionCallStatement)
|
||||
if(targetStatement!=null)
|
||||
checkFunctionCall(targetStatement, functionCallStatement.args, functionCallStatement.position)
|
||||
if(!functionCallStatement.void && targetStatement is Subroutine && targetStatement.returntypes.isNotEmpty()) {
|
||||
if(targetStatement.returntypes.size==1)
|
||||
errors.warn("result value of subroutine call is discarded (use void?)", functionCallStatement.position)
|
||||
else
|
||||
errors.warn("result values of subroutine call are discarded (use void?)", functionCallStatement.position)
|
||||
if (!functionCallStatement.void) {
|
||||
// check for unused return values
|
||||
if (targetStatement is Subroutine && targetStatement.returntypes.isNotEmpty()) {
|
||||
if(targetStatement.returntypes.size==1)
|
||||
errors.warn("result value of subroutine call is discarded (use void?)", functionCallStatement.position)
|
||||
else
|
||||
errors.warn("result values of subroutine call are discarded (use void?)", functionCallStatement.position)
|
||||
}
|
||||
else if(targetStatement is BuiltinFunctionStatementPlaceholder) {
|
||||
val rt = builtinFunctionReturnType(targetStatement.name, functionCallStatement.args, program)
|
||||
if(rt.isKnown)
|
||||
errors.warn("result value of a function call is discarded (use void?)", functionCallStatement.position)
|
||||
}
|
||||
}
|
||||
|
||||
if(functionCallStatement.target.nameInSource.last() == "sort") {
|
||||
@ -981,11 +1033,9 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
} else if(target is Subroutine) {
|
||||
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.withIndex().zip(target.parameters)) {
|
||||
val argIDt = arg.first.value.inferType(program)
|
||||
for (arg in args.zip(target.parameters)) {
|
||||
val argIDt = arg.first.inferType(program)
|
||||
if (!argIDt.isKnown)
|
||||
return
|
||||
}
|
||||
@ -1218,7 +1268,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
|
||||
}
|
||||
@ -1350,9 +1400,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)
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,13 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
|
||||
else
|
||||
blocks[block.name] = block
|
||||
|
||||
if(!block.isInLibrary) {
|
||||
val libraries = program.modules.filter { it.isLibraryModule }
|
||||
val libraryBlockNames = libraries.flatMap { it.statements.filterIsInstance<Block>().map { b -> b.name } }
|
||||
if(block.name in libraryBlockNames)
|
||||
errors.err("block is already defined in an included library module", block.position)
|
||||
}
|
||||
|
||||
super.visit(block)
|
||||
}
|
||||
|
||||
@ -112,9 +119,12 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
|
||||
val labelOrVar = subroutine.getLabelOrVariable(name)
|
||||
if(labelOrVar!=null && labelOrVar.position != subroutine.position)
|
||||
nameError(name, labelOrVar.position, subroutine)
|
||||
val sub = subroutine.statements.singleOrNull { it is Subroutine && it.name==name}
|
||||
val sub = subroutine.statements.firstOrNull { it is Subroutine && it.name==name}
|
||||
if(sub!=null)
|
||||
nameError(name, sub.position, subroutine)
|
||||
nameError(name, subroutine.position, sub)
|
||||
val block = program.allBlocks().firstOrNull { it.name==name }
|
||||
if(block!=null)
|
||||
nameError(name, subroutine.position, block)
|
||||
}
|
||||
|
||||
if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) {
|
||||
|
@ -4,6 +4,7 @@ import prog8.ast.*
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.functions.BuiltinFunctions
|
||||
|
||||
|
||||
internal class StatementReorderer(val program: Program, val errors: ErrorReporter) : AstWalker() {
|
||||
@ -68,10 +69,36 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val subs = subroutine.statements.filterIsInstance<Subroutine>()
|
||||
if(subs.isNotEmpty()) {
|
||||
// all subroutines defined within this subroutine are moved to the end
|
||||
return subs.map { IAstModification.Remove(it, subroutine) } +
|
||||
subs.map { IAstModification.InsertLast(it, subroutine) }
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||
|
||||
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program.namespace)
|
||||
if(arrayVar!=null && arrayVar.datatype == DataType.UWORD) {
|
||||
// rewrite pointervar[index] into @(pointervar+index)
|
||||
val indexer = arrayIndexedExpression.indexer
|
||||
val index = (indexer.indexNum ?: indexer.indexVar)!!
|
||||
val add = BinaryExpression(arrayIndexedExpression.arrayvar, "+", index, arrayIndexedExpression.position)
|
||||
return if(parent is AssignTarget) {
|
||||
// we're part of the target of an assignment, we have to actually change the assign target itself
|
||||
val memwrite = DirectMemoryWrite(add, arrayIndexedExpression.position)
|
||||
val newtarget = AssignTarget(null, null, memwrite, arrayIndexedExpression.position)
|
||||
listOf(IAstModification.ReplaceNode(parent, newtarget, parent.parent))
|
||||
} else {
|
||||
val memread = DirectMemoryRead(add, arrayIndexedExpression.position)
|
||||
listOf(IAstModification.ReplaceNode(arrayIndexedExpression, memread, parent))
|
||||
}
|
||||
}
|
||||
|
||||
when (val expr2 = arrayIndexedExpression.indexer.origExpression) {
|
||||
is NumericLiteralValue -> {
|
||||
arrayIndexedExpression.indexer.indexNum = expr2
|
||||
@ -91,41 +118,78 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
|
||||
}
|
||||
}
|
||||
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
// when using a simple bit shift and assigning it to a variable of a different type,
|
||||
// try to make the bit shifting 'wide enough' to fall into the variable's type.
|
||||
// with this, for instance, uword x = 1 << 10 will result in 1024 rather than 0 (the ubyte result).
|
||||
if(expr.operator=="<<" || expr.operator==">>") {
|
||||
val leftDt = expr.left.inferType(program)
|
||||
when (parent) {
|
||||
is Assignment -> {
|
||||
val targetDt = parent.target.inferType(program)
|
||||
if(leftDt != targetDt) {
|
||||
val cast = TypecastExpression(expr.left, targetDt.typeOrElse(DataType.STRUCT), true, parent.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||
}
|
||||
}
|
||||
is VarDecl -> {
|
||||
if(!leftDt.istype(parent.datatype)) {
|
||||
val cast = TypecastExpression(expr.left, parent.datatype, true, parent.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||
}
|
||||
}
|
||||
is IFunctionCall -> {
|
||||
val argnum = parent.args.indexOf(expr)
|
||||
when (val callee = parent.target.targetStatement(program.namespace)) {
|
||||
is Subroutine -> {
|
||||
val paramType = callee.parameters[argnum].type
|
||||
if(leftDt isAssignableTo paramType) {
|
||||
val cast = TypecastExpression(expr.left, paramType, true, parent.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||
}
|
||||
}
|
||||
is BuiltinFunctionStatementPlaceholder -> {
|
||||
val func = BuiltinFunctions.getValue(callee.name)
|
||||
val paramTypes = func.parameters[argnum].possibleDatatypes
|
||||
for(type in paramTypes) {
|
||||
if(leftDt isAssignableTo type) {
|
||||
val cast = TypecastExpression(expr.left, type, true, parent.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw FatalAstException("weird callee")
|
||||
}
|
||||
}
|
||||
else -> return noModifications
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
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 indexerVarName: String
|
||||
val repo = subroutine.asmGenInfo.usedAutoArrayIndexerForStatements
|
||||
|
||||
val freeVar = repo.filter { statement !in it.value }.map { it.key }.firstOrNull()
|
||||
if(freeVar==null) {
|
||||
// add another loop index var to be used for this expression
|
||||
val statementId = expr.hashCode()
|
||||
indexerVarName = "$indexerVarPrefix$statementId"
|
||||
// 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))
|
||||
var statements = repo[indexerVarName]
|
||||
if(statements==null) {
|
||||
statements = mutableSetOf()
|
||||
repo[indexerVarName] = statements
|
||||
}
|
||||
statements.add(statement)
|
||||
} else {
|
||||
// reuse an already created indexer autovar
|
||||
repo.getValue(freeVar).add(statement)
|
||||
indexerVarName = freeVar
|
||||
}
|
||||
// TODO make this a bit smarter so it can reuse indexer variables. BUT BEWARE of scoping+initialization problems then
|
||||
// add another loop index var to be used for this expression
|
||||
val indexerVarName = "$indexerVarPrefix${expr.indexer.hashCode()}"
|
||||
val indexerVar = AsmGenInfo.ArrayIndexerInfo(indexerVarName, expr.indexer)
|
||||
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))
|
||||
|
||||
// assign the indexing expression to the helper variable, and replace the indexer with just the variable
|
||||
// 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(indexerVarName), indexerExpression.position), null, null, indexerExpression.position)
|
||||
val target = AssignTarget(IdentifierReference(listOf(indexerVar.name), indexerExpression.position), null, null, indexerExpression.position)
|
||||
val assign = Assignment(target, indexerExpression, indexerExpression.position)
|
||||
val beforeWhat = expr.containingStatement()
|
||||
modifications.add(IAstModification.InsertBefore(beforeWhat, assign, beforeWhat.definingScope()))
|
||||
modifications.add(IAstModification.InsertBefore(statement, assign, statement.definingScope()))
|
||||
modifications.add(IAstModification.SetExpression( {
|
||||
expr.indexer.indexVar = it as IdentifierReference
|
||||
expr.indexer.indexNum = null
|
||||
@ -168,7 +232,7 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
|
||||
|
||||
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
val valueType = assignment.value.inferType(program)
|
||||
val targetType = assignment.target.inferType(program, assignment)
|
||||
val targetType = assignment.target.inferType(program)
|
||||
var assignments = emptyList<Assignment>()
|
||||
|
||||
if(targetType.istype(DataType.STRUCT) && (valueType.istype(DataType.STRUCT) || valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes )) {
|
||||
@ -279,7 +343,7 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
|
||||
}
|
||||
|
||||
// TODO use a pointer loop instead of individual assignments
|
||||
return alv.value.withIndex().map { (index, value)->
|
||||
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)
|
||||
}
|
||||
|
@ -60,12 +60,15 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
// see if a typecast is needed to convert the value's type into the proper target type
|
||||
val valueItype = assignment.value.inferType(program)
|
||||
val targetItype = assignment.target.inferType(program, assignment)
|
||||
val targetItype = assignment.target.inferType(program)
|
||||
if(targetItype.isKnown && valueItype.isKnown) {
|
||||
val targettype = targetItype.typeOrElse(DataType.STRUCT)
|
||||
val valuetype = valueItype.typeOrElse(DataType.STRUCT)
|
||||
if (valuetype != targettype) {
|
||||
if (valuetype isAssignableTo targettype) {
|
||||
if(valuetype in IterableDatatypes && targettype==DataType.UWORD)
|
||||
// special case, don't typecast STR/arrays to UWORD, we support those assignments "directly"
|
||||
return noModifications
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
assignment.value,
|
||||
TypecastExpression(assignment.value, targettype, true, assignment.value.position),
|
||||
@ -116,30 +119,30 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
|
||||
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) {
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[arg.second.index],
|
||||
TypecastExpression(arg.second.value, requiredType, true, arg.second.value.position),
|
||||
call.args[index],
|
||||
TypecastExpression(pair.second, requiredType, true, pair.second.position),
|
||||
call as Node)
|
||||
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
|
||||
// we allow STR/ARRAY values in place of UWORD parameters. Take their address instead.
|
||||
if(arg.second.value is IdentifierReference) {
|
||||
if(pair.second is IdentifierReference) {
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[arg.second.index],
|
||||
AddressOf(arg.second.value as IdentifierReference, arg.second.value.position),
|
||||
call.args[index],
|
||||
AddressOf(pair.second as IdentifierReference, pair.second.position),
|
||||
call as Node)
|
||||
}
|
||||
} else if(arg.second.value is NumericLiteralValue) {
|
||||
val cast = (arg.second.value as NumericLiteralValue).cast(requiredType)
|
||||
} else if(pair.second is NumericLiteralValue) {
|
||||
val cast = (pair.second as NumericLiteralValue).cast(requiredType)
|
||||
if(cast.isValid)
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[arg.second.index],
|
||||
call.args[index],
|
||||
cast.valueOrZero(),
|
||||
call as Node)
|
||||
}
|
||||
@ -149,19 +152,19 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
}
|
||||
is BuiltinFunctionStatementPlaceholder -> {
|
||||
val func = BuiltinFunctions.getValue(sub.name)
|
||||
for (arg in func.parameters.zip(call.args.withIndex())) {
|
||||
val argItype = arg.second.value.inferType(program)
|
||||
func.parameters.zip(call.args).forEachIndexed { index, pair ->
|
||||
val argItype = pair.second.inferType(program)
|
||||
if (argItype.isKnown) {
|
||||
val argtype = argItype.typeOrElse(DataType.STRUCT)
|
||||
if (arg.first.possibleDatatypes.any { argtype == it })
|
||||
continue
|
||||
for (possibleType in arg.first.possibleDatatypes) {
|
||||
if (argtype isAssignableTo possibleType) {
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[arg.second.index],
|
||||
TypecastExpression(arg.second.value, possibleType, true, arg.second.value.position),
|
||||
call as Node)
|
||||
break
|
||||
if (pair.first.possibleDatatypes.all { argtype != it }) {
|
||||
for (possibleType in pair.first.possibleDatatypes) {
|
||||
if (argtype isAssignableTo possibleType) {
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[index],
|
||||
TypecastExpression(pair.second, possibleType, true, pair.second.position),
|
||||
call as Node)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.FunctionCall
|
||||
import prog8.ast.expressions.TypecastExpression
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.CompilerException
|
||||
import prog8.functions.BuiltinFunctions
|
||||
@ -39,7 +40,11 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
||||
}
|
||||
|
||||
fun checkTypes(call: IFunctionCall, scope: INameScope, program: Program): String? {
|
||||
val argtypes = call.args.map { it.inferType(program).typeOrElse(DataType.STRUCT) }
|
||||
val argITypes = call.args.map { it.inferType(program) }
|
||||
val firstUnknownDt = argITypes.indexOfFirst { it.isUnknown }
|
||||
if(firstUnknownDt>=0)
|
||||
return "argument ${firstUnknownDt+1} invalid argument type"
|
||||
val argtypes = argITypes.map { it.typeOrElse(DataType.STRUCT) }
|
||||
val target = call.target.targetStatement(scope)
|
||||
if (target is Subroutine) {
|
||||
if(call.args.size != target.parameters.size)
|
||||
@ -56,8 +61,15 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
||||
// 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)"
|
||||
if (call !is FunctionCallStatement) {
|
||||
val checkParent =
|
||||
if(parent is TypecastExpression)
|
||||
parent.parent
|
||||
else
|
||||
parent
|
||||
if (checkParent !is Assignment && checkParent !is VarDecl) {
|
||||
return "can't use subroutine call that returns multiple return values here"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -67,12 +79,12 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
||||
if(call.args.size != func.parameters.size)
|
||||
return "invalid number of arguments"
|
||||
val paramtypes = func.parameters.map { it.possibleDatatypes }
|
||||
for (x in argtypes.zip(paramtypes).withIndex()) {
|
||||
val anyCompatible = x.value.second.any { argTypeCompatible(x.value.first, it) }
|
||||
argtypes.zip(paramtypes).forEachIndexed { index, pair ->
|
||||
val anyCompatible = pair.second.any { argTypeCompatible(pair.first, it) }
|
||||
if (!anyCompatible) {
|
||||
val actual = x.value.first.toString()
|
||||
val expected = x.value.second.toString()
|
||||
return "argument ${x.index + 1} type mismatch, was: $actual expected: $expected"
|
||||
val actual = pair.first.toString()
|
||||
val expected = pair.second.toString()
|
||||
return "argument ${index + 1} type mismatch, was: $actual expected: $expected"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ class BuiltinFunctionStatementPlaceholder(val name: String, override val positio
|
||||
}
|
||||
}
|
||||
|
||||
data class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusflag: Statusflag?, val stack: Boolean)
|
||||
data class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusflag: Statusflag?)
|
||||
|
||||
class Block(override val name: String,
|
||||
val address: Int?,
|
||||
@ -52,7 +52,6 @@ class Block(override val name: String,
|
||||
val isInLibrary: Boolean,
|
||||
override val position: Position) : Statement(), INameScope {
|
||||
override lateinit var parent: Node
|
||||
override val asmGenInfo = AsmGenInfo()
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -187,7 +186,12 @@ open class VarDecl(val type: VarDeclType,
|
||||
|
||||
fun createAuto(array: ArrayLiteralValue): VarDecl {
|
||||
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
|
||||
val declaredType = ArrayElementTypes.getValue(array.type.typeOrElse(DataType.STRUCT))
|
||||
val arrayDt =
|
||||
if(!array.type.isKnown)
|
||||
throw FatalAstException("unknown dt")
|
||||
else
|
||||
array.type.typeOrElse(DataType.STRUCT)
|
||||
val declaredType = ArrayElementTypes.getValue(arrayDt)
|
||||
val arraysize = ArrayIndex.forArray(array)
|
||||
return VarDecl(VarDeclType.VAR, declaredType, ZeropageWish.NOT_IN_ZEROPAGE, arraysize, autoVarName, null, array,
|
||||
isArray = true, autogeneratedDontRemove = true, position = array.position)
|
||||
@ -196,7 +200,7 @@ open class VarDecl(val type: VarDeclType,
|
||||
fun defaultZero(dt: DataType, position: Position) = when(dt) {
|
||||
DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, 0, position)
|
||||
DataType.BYTE -> NumericLiteralValue(DataType.BYTE, 0, position)
|
||||
DataType.UWORD -> NumericLiteralValue(DataType.UWORD, 0, position)
|
||||
DataType.UWORD, DataType.STR -> NumericLiteralValue(DataType.UWORD, 0, position)
|
||||
DataType.WORD -> NumericLiteralValue(DataType.WORD, 0, position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, 0.0, position)
|
||||
else -> throw FatalAstException("can only determine default zero value for a numeric type")
|
||||
@ -252,9 +256,9 @@ open class VarDecl(val type: VarDeclType,
|
||||
}
|
||||
|
||||
fun flattenStructMembers(): MutableList<Statement> {
|
||||
val result = struct!!.statements.withIndex().map {
|
||||
val member = it.value as VarDecl
|
||||
val initvalue = if(value!=null) (value as ArrayLiteralValue).value[it.index] else null
|
||||
val result = struct!!.statements.mapIndexed { index, statement ->
|
||||
val member = statement as VarDecl
|
||||
val initvalue = if(value!=null) (value as ArrayLiteralValue).value[index] else null
|
||||
VarDecl(
|
||||
VarDeclType.VAR,
|
||||
member.datatype,
|
||||
@ -309,6 +313,7 @@ class ArrayIndex(var origExpression: Expression?, // will be replaced
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
indexVar = replacement
|
||||
indexNum = null
|
||||
}
|
||||
else -> {
|
||||
throw FatalAstException("invalid replace")
|
||||
@ -343,7 +348,12 @@ class ArrayIndex(var origExpression: Expression?, // will be replaced
|
||||
|
||||
fun constIndex() = indexNum?.number?.toInt()
|
||||
|
||||
infix fun isSameAs(other: ArrayIndex) = indexNum==other.indexNum && indexVar == other.indexVar
|
||||
infix fun isSameAs(other: ArrayIndex): Boolean {
|
||||
return if(indexNum!=null || indexVar!=null)
|
||||
indexNum==other.indexNum && indexVar == other.indexVar
|
||||
else
|
||||
other.origExpression!=null && origExpression!! isSameAs other.origExpression!!
|
||||
}
|
||||
}
|
||||
|
||||
open class Assignment(var target: AssignTarget, var value: Expression, override val position: Position) : Statement() {
|
||||
@ -444,9 +454,9 @@ data class AssignTarget(var identifier: IdentifierReference?,
|
||||
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
fun inferType(program: Program, stmt: Statement): InferredTypes.InferredType { // TODO why does this have the extra 'stmt' scope parameter???
|
||||
fun inferType(program: Program): InferredTypes.InferredType {
|
||||
if (identifier != null) {
|
||||
val symbol = program.namespace.lookup(identifier!!.nameInSource, stmt) ?: return InferredTypes.unknown()
|
||||
val symbol = program.namespace.lookup(identifier!!.nameInSource, this) ?: return InferredTypes.unknown()
|
||||
if (symbol is VarDecl) return InferredTypes.knownFor(symbol.datatype)
|
||||
}
|
||||
|
||||
@ -637,7 +647,6 @@ class AnonymousScope(override var statements: MutableList<Statement>,
|
||||
override val position: Position) : INameScope, Statement() {
|
||||
override val name: String
|
||||
override lateinit var parent: Node
|
||||
override val asmGenInfo = AsmGenInfo()
|
||||
|
||||
companion object {
|
||||
private var sequenceNumber = 1
|
||||
@ -676,6 +685,22 @@ class NopStatement(override val position: Position): Statement() {
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
|
||||
|
||||
class AsmGenInfo {
|
||||
// This class contains various attributes that influence the assembly code generator.
|
||||
// Conceptually it should be part of any INameScope.
|
||||
// But because the resulting code only creates "real" scopes on a subroutine level,
|
||||
// it's more consistent to only define these attributes on a Subroutine node.
|
||||
var usedAutoArrayIndexerForStatements = mutableListOf<ArrayIndexerInfo>()
|
||||
var usedRegsaveA = false
|
||||
var usedRegsaveX = false
|
||||
var usedRegsaveY = false
|
||||
var usedFloatEvalResultVar1 = false
|
||||
var usedFloatEvalResultVar2 = false
|
||||
|
||||
class ArrayIndexerInfo(val name: String, val replaces: ArrayIndex)
|
||||
}
|
||||
|
||||
// the subroutine class covers both the normal user-defined subroutines,
|
||||
// and also the predefined/ROM/register-based subroutines.
|
||||
// (multiple return types can only occur for the latter type)
|
||||
@ -687,11 +712,28 @@ class Subroutine(override val name: String,
|
||||
val asmClobbers: Set<CpuRegister>,
|
||||
val asmAddress: Int?,
|
||||
val isAsmSubroutine: Boolean,
|
||||
val inline: Boolean,
|
||||
override var statements: MutableList<Statement>,
|
||||
override val position: Position) : Statement(), INameScope {
|
||||
|
||||
constructor(name: String, parameters: List<SubroutineParameter>, returntypes: List<DataType>, statements: MutableList<Statement>, inline: Boolean, position: Position)
|
||||
: this(name, parameters, returntypes, emptyList(), determineReturnRegisters(returntypes), emptySet(), null, false, inline, statements, position)
|
||||
|
||||
companion object {
|
||||
private fun determineReturnRegisters(returntypes: List<DataType>): List<RegisterOrStatusflag> {
|
||||
// for non-asm subroutines, determine the return registers based on the type of the return value
|
||||
return when(returntypes.singleOrNull()) {
|
||||
in ByteDatatypes -> listOf(RegisterOrStatusflag(RegisterOrPair.A, null))
|
||||
in WordDatatypes -> listOf(RegisterOrStatusflag(RegisterOrPair.AY, null))
|
||||
DataType.FLOAT -> listOf(RegisterOrStatusflag(RegisterOrPair.FAC1, null))
|
||||
null -> emptyList()
|
||||
else -> listOf(RegisterOrStatusflag(RegisterOrPair.AY, null))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override lateinit var parent: Node
|
||||
override val asmGenInfo = AsmGenInfo()
|
||||
val asmGenInfo = AsmGenInfo()
|
||||
val scopedname: String by lazy { makeScopedName(name) }
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
@ -716,6 +758,17 @@ class Subroutine(override val name: String,
|
||||
|
||||
fun regXasResult() = asmReturnvaluesRegisters.any { it.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
|
||||
fun regXasParam() = asmParameterRegisters.any { it.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
|
||||
fun shouldSaveX() = CpuRegister.X in asmClobbers || regXasResult() || regXasParam()
|
||||
fun shouldKeepA(): Pair<Boolean, Boolean> {
|
||||
// determine if A's value should be kept when preparing for calling the subroutine, and when returning from it
|
||||
if(!isAsmSubroutine)
|
||||
return Pair(false, false)
|
||||
|
||||
// it seems that we never have to save A when calling? will be loaded correctly after setup.
|
||||
// but on return it depends on wether the routine returns something in A.
|
||||
val saveAonReturn = asmReturnvaluesRegisters.any { it.registerOrPair==RegisterOrPair.A || it.registerOrPair==RegisterOrPair.AY || it.registerOrPair==RegisterOrPair.AX }
|
||||
return Pair(false, saveAonReturn)
|
||||
}
|
||||
|
||||
fun amountOfRtsInAsm(): Int = statements
|
||||
.asSequence()
|
||||
@ -938,7 +991,7 @@ class WhenStatement(var condition: Expression,
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
|
||||
class WhenChoice(var values: List<Expression>?, // if null, this is the 'else' part
|
||||
class WhenChoice(var values: MutableList<Expression>?, // if null, this is the 'else' part
|
||||
var statements: AnonymousScope,
|
||||
override val position: Position) : Node {
|
||||
override lateinit var parent: Node
|
||||
@ -955,7 +1008,9 @@ class WhenChoice(var values: List<Expression>?, // if null, this is t
|
||||
statements = replacement
|
||||
replacement.parent = this
|
||||
} else if(choiceValues!=null && node in choiceValues) {
|
||||
throw FatalAstException("cannot replace choice values")
|
||||
val idx = choiceValues.indexOf(node)
|
||||
choiceValues[idx] = replacement as Expression
|
||||
replacement.parent = this
|
||||
} else {
|
||||
throw FatalAstException("invalid replacement")
|
||||
}
|
||||
@ -975,7 +1030,6 @@ class StructDecl(override val name: String,
|
||||
override val position: Position): Statement(), INameScope {
|
||||
|
||||
override lateinit var parent: Node
|
||||
override val asmGenInfo = AsmGenInfo()
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
|
@ -15,7 +15,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
subroutineVariables.add(Pair(decl.name, decl))
|
||||
subroutineVariables.add(decl.name to decl)
|
||||
if (decl.value == null && !decl.autogeneratedDontRemove && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
|
||||
// a numeric vardecl without an initial value is initialized with zero,
|
||||
// unless there's already an assignment below, that initializes the value
|
||||
@ -74,7 +74,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
||||
|
||||
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
val decls = scope.statements.filterIsInstance<VarDecl>()
|
||||
subroutineVariables.addAll(decls.map { Pair(it.name, it) })
|
||||
subroutineVariables.addAll(decls.map { it.name to it })
|
||||
|
||||
val sub = scope.definingSubroutine()
|
||||
if (sub != null) {
|
||||
@ -111,6 +111,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
||||
val mods = mutableListOf<IAstModification>()
|
||||
val returnStmt = Return(null, subroutine.position)
|
||||
if (subroutine.asmAddress == null
|
||||
&& !subroutine.inline
|
||||
&& subroutine.statements.isNotEmpty()
|
||||
&& subroutine.amountOfRtsInAsm() == 0
|
||||
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
|
||||
@ -170,6 +171,12 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
||||
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)
|
||||
|
@ -30,6 +30,7 @@ data class CompilationOptions(val output: OutputType,
|
||||
val floats: Boolean,
|
||||
val noSysInit: Boolean) {
|
||||
var slowCodegenWarnings = false
|
||||
var optimize = false
|
||||
}
|
||||
|
||||
|
||||
|
@ -51,17 +51,18 @@ fun compileProgram(filepath: Path,
|
||||
// import main module and everything it needs
|
||||
val (ast, compilationOptions, imported) = parseImports(filepath, errors)
|
||||
compilationOptions.slowCodegenWarnings = slowCodegenWarnings
|
||||
compilationOptions.optimize = optimize
|
||||
programAst = ast
|
||||
importedFiles = imported
|
||||
processAst(programAst, errors, compilationOptions)
|
||||
if (optimize)
|
||||
if (compilationOptions.optimize)
|
||||
optimizeAst(programAst, errors)
|
||||
postprocessAst(programAst, errors, compilationOptions)
|
||||
|
||||
// printAst(programAst)
|
||||
|
||||
if(writeAssembly)
|
||||
programName = writeAssembly(programAst, errors, outputDir, optimize, compilationOptions)
|
||||
programName = writeAssembly(programAst, errors, outputDir, compilationOptions)
|
||||
}
|
||||
System.out.flush()
|
||||
System.err.flush()
|
||||
@ -218,7 +219,7 @@ private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerO
|
||||
}
|
||||
|
||||
private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir: Path,
|
||||
optimize: Boolean, compilerOptions: CompilationOptions): String {
|
||||
compilerOptions: CompilationOptions): String {
|
||||
// asm generation directly from the Ast,
|
||||
programAst.processAstBeforeAsmGeneration(errors)
|
||||
errors.handle()
|
||||
@ -231,7 +232,7 @@ private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir:
|
||||
errors,
|
||||
CompilationTarget.instance.machine.zeropage,
|
||||
compilerOptions,
|
||||
outputDir).compileToAssembly(optimize)
|
||||
outputDir).compileToAssembly()
|
||||
assembly.assemble(compilerOptions)
|
||||
errors.handle()
|
||||
return assembly.name
|
||||
|
@ -64,7 +64,7 @@ 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
|
||||
}
|
||||
|
||||
|
@ -3,10 +3,12 @@ package prog8.compiler.target
|
||||
import prog8.compiler.CompilationOptions
|
||||
|
||||
internal interface IAssemblyGenerator {
|
||||
fun compileToAssembly(optimize: Boolean): IAssemblyProgram
|
||||
fun compileToAssembly(): IAssemblyProgram
|
||||
}
|
||||
|
||||
internal const val generatedLabelPrefix = "_prog8_label_"
|
||||
internal const val subroutineFloatEvalResultVar1 = "_prog8_float_eval_result1"
|
||||
internal const val subroutineFloatEvalResultVar2 = "_prog8_float_eval_result2"
|
||||
|
||||
internal interface IAssemblyProgram {
|
||||
val name: String
|
||||
|
@ -32,7 +32,6 @@ internal interface IMachineDefinition {
|
||||
|
||||
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
|
||||
|
@ -7,7 +7,6 @@ import prog8.compiler.target.IMachineDefinition
|
||||
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
|
||||
|
||||
@ -24,7 +23,6 @@ internal object C64MachineDefinition: IMachineDefinition {
|
||||
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
|
||||
override val ESTACK_LO = 0xce00 // $ce00-$ceff inclusive
|
||||
override val ESTACK_HI = 0xcf00 // $ce00-$ceff inclusive
|
||||
|
||||
@ -32,41 +30,6 @@ internal object C64MachineDefinition: IMachineDefinition {
|
||||
|
||||
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")
|
||||
@ -110,7 +73,7 @@ internal object C64MachineDefinition: IMachineDefinition {
|
||||
internal class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
|
||||
override val SCRATCH_B1 = 0x02 // temp storage for a single byte
|
||||
override val SCRATCH_REG = 0x03 // temp storage for a register
|
||||
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
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package prog8.compiler.target.c64.codegen
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
@ -8,20 +9,19 @@ import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.*
|
||||
import prog8.compiler.target.*
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.compiler.target.CpuType
|
||||
import prog8.compiler.target.IAssemblyGenerator
|
||||
import prog8.compiler.target.IAssemblyProgram
|
||||
import prog8.compiler.target.c64.AssemblyProgram
|
||||
import prog8.compiler.target.c64.Petscii
|
||||
import prog8.compiler.target.c64.codegen.assignment.AsmAssignSource
|
||||
import prog8.compiler.target.c64.codegen.assignment.AsmAssignTarget
|
||||
import prog8.compiler.target.c64.codegen.assignment.AsmAssignment
|
||||
import prog8.compiler.target.c64.codegen.assignment.AssignmentAsmGen
|
||||
import prog8.compiler.target.c64.codegen.assignment.TargetStorageKind
|
||||
import prog8.compiler.target.generatedLabelPrefix
|
||||
import prog8.functions.BuiltinFunctions
|
||||
import prog8.functions.FSignature
|
||||
import java.io.CharConversionException
|
||||
import java.nio.file.Path
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
@ -37,7 +37,7 @@ internal class AsmGen(private val program: Program,
|
||||
|
||||
// for expressions and augmented assignments:
|
||||
val optimizedByteMultiplications = setOf(3,5,6,7,9,10,11,12,13,14,15,20,25,40,50,80,100)
|
||||
val optimizedWordMultiplications = setOf(3,5,6,7,9,10,12,15,20,25,40,50,80,100)
|
||||
val optimizedWordMultiplications = setOf(3,5,6,7,9,10,12,15,20,25,40,50,80,100,320)
|
||||
|
||||
private val assemblyLines = mutableListOf<String>()
|
||||
private val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
|
||||
@ -51,8 +51,10 @@ internal class AsmGen(private val program: Program,
|
||||
private val assignmentAsmGen = AssignmentAsmGen(program, this, expressionsAsmGen)
|
||||
internal val loopEndLabels = ArrayDeque<String>()
|
||||
private val blockLevelVarInits = mutableMapOf<Block, MutableSet<VarDecl>>()
|
||||
internal val slabs = mutableMapOf<String, Int>()
|
||||
internal val removals = mutableListOf<Pair<Statement, INameScope>>()
|
||||
|
||||
override fun compileToAssembly(optimize: Boolean): IAssemblyProgram {
|
||||
override fun compileToAssembly(): IAssemblyProgram {
|
||||
assemblyLines.clear()
|
||||
loopEndLabels.clear()
|
||||
|
||||
@ -64,6 +66,13 @@ internal class AsmGen(private val program: Program,
|
||||
throw AssemblyError("first block should be 'main'")
|
||||
for(b in program.allBlocks())
|
||||
block2asm(b)
|
||||
|
||||
for(removal in removals.toList()) {
|
||||
removal.second.remove(removal.first)
|
||||
removals.remove(removal)
|
||||
}
|
||||
|
||||
slaballocations()
|
||||
footer()
|
||||
|
||||
val outputFile = outputDir.resolve("${program.name}.asm").toFile()
|
||||
@ -71,7 +80,7 @@ internal class AsmGen(private val program: Program,
|
||||
for (line in assemblyLines) { it.println(line) }
|
||||
}
|
||||
|
||||
if(optimize) {
|
||||
if(options.optimize) {
|
||||
assemblyLines.clear()
|
||||
assemblyLines.addAll(outputFile.readLines())
|
||||
var optimizationsDone = 1
|
||||
@ -144,15 +153,23 @@ internal class AsmGen(private val program: Program,
|
||||
if(options.zeropage !in setOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE)) {
|
||||
out("""
|
||||
; zeropage is clobbered so we need to reset the machine at exit
|
||||
lda #>${CompilationTarget.instance.name}.reset_system
|
||||
lda #>sys.reset_system
|
||||
pha
|
||||
lda #<${CompilationTarget.instance.name}.reset_system
|
||||
lda #<sys.reset_system
|
||||
pha""")
|
||||
}
|
||||
|
||||
out(" jmp main.start ; start program / force start proc to be included")
|
||||
}
|
||||
|
||||
private fun slaballocations() {
|
||||
out("; memory slabs")
|
||||
out("prog8_slabs\t.block")
|
||||
for((name, size) in slabs)
|
||||
out("$name\t.fill $size")
|
||||
out("\t.bend")
|
||||
}
|
||||
|
||||
private fun footer() {
|
||||
// the global list of all floating point constants for the whole program
|
||||
out("; global float constants")
|
||||
@ -161,6 +178,7 @@ internal class AsmGen(private val program: Program,
|
||||
val floatvalue = flt.key
|
||||
out("${flt.value}\t.byte $floatFill ; float $floatvalue")
|
||||
}
|
||||
out("prog8_program_end\t; end of program label for progend()")
|
||||
}
|
||||
|
||||
private fun block2asm(block: Block) {
|
||||
@ -200,11 +218,7 @@ internal class AsmGen(private val program: Program,
|
||||
|
||||
private fun assignInitialValueToVar(decl: VarDecl, variableName: List<String>) {
|
||||
val asmName = asmVariableName(variableName)
|
||||
val asgn = AsmAssignment(
|
||||
AsmAssignSource.fromAstSource(decl.value!!, program, this),
|
||||
AsmAssignTarget(TargetStorageKind.VARIABLE, program, this, decl.datatype, decl.definingSubroutine(), variableAsmName = asmName),
|
||||
false, decl.position)
|
||||
assignmentAsmGen.translateNormalAssignment(asgn)
|
||||
assignmentAsmGen.assignExpressionToVariable(decl.value!!, asmName, decl.datatype, decl.definingSubroutine())
|
||||
}
|
||||
|
||||
private var generatedLabelSequenceNumber: Int = 0
|
||||
@ -231,8 +245,12 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
|
||||
private fun encode(str: String, altEncoding: Boolean): List<Short> {
|
||||
val bytes = if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||
return bytes.plus(0)
|
||||
try {
|
||||
val bytes = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||
return bytes.plus(0)
|
||||
} catch(x: CharConversionException) {
|
||||
throw AssemblyError("There was a problem converting a string to the target machine's char encoding: ${x.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun zeropagevars2asm(statements: List<Statement>) {
|
||||
@ -253,7 +271,7 @@ internal class AsmGen(private val program: Program,
|
||||
errors.handle()
|
||||
out("${variable.name} = $address\t; auto zp ${variable.datatype}")
|
||||
// make sure we add the var to the set of zpvars for this block
|
||||
allocatedZeropageVariables[fullName] = Pair(address, variable.datatype)
|
||||
allocatedZeropageVariables[fullName] = address to variable.datatype
|
||||
} catch (x: ZeropageDepletedError) {
|
||||
// leave it as it is.
|
||||
}
|
||||
@ -470,16 +488,13 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
|
||||
internal fun getFloatAsmConst(number: Double): String {
|
||||
var asmName = CompilationTarget.instance.machine.getFloatRomConst(number)
|
||||
if(asmName.isNullOrEmpty()) {
|
||||
// no ROM float const for this value, create our own
|
||||
asmName = globalFloatConsts[number]
|
||||
if(asmName==null) {
|
||||
asmName = "prog8_float_const_${globalFloatConsts.size}"
|
||||
globalFloatConsts[number] = asmName
|
||||
}
|
||||
}
|
||||
return asmName
|
||||
val asmName = globalFloatConsts[number]
|
||||
if(asmName!=null)
|
||||
return asmName
|
||||
|
||||
val newName = "prog8_float_const_${globalFloatConsts.size}"
|
||||
globalFloatConsts[number] = newName
|
||||
return newName
|
||||
}
|
||||
|
||||
internal fun asmSymbolName(identifier: IdentifierReference): String {
|
||||
@ -511,112 +526,136 @@ internal class AsmGen(private val program: Program,
|
||||
val sourceName = asmVariableName(pointervar)
|
||||
val vardecl = pointervar.targetVarDecl(program.namespace)!!
|
||||
val scopedName = vardecl.makeScopedName(vardecl.name)
|
||||
return if(scopedName in allocatedZeropageVariables) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
out(" ldy #0 | lda ($sourceName),y")
|
||||
Pair(true, sourceName)
|
||||
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) {
|
||||
return if (isZpVar(scopedName)) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
out(" lda ($sourceName)")
|
||||
Pair(true, sourceName)
|
||||
} else {
|
||||
out("""
|
||||
lda $sourceName
|
||||
ldy $sourceName+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda (P8ZP_SCRATCH_W1)""")
|
||||
Pair(false, sourceName)
|
||||
}
|
||||
} else {
|
||||
out("""
|
||||
lda $sourceName
|
||||
ldy $sourceName+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_W1),y""")
|
||||
Pair(false, sourceName)
|
||||
}
|
||||
}
|
||||
|
||||
fun storeByteIntoPointer(pointervar: IdentifierReference, ldaInstructionArg: String?) {
|
||||
val sourceName = asmVariableName(pointervar)
|
||||
val vardecl = pointervar.targetVarDecl(program.namespace)!!
|
||||
val scopedName = vardecl.makeScopedName(vardecl.name)
|
||||
if(scopedName in allocatedZeropageVariables) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
if(ldaInstructionArg!=null)
|
||||
out(" lda $ldaInstructionArg")
|
||||
out(" ldy #0 | sta ($sourceName),y")
|
||||
} else {
|
||||
out("""
|
||||
ldy $sourceName
|
||||
sty P8ZP_SCRATCH_W2
|
||||
ldy $sourceName+1
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
${if(ldaInstructionArg==null) "" else "lda $ldaInstructionArg"}
|
||||
ldy #0
|
||||
sta (P8ZP_SCRATCH_W2),y""")
|
||||
return if (isZpVar(scopedName)) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
out(" ldy #0 | lda ($sourceName),y")
|
||||
Pair(true, sourceName)
|
||||
} else {
|
||||
out("""
|
||||
lda $sourceName
|
||||
ldy $sourceName+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_W1),y""")
|
||||
Pair(false, sourceName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun fixNameSymbols(name: String) = name.replace("<", "prog8_").replace(">", "") // take care of the autogenerated invalid (anon) label names
|
||||
|
||||
internal fun saveRegister(register: CpuRegister, dontUseStack: Boolean, scope: Subroutine?) {
|
||||
if(dontUseStack) {
|
||||
internal fun saveRegisterLocal(register: CpuRegister, scope: Subroutine) {
|
||||
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) {
|
||||
// just use the cpu's stack for all registers, shorter code
|
||||
when (register) {
|
||||
CpuRegister.A -> out(" pha")
|
||||
CpuRegister.X -> out(" phx")
|
||||
CpuRegister.Y -> out(" phy")
|
||||
}
|
||||
} else {
|
||||
when (register) {
|
||||
CpuRegister.A -> {
|
||||
out(" sta _prog8_regsaveA")
|
||||
if (scope != null)
|
||||
scope.asmGenInfo.usedRegsaveA = true
|
||||
// just use the stack, only for A
|
||||
out(" pha")
|
||||
}
|
||||
CpuRegister.X -> {
|
||||
out(" stx _prog8_regsaveX")
|
||||
if (scope != null)
|
||||
scope.asmGenInfo.usedRegsaveX = true
|
||||
scope.asmGenInfo.usedRegsaveX = true
|
||||
}
|
||||
CpuRegister.Y -> {
|
||||
out(" sty _prog8_regsaveY")
|
||||
if (scope != null)
|
||||
scope.asmGenInfo.usedRegsaveY = true
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
when (register) {
|
||||
CpuRegister.A -> out(" pha")
|
||||
CpuRegister.X -> {
|
||||
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phx")
|
||||
else {
|
||||
out(" stx _prog8_regsaveX")
|
||||
if (scope != null)
|
||||
scope.asmGenInfo.usedRegsaveX = true
|
||||
}
|
||||
}
|
||||
CpuRegister.Y -> {
|
||||
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phy")
|
||||
else {
|
||||
out(" sty _prog8_regsaveY")
|
||||
if (scope != null)
|
||||
scope.asmGenInfo.usedRegsaveY = true
|
||||
}
|
||||
scope.asmGenInfo.usedRegsaveY = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun restoreRegister(register: CpuRegister, dontUseStack: Boolean) {
|
||||
if(dontUseStack) {
|
||||
internal fun saveRegisterStack(register: CpuRegister, keepA: Boolean) {
|
||||
when (register) {
|
||||
CpuRegister.A -> out(" pha")
|
||||
CpuRegister.X -> {
|
||||
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phx")
|
||||
else {
|
||||
if(keepA)
|
||||
out(" sta P8ZP_SCRATCH_REG | txa | pha | lda P8ZP_SCRATCH_REG")
|
||||
else
|
||||
out(" txa | pha")
|
||||
}
|
||||
}
|
||||
CpuRegister.Y -> {
|
||||
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phy")
|
||||
else {
|
||||
if(keepA)
|
||||
out(" sta P8ZP_SCRATCH_REG | tya | pha | lda P8ZP_SCRATCH_REG")
|
||||
else
|
||||
out(" tya | pha")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun restoreRegisterLocal(register: CpuRegister) {
|
||||
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) {
|
||||
when (register) {
|
||||
CpuRegister.A -> out(" sta _prog8_regsaveA")
|
||||
// this just used the stack, for all registers. Shorter code.
|
||||
CpuRegister.A -> out(" pla")
|
||||
CpuRegister.X -> out(" plx")
|
||||
CpuRegister.Y -> out(" ply")
|
||||
}
|
||||
|
||||
} else {
|
||||
when (register) {
|
||||
CpuRegister.A -> out(" pla") // this just used the stack but only for A
|
||||
CpuRegister.X -> out(" ldx _prog8_regsaveX")
|
||||
CpuRegister.Y -> out(" ldy _prog8_regsaveY")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
when (register) {
|
||||
CpuRegister.A -> out(" pla")
|
||||
CpuRegister.X -> {
|
||||
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" plx")
|
||||
else out(" ldx _prog8_regsaveX")
|
||||
internal fun restoreRegisterStack(register: CpuRegister, keepA: Boolean) {
|
||||
when (register) {
|
||||
CpuRegister.A -> {
|
||||
if(keepA)
|
||||
throw AssemblyError("can't set keepA if A is restored")
|
||||
out(" pla")
|
||||
}
|
||||
CpuRegister.X -> {
|
||||
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" plx")
|
||||
else {
|
||||
if(keepA)
|
||||
out(" sta P8ZP_SCRATCH_REG | pla | tax | lda P8ZP_SCRATCH_REG")
|
||||
else
|
||||
out(" pla | tax")
|
||||
}
|
||||
CpuRegister.Y -> {
|
||||
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" ply")
|
||||
else out(" ldy _prog8_regsaveY")
|
||||
}
|
||||
CpuRegister.Y -> {
|
||||
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" ply")
|
||||
else {
|
||||
if(keepA)
|
||||
out(" sta P8ZP_SCRATCH_REG | pla | tay | lda P8ZP_SCRATCH_REG")
|
||||
else
|
||||
out(" pla | tay")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal fun translate(stmt: Statement) {
|
||||
outputSourceLine(stmt)
|
||||
when(stmt) {
|
||||
@ -633,19 +672,7 @@ internal class AsmGen(private val program: Program,
|
||||
if (builtinFunc != null) {
|
||||
builtinFunctionsAsmGen.translateFunctioncallStatement(stmt, builtinFunc)
|
||||
} else {
|
||||
val sub = stmt.target.targetSubroutine(program.namespace)!!
|
||||
val preserveStatusRegisterAfterCall = sub.asmReturnvaluesRegisters.any {it.statusflag!=null}
|
||||
functioncallAsmGen.translateFunctionCall(stmt, preserveStatusRegisterAfterCall)
|
||||
// discard any results from the stack:
|
||||
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
|
||||
for ((t, reg) in returns) {
|
||||
if (reg.stack) {
|
||||
if (t in IntegerDatatypes || t in PassByReferenceDatatypes) out(" inx")
|
||||
else if (t == DataType.FLOAT) out(" inx | inx | inx")
|
||||
}
|
||||
}
|
||||
if(preserveStatusRegisterAfterCall)
|
||||
out(" plp\t; restore status flags from call")
|
||||
functioncallAsmGen.translateFunctionCallStatement(stmt)
|
||||
}
|
||||
}
|
||||
is Assignment -> assignmentAsmGen.translate(stmt)
|
||||
@ -755,19 +782,45 @@ internal class AsmGen(private val program: Program,
|
||||
internal fun translateExpression(indexer: ArrayIndex) =
|
||||
expressionsAsmGen.translateExpression(indexer)
|
||||
|
||||
internal fun translateFunctioncallExpression(functionCall: FunctionCall, signature: FSignature) =
|
||||
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature)
|
||||
internal fun translateBuiltinFunctionCallExpression(functionCall: FunctionCall, signature: FSignature, resultToStack: Boolean) =
|
||||
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature, resultToStack)
|
||||
|
||||
internal fun translateFunctionCall(functionCall: FunctionCall, preserveStatusRegisterAfterCall: Boolean) =
|
||||
functioncallAsmGen.translateFunctionCall(functionCall, preserveStatusRegisterAfterCall)
|
||||
internal fun translateFunctionCall(functionCall: FunctionCall) =
|
||||
functioncallAsmGen.translateFunctionCall(functionCall)
|
||||
|
||||
internal fun saveXbeforeCall(functionCall: IFunctionCall) =
|
||||
functioncallAsmGen.saveXbeforeCall(functionCall)
|
||||
|
||||
internal fun restoreXafterCall(functionCall: IFunctionCall) =
|
||||
functioncallAsmGen.restoreXafterCall(functionCall)
|
||||
|
||||
internal fun translateNormalAssignment(assign: AsmAssignment) =
|
||||
assignmentAsmGen.translateNormalAssignment(assign)
|
||||
|
||||
internal fun assignExpressionToRegister(expr: Expression, register: RegisterOrPair) =
|
||||
assignmentAsmGen.assignExpressionToRegister(expr, register)
|
||||
|
||||
internal fun assignExpressionToVariable(expr: Expression, asmVarName: String, dt: DataType, scope: Subroutine?) =
|
||||
assignmentAsmGen.assignExpressionToVariable(expr, asmVarName, dt, scope)
|
||||
|
||||
internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair) =
|
||||
assignmentAsmGen.assignVariableToRegister(asmVarName, register)
|
||||
|
||||
|
||||
private fun translateSubroutine(sub: Subroutine) {
|
||||
if(sub.inline) {
|
||||
if(options.optimize)
|
||||
return // inline subroutines don't exist anymore on their own
|
||||
else if(sub.amountOfRtsInAsm()==0) {
|
||||
// make sure the NOT INLINED subroutine actually does an rts at the end
|
||||
sub.statements.add(Return(null, Position.DUMMY))
|
||||
}
|
||||
}
|
||||
|
||||
out("")
|
||||
outputSourceLine(sub)
|
||||
|
||||
|
||||
if(sub.isAsmSubroutine) {
|
||||
if(sub.asmAddress!=null)
|
||||
return // already done at the memvars section
|
||||
@ -792,7 +845,7 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
out("""
|
||||
tsx
|
||||
stx prog8_lib.orig_stackpointer ; required for func_exit
|
||||
stx prog8_lib.orig_stackpointer ; required for sys.exit()
|
||||
ldx #255 ; init estack ptr
|
||||
clv
|
||||
clc""")
|
||||
@ -800,6 +853,14 @@ internal class AsmGen(private val program: Program,
|
||||
|
||||
out("; statements")
|
||||
sub.statements.forEach{ translate(it) }
|
||||
|
||||
for(removal in removals.toList()) {
|
||||
if(removal.second==sub) {
|
||||
removal.second.remove(removal.first)
|
||||
removals.remove(removal)
|
||||
}
|
||||
}
|
||||
|
||||
out("; variables")
|
||||
out("; register saves")
|
||||
if(sub.asmGenInfo.usedRegsaveA)
|
||||
@ -808,6 +869,10 @@ internal class AsmGen(private val program: Program,
|
||||
out("_prog8_regsaveX .byte 0")
|
||||
if(sub.asmGenInfo.usedRegsaveY)
|
||||
out("_prog8_regsaveY .byte 0")
|
||||
if(sub.asmGenInfo.usedFloatEvalResultVar1)
|
||||
out("$subroutineFloatEvalResultVar1 .byte 0,0,0,0,0")
|
||||
if(sub.asmGenInfo.usedFloatEvalResultVar2)
|
||||
out("$subroutineFloatEvalResultVar2 .byte 0,0,0,0,0")
|
||||
vardecls2asm(sub.statements)
|
||||
out(" .pend\n")
|
||||
}
|
||||
@ -818,12 +883,12 @@ internal class AsmGen(private val program: Program,
|
||||
when (condition) {
|
||||
BranchCondition.CS -> "bcc"
|
||||
BranchCondition.CC -> "bcs"
|
||||
BranchCondition.EQ, BranchCondition.Z -> "beq"
|
||||
BranchCondition.NE, BranchCondition.NZ -> "bne"
|
||||
BranchCondition.EQ, BranchCondition.Z -> "bne"
|
||||
BranchCondition.NE, BranchCondition.NZ -> "beq"
|
||||
BranchCondition.VS -> "bvc"
|
||||
BranchCondition.VC -> "bvs"
|
||||
BranchCondition.MI, BranchCondition.NEG -> "bmi"
|
||||
BranchCondition.PL, BranchCondition.POS -> "bpl"
|
||||
BranchCondition.MI, BranchCondition.NEG -> "bpl"
|
||||
BranchCondition.PL, BranchCondition.POS -> "bmi"
|
||||
}
|
||||
} else {
|
||||
when (condition) {
|
||||
@ -882,7 +947,7 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
is NumericLiteralValue -> {
|
||||
val iterations = (stmt.iterations as NumericLiteralValue).number.toInt()
|
||||
if(iterations<0 || iterations > 65536)
|
||||
if(iterations<0 || iterations > 65535)
|
||||
throw AssemblyError("invalid number of iterations")
|
||||
when {
|
||||
iterations == 0 -> {}
|
||||
@ -912,15 +977,16 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
translateExpression(stmt.iterations!!)
|
||||
val dt = stmt.iterations!!.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
when (dt) {
|
||||
val dt = stmt.iterations!!.inferType(program)
|
||||
if(!dt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||
in ByteDatatypes -> {
|
||||
out(" inx | lda P8ESTACK_LO,x")
|
||||
assignExpressionToRegister(stmt.iterations!!, RegisterOrPair.A)
|
||||
repeatByteCountInA(null, repeatLabel, endLabel, stmt.body)
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
out(" inx | lda P8ESTACK_LO,x | ldy P8ESTACK_HI,x")
|
||||
assignExpressionToRegister(stmt.iterations!!, RegisterOrPair.AY)
|
||||
repeatWordCountInAY(null, repeatLabel, endLabel, stmt.body)
|
||||
}
|
||||
else -> throw AssemblyError("invalid loop expression datatype $dt")
|
||||
@ -1016,16 +1082,16 @@ $counterVar .byte 0""")
|
||||
}
|
||||
|
||||
private fun translate(stmt: WhenStatement) {
|
||||
expressionsAsmGen.translateExpression(stmt.condition)
|
||||
val endLabel = makeLabel("choice_end")
|
||||
val choiceBlocks = mutableListOf<Pair<String, AnonymousScope>>()
|
||||
val conditionDt = stmt.condition.inferType(program)
|
||||
if(!conditionDt.isKnown)
|
||||
throw AssemblyError("unknown condition dt")
|
||||
if(conditionDt.typeOrElse(DataType.BYTE) in ByteDatatypes)
|
||||
out(" inx | lda P8ESTACK_LO,x")
|
||||
assignExpressionToRegister(stmt.condition, RegisterOrPair.A)
|
||||
else
|
||||
out(" inx | lda P8ESTACK_LO,x | ldy P8ESTACK_HI,x")
|
||||
assignExpressionToRegister(stmt.condition, RegisterOrPair.AY)
|
||||
|
||||
for(choice in stmt.choices) {
|
||||
val choiceLabel = makeLabel("choice")
|
||||
if(choice.values==null) {
|
||||
@ -1033,7 +1099,7 @@ $counterVar .byte 0""")
|
||||
translate(choice.statements)
|
||||
out(" jmp $endLabel")
|
||||
} else {
|
||||
choiceBlocks.add(Pair(choiceLabel, choice.statements))
|
||||
choiceBlocks.add(choiceLabel to choice.statements)
|
||||
for (cv in choice.values!!) {
|
||||
val value = (cv as NumericLiteralValue).number.toInt()
|
||||
if(conditionDt.typeOrElse(DataType.BYTE) in ByteDatatypes) {
|
||||
@ -1074,26 +1140,48 @@ $counterVar .byte 0""")
|
||||
|
||||
val jump = stmt.truepart.statements.first() as? Jump
|
||||
if(jump!=null) {
|
||||
// branch with only a jump
|
||||
// branch with only a jump (goto)
|
||||
val instruction = branchInstruction(stmt.condition, false)
|
||||
out(" $instruction ${getJumpTarget(jump)}")
|
||||
translate(stmt.elsepart)
|
||||
} else {
|
||||
val truePartIsJustBreak = stmt.truepart.statements.firstOrNull() is Break
|
||||
val elsePartIsJustBreak = stmt.elsepart.statements.firstOrNull() is Break
|
||||
if(stmt.elsepart.containsNoCodeNorVars()) {
|
||||
if(truePartIsJustBreak) {
|
||||
// branch with just a break (jump out of loop)
|
||||
val instruction = branchInstruction(stmt.condition, false)
|
||||
val loopEndLabel = loopEndLabels.peek()
|
||||
out(" $instruction $loopEndLabel")
|
||||
} else {
|
||||
val instruction = branchInstruction(stmt.condition, true)
|
||||
val elseLabel = makeLabel("branch_else")
|
||||
out(" $instruction $elseLabel")
|
||||
translate(stmt.truepart)
|
||||
out(elseLabel)
|
||||
}
|
||||
}
|
||||
else if(truePartIsJustBreak) {
|
||||
// branch with just a break (jump out of loop)
|
||||
val instruction = branchInstruction(stmt.condition, false)
|
||||
val loopEndLabel = loopEndLabels.peek()
|
||||
out(" $instruction $loopEndLabel")
|
||||
translate(stmt.elsepart)
|
||||
} else if(elsePartIsJustBreak) {
|
||||
// branch with just a break (jump out of loop) but true/false inverted
|
||||
val instruction = branchInstruction(stmt.condition, true)
|
||||
val loopEndLabel = loopEndLabels.peek()
|
||||
out(" $instruction $loopEndLabel")
|
||||
translate(stmt.truepart)
|
||||
} else {
|
||||
val instruction = branchInstruction(stmt.condition, true)
|
||||
val elseLabel = makeLabel("branch_else")
|
||||
val endLabel = makeLabel("branch_end")
|
||||
out(" $instruction $elseLabel")
|
||||
translate(stmt.truepart)
|
||||
out(elseLabel)
|
||||
} else {
|
||||
val instruction = branchInstruction(stmt.condition, false)
|
||||
val trueLabel = makeLabel("branch_true")
|
||||
val endLabel = makeLabel("branch_end")
|
||||
out(" $instruction $trueLabel")
|
||||
translate(stmt.elsepart)
|
||||
out(" jmp $endLabel")
|
||||
out(trueLabel)
|
||||
translate(stmt.truepart)
|
||||
out(elseLabel)
|
||||
translate(stmt.elsepart)
|
||||
out(endLabel)
|
||||
}
|
||||
}
|
||||
@ -1125,10 +1213,10 @@ $counterVar .byte 0""")
|
||||
"%asminclude" -> {
|
||||
val sourcecode = loadAsmIncludeFile(stmt.args[0].str!!, stmt.definingModule().source)
|
||||
val scopeprefix = stmt.args[1].str ?: ""
|
||||
if(!scopeprefix.isBlank())
|
||||
if(scopeprefix.isNotBlank())
|
||||
out("$scopeprefix\t.proc")
|
||||
assemblyLines.add(sourcecode.trimEnd().trimStart('\n'))
|
||||
if(!scopeprefix.isBlank())
|
||||
if(scopeprefix.isNotBlank())
|
||||
out(" .pend\n")
|
||||
}
|
||||
"%asmbinary" -> {
|
||||
@ -1139,7 +1227,9 @@ $counterVar .byte 0""")
|
||||
"%breakpoint" -> {
|
||||
val label = "_prog8_breakpoint_${breakpointLabels.size+1}"
|
||||
breakpointLabels.add(label)
|
||||
out("$label\tnop")
|
||||
out("""
|
||||
nop
|
||||
$label nop""")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1165,7 +1255,24 @@ $counterVar .byte 0""")
|
||||
}
|
||||
|
||||
private fun translate(ret: Return) {
|
||||
ret.value?.let { expressionsAsmGen.translateExpression(it) }
|
||||
ret.value?.let { returnvalue ->
|
||||
val sub = ret.definingSubroutine()!!
|
||||
val returnType = sub.returntypes.single()
|
||||
val returnReg = sub.asmReturnvaluesRegisters.single()
|
||||
if(returnReg.registerOrPair==null)
|
||||
throw AssemblyError("normal subroutines can't return value in status register directly")
|
||||
|
||||
when (returnType) {
|
||||
in NumericDatatypes -> {
|
||||
assignExpressionToRegister(returnvalue, returnReg.registerOrPair)
|
||||
}
|
||||
else -> {
|
||||
// all else take its address and assign that also to AY register pair
|
||||
val addrofValue = AddressOf(returnvalue as IdentifierReference, returnvalue.position)
|
||||
assignmentAsmGen.assignExpressionToRegister(addrofValue, returnReg.registerOrPair)
|
||||
}
|
||||
}
|
||||
}
|
||||
out(" rts")
|
||||
}
|
||||
|
||||
@ -1174,33 +1281,41 @@ $counterVar .byte 0""")
|
||||
assemblyLines.add(assembly)
|
||||
}
|
||||
|
||||
internal fun signExtendAYlsb(valueDt: DataType) {
|
||||
// sign extend signed byte in A to full word in AY
|
||||
when(valueDt) {
|
||||
DataType.UBYTE -> out(" ldy #0")
|
||||
DataType.BYTE -> out(" jsr prog8_lib.sign_extend_AY_byte")
|
||||
else -> throw AssemblyError("need byte type")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun signExtendStackLsb(valueDt: DataType) {
|
||||
// sign extend signed byte on stack to signed word
|
||||
// sign extend signed byte on stack to signed word on stack
|
||||
when(valueDt) {
|
||||
DataType.UBYTE -> {
|
||||
out(" lda #0 | sta P8ESTACK_HI+1,x")
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
out("""
|
||||
lda P8ESTACK_LO+1,x
|
||||
ora #$7f
|
||||
bmi +
|
||||
lda #0
|
||||
+ sta P8ESTACK_HI+1,x""")
|
||||
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
|
||||
out(" stz P8ESTACK_HI+1,x")
|
||||
else
|
||||
out(" lda #0 | sta P8ESTACK_HI+1,x")
|
||||
}
|
||||
DataType.BYTE -> out(" jsr prog8_lib.sign_extend_stack_byte")
|
||||
else -> throw AssemblyError("need byte type")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun signExtendVariableLsb(asmvar: String, valueDt: DataType) {
|
||||
// sign extend signed byte in a word variable
|
||||
// sign extend signed byte in a var to a full word in that variable
|
||||
when(valueDt) {
|
||||
DataType.UBYTE -> {
|
||||
out(" lda #0 | sta $asmvar+1")
|
||||
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
|
||||
out(" stz $asmvar+1")
|
||||
else
|
||||
out(" lda #0 | sta $asmvar+1")
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
out("""
|
||||
lda $asmvar+1
|
||||
lda $asmvar
|
||||
ora #$7f
|
||||
bmi +
|
||||
lda #0
|
||||
@ -1209,4 +1324,92 @@ $counterVar .byte 0""")
|
||||
else -> throw AssemblyError("need byte type")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun isZpVar(scopedName: String): Boolean = scopedName in allocatedZeropageVariables
|
||||
|
||||
internal fun isZpVar(variable: IdentifierReference): Boolean {
|
||||
val vardecl = variable.targetVarDecl(program.namespace)!!
|
||||
return vardecl.makeScopedName(vardecl.name) in allocatedZeropageVariables
|
||||
}
|
||||
|
||||
internal fun pointerViaIndexRegisterPossible(pointerOffsetExpr: Expression): Pair<Expression, Expression>? {
|
||||
if(pointerOffsetExpr is BinaryExpression && pointerOffsetExpr.operator=="+") {
|
||||
val leftDt = pointerOffsetExpr.left.inferType(program)
|
||||
val rightDt = pointerOffsetExpr.left.inferType(program)
|
||||
if(leftDt.istype(DataType.UWORD) && rightDt.istype(DataType.UBYTE))
|
||||
return Pair(pointerOffsetExpr.left, pointerOffsetExpr.right)
|
||||
if(leftDt.istype(DataType.UBYTE) && rightDt.istype(DataType.UWORD))
|
||||
return Pair(pointerOffsetExpr.right, pointerOffsetExpr.left)
|
||||
if(leftDt.istype(DataType.UWORD) && rightDt.istype(DataType.UWORD)) {
|
||||
// could be that the index was a constant numeric byte but converted to word, check that
|
||||
val constIdx = pointerOffsetExpr.right.constValue(program)
|
||||
if(constIdx!=null && constIdx.number.toInt()>=0 && constIdx.number.toInt()<=255) {
|
||||
return Pair(pointerOffsetExpr.left, NumericLiteralValue(DataType.UBYTE, constIdx.number, constIdx.position))
|
||||
}
|
||||
// could be that the index was type casted into uword, check that
|
||||
val rightTc = pointerOffsetExpr.right as? TypecastExpression
|
||||
if(rightTc!=null && rightTc.expression.inferType(program).istype(DataType.UBYTE))
|
||||
return Pair(pointerOffsetExpr.left, rightTc.expression)
|
||||
val leftTc = pointerOffsetExpr.left as? TypecastExpression
|
||||
if(leftTc!=null && leftTc.expression.inferType(program).istype(DataType.UBYTE))
|
||||
return Pair(pointerOffsetExpr.right, leftTc.expression)
|
||||
}
|
||||
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
internal fun tryOptimizedPointerAccessWithA(expr: BinaryExpression, write: Boolean): Boolean {
|
||||
// optimize pointer,indexregister if possible
|
||||
if(expr.operator=="+") {
|
||||
val ptrAndIndex = pointerViaIndexRegisterPossible(expr)
|
||||
if(ptrAndIndex!=null) {
|
||||
val pointervar = ptrAndIndex.first as? IdentifierReference
|
||||
if(write) {
|
||||
when(ptrAndIndex.second) {
|
||||
is NumericLiteralValue, is IdentifierReference -> {
|
||||
if(pointervar!=null && isZpVar(pointervar)) {
|
||||
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
|
||||
out(" sta (${asmSymbolName(pointervar)}),y")
|
||||
} else {
|
||||
// copy the pointer var to zp first
|
||||
assignExpressionToVariable(ptrAndIndex.first, asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
|
||||
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
|
||||
out(" sta (P8ZP_SCRATCH_W2),y")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// same as above but we need to save the A register
|
||||
if(pointervar!=null && isZpVar(pointervar)) {
|
||||
out(" pha")
|
||||
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
|
||||
out(" pla")
|
||||
out(" sta (${asmSymbolName(pointervar)}),y")
|
||||
} else {
|
||||
// copy the pointer var to zp first
|
||||
assignExpressionToVariable(ptrAndIndex.first, asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
|
||||
out(" pha")
|
||||
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
|
||||
out(" pla")
|
||||
out(" sta (P8ZP_SCRATCH_W2),y")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(pointervar!=null && isZpVar(pointervar)) {
|
||||
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
|
||||
out(" lda (${asmSymbolName(pointervar)}),y")
|
||||
} else {
|
||||
// copy the pointer var to zp first
|
||||
assignExpressionToVariable(ptrAndIndex.first, asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
|
||||
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
|
||||
out(" lda (P8ZP_SCRATCH_W2),y")
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -75,11 +75,11 @@ private fun getLinesBy(lines: MutableList<String>, windowSize: Int) =
|
||||
|
||||
private fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||
// the when statement (on bytes) generates a sequence of:
|
||||
// lda $ce01,x
|
||||
// cmp #$20
|
||||
// lda $ce01,x
|
||||
// cmp #$20
|
||||
// beq check_prog8_s72choice_32
|
||||
// lda $ce01,x
|
||||
// cmp #$21
|
||||
// lda $ce01,x
|
||||
// cmp #$21
|
||||
// beq check_prog8_s73choice_33
|
||||
// the repeated lda can be removed
|
||||
val mods = mutableListOf<Modification>()
|
||||
@ -179,8 +179,8 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
||||
}
|
||||
|
||||
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||
// TODO not sure if this is correct in all situations....:
|
||||
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can be eliminated
|
||||
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can OFTEN be eliminated
|
||||
// TODO this is not true if X is not a regular RAM memory address (but instead mapped I/O or ROM)
|
||||
val mods = mutableListOf<Modification>()
|
||||
for (pair in linesByFour) {
|
||||
val first = pair[0].value.trimStart()
|
||||
@ -196,10 +196,14 @@ 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).trimStart()
|
||||
val secondLoc = second.substring(4).trimStart()
|
||||
if (firstLoc == secondLoc) {
|
||||
mods.add(Modification(pair[1].index, true, null))
|
||||
val third = pair[2].value.trimStart()
|
||||
if(!third.startsWith("b")) {
|
||||
// no branch instruction follows, we can potentiall remove the load instruction
|
||||
val firstLoc = first.substring(4).trimStart()
|
||||
val secondLoc = second.substring(4).trimStart()
|
||||
if (firstLoc == secondLoc) {
|
||||
mods.add(Modification(pair[1].index, true, null))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,79 +1,116 @@
|
||||
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.DirectMemoryWrite
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.c64.codegen.assignment.AsmAssignSource
|
||||
import prog8.compiler.target.c64.codegen.assignment.AsmAssignTarget
|
||||
import prog8.compiler.target.c64.codegen.assignment.AsmAssignment
|
||||
import prog8.compiler.target.c64.codegen.assignment.SourceStorageKind
|
||||
import prog8.compiler.target.c64.codegen.assignment.TargetStorageKind
|
||||
import prog8.compiler.target.subroutineFloatEvalResultVar2
|
||||
import prog8.compiler.toHex
|
||||
import prog8.functions.FSignature
|
||||
|
||||
internal class BuiltinFunctionsAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||
|
||||
internal fun translateFunctioncallExpression(fcall: FunctionCall, func: FSignature) {
|
||||
translateFunctioncall(fcall, func, false)
|
||||
internal fun translateFunctioncallExpression(fcall: FunctionCall, func: FSignature, resultToStack: Boolean) {
|
||||
translateFunctioncall(fcall, func, discardResult = false, resultToStack = resultToStack)
|
||||
}
|
||||
|
||||
internal fun translateFunctioncallStatement(fcall: FunctionCallStatement, func: FSignature) {
|
||||
translateFunctioncall(fcall, func, true)
|
||||
translateFunctioncall(fcall, func, discardResult = true, resultToStack = false)
|
||||
}
|
||||
|
||||
private fun translateFunctioncall(fcall: IFunctionCall, func: FSignature, discardResult: Boolean) {
|
||||
val functionName = fcall.target.nameInSource.last()
|
||||
if (discardResult) {
|
||||
if (func.pure)
|
||||
return // can just ignore the whole function call altogether
|
||||
else if (func.returntype != null)
|
||||
throw AssemblyError("discarding result of non-pure function $fcall")
|
||||
}
|
||||
private fun translateFunctioncall(fcall: IFunctionCall, func: FSignature, discardResult: Boolean, resultToStack: Boolean) {
|
||||
if (discardResult && func.pure)
|
||||
return // can just ignore the whole function call altogether
|
||||
|
||||
when (functionName) {
|
||||
"msb" -> funcMsb(fcall)
|
||||
"lsb" -> funcLsb(fcall)
|
||||
"mkword" -> funcMkword(fcall, func)
|
||||
"abs" -> funcAbs(fcall, func)
|
||||
if(discardResult && resultToStack)
|
||||
throw AssemblyError("cannot both discard the result AND put it onto stack")
|
||||
|
||||
val sscope = (fcall as Node).definingSubroutine()
|
||||
when (func.name) {
|
||||
"msb" -> funcMsb(fcall, resultToStack)
|
||||
"lsb" -> funcLsb(fcall, resultToStack)
|
||||
"mkword" -> funcMkword(fcall, resultToStack)
|
||||
"abs" -> funcAbs(fcall, func, resultToStack, sscope)
|
||||
"swap" -> funcSwap(fcall)
|
||||
"strlen" -> funcStrlen(fcall)
|
||||
"min", "max", "sum" -> funcMinMaxSum(fcall, functionName)
|
||||
"any", "all" -> funcAnyAll(fcall, functionName)
|
||||
"sgn" -> funcSgn(fcall, func)
|
||||
"min", "max" -> funcMinMax(fcall, func, resultToStack)
|
||||
"sum" -> funcSum(fcall, resultToStack)
|
||||
"any", "all" -> funcAnyAll(fcall, func, resultToStack)
|
||||
"sin8", "sin8u", "sin16", "sin16u",
|
||||
"cos8", "cos8u", "cos16", "cos16u" -> funcSinCosInt(fcall, func, resultToStack, sscope)
|
||||
"sgn" -> funcSgn(fcall, func, resultToStack, sscope)
|
||||
"sin", "cos", "tan", "atan",
|
||||
"ln", "log2", "sqrt", "rad",
|
||||
"deg", "round", "floor", "ceil",
|
||||
"rdnf" -> funcVariousFloatFuncs(fcall, func, functionName)
|
||||
"rndf" -> funcVariousFloatFuncs(fcall, func, resultToStack, sscope)
|
||||
"rnd", "rndw" -> funcRnd(func, resultToStack)
|
||||
"sqrt16" -> funcSqrt16(fcall, func, resultToStack, sscope)
|
||||
"rol" -> funcRol(fcall)
|
||||
"rol2" -> funcRol2(fcall)
|
||||
"ror" -> funcRor(fcall)
|
||||
"ror2" -> funcRor2(fcall)
|
||||
"sort" -> funcSort(fcall)
|
||||
"reverse" -> funcReverse(fcall)
|
||||
"rsave" -> {
|
||||
// save cpu status flag and all registers A, X, Y.
|
||||
// see http://6502.org/tutorials/register_preservation.html
|
||||
asmgen.out(" php | sta P8ZP_SCRATCH_REG | pha | txa | pha | tya | pha | lda P8ZP_SCRATCH_REG")
|
||||
}
|
||||
"rrestore" -> {
|
||||
// restore all registers and cpu status flag
|
||||
asmgen.out(" pla | tay | pla | tax | pla | plp")
|
||||
}
|
||||
"clear_carry" -> asmgen.out(" clc")
|
||||
"set_carry" -> asmgen.out(" sec")
|
||||
"clear_irqd" -> asmgen.out(" cli")
|
||||
"set_irqd" -> asmgen.out(" sei")
|
||||
else -> {
|
||||
translateFunctionArguments(fcall.args, func)
|
||||
asmgen.out(" jsr prog8_lib.func_$functionName")
|
||||
}
|
||||
"memory" -> funcMemory(fcall, discardResult, resultToStack)
|
||||
else -> TODO("missing asmgen for builtin func ${func.name}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcMemory(fcall: IFunctionCall, discardResult: Boolean, resultToStack: Boolean) {
|
||||
if(discardResult || fcall !is FunctionCall)
|
||||
throw AssemblyError("should not discard result of memory allocation at $fcall")
|
||||
val scope = fcall.definingScope()
|
||||
val nameRef = fcall.args[0] as IdentifierReference
|
||||
val name = (nameRef.targetVarDecl(program.namespace)!!.value as StringLiteralValue).value
|
||||
val size = (fcall.args[1] as NumericLiteralValue).number.toInt()
|
||||
|
||||
val existingSize = asmgen.slabs[name]
|
||||
if(existingSize!=null && existingSize!=size)
|
||||
throw AssemblyError("memory slab '$name' already exists with a different size ($size) at ${fcall.position}")
|
||||
|
||||
val slabname = IdentifierReference(listOf("prog8_slabs", name), fcall.position)
|
||||
slabname.linkParents(fcall)
|
||||
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UWORD, expression = AddressOf(slabname, fcall.position))
|
||||
val target =
|
||||
if(resultToStack)
|
||||
AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, DataType.UWORD, null)
|
||||
else
|
||||
AsmAssignTarget.fromRegisters(RegisterOrPair.AY, null, program, asmgen)
|
||||
val assign = AsmAssignment(src, target, false, fcall.position)
|
||||
asmgen.translateNormalAssignment(assign)
|
||||
|
||||
// remove the variable for the name, it's not used as a variable only as a tag for the assembler.
|
||||
val nameDecl = scope.statements.single { it is VarDecl && it.name==nameRef.nameInSource.single() }
|
||||
asmgen.removals.add(Pair(nameDecl, scope))
|
||||
asmgen.slabs[name] = size
|
||||
}
|
||||
|
||||
private fun funcSqrt16(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, scope: Subroutine?) {
|
||||
translateArguments(fcall.args, func, scope)
|
||||
if(resultToStack)
|
||||
asmgen.out(" jsr prog8_lib.func_sqrt16_stack")
|
||||
else
|
||||
asmgen.out(" jsr prog8_lib.func_sqrt16_into_A")
|
||||
}
|
||||
|
||||
private fun funcSinCosInt(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, scope: Subroutine?) {
|
||||
translateArguments(fcall.args, func, scope)
|
||||
if(resultToStack)
|
||||
asmgen.out(" jsr prog8_lib.func_${func.name}_stack")
|
||||
else
|
||||
when(func.name) {
|
||||
"sin8", "sin8u", "cos8", "cos8u" -> asmgen.out(" jsr prog8_lib.func_${func.name}_into_A")
|
||||
"sin16", "sin16u", "cos16", "cos16u" -> asmgen.out(" jsr prog8_lib.func_${func.name}_into_AY")
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcReverse(fcall: IFunctionCall) {
|
||||
val variable = fcall.args.single()
|
||||
if (variable is IdentifierReference) {
|
||||
@ -83,33 +120,30 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
when (decl.datatype) {
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||
asmgen.out("""
|
||||
lda #<$varName
|
||||
ldy #>$varName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements
|
||||
jsr prog8_lib.reverse_b
|
||||
""")
|
||||
lda #<$varName
|
||||
ldy #>$varName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements
|
||||
jsr prog8_lib.func_reverse_b""")
|
||||
}
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
asmgen.out("""
|
||||
lda #<$varName
|
||||
ldy #>$varName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements
|
||||
jsr prog8_lib.reverse_w
|
||||
""")
|
||||
lda #<$varName
|
||||
ldy #>$varName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements
|
||||
jsr prog8_lib.func_reverse_w""")
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
asmgen.out("""
|
||||
lda #<$varName
|
||||
ldy #>$varName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements
|
||||
jsr prog8_lib.reverse_f
|
||||
""")
|
||||
lda #<$varName
|
||||
ldy #>$varName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements
|
||||
jsr floats.func_reverse_f""")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
@ -125,25 +159,21 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
when (decl.datatype) {
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||
asmgen.out("""
|
||||
lda #<$varName
|
||||
ldy #>$varName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements
|
||||
sta P8ZP_SCRATCH_B1
|
||||
""")
|
||||
asmgen.out(if (decl.datatype == DataType.ARRAY_UB) " jsr prog8_lib.sort_ub" else " jsr prog8_lib.sort_b")
|
||||
lda #<$varName
|
||||
ldy #>$varName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements""")
|
||||
asmgen.out(if (decl.datatype == DataType.ARRAY_UB) " jsr prog8_lib.func_sort_ub" else " jsr prog8_lib.func_sort_b")
|
||||
}
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
asmgen.out("""
|
||||
lda #<$varName
|
||||
ldy #>$varName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements
|
||||
sta P8ZP_SCRATCH_B1
|
||||
""")
|
||||
asmgen.out(if (decl.datatype == DataType.ARRAY_UW) " jsr prog8_lib.sort_uw" else " jsr prog8_lib.sort_w")
|
||||
lda #<$varName
|
||||
ldy #>$varName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements""")
|
||||
asmgen.out(if (decl.datatype == DataType.ARRAY_UW) " jsr prog8_lib.func_sort_uw" else " jsr prog8_lib.func_sort_w")
|
||||
}
|
||||
DataType.ARRAY_F -> throw AssemblyError("sorting of floating point array is not supported")
|
||||
else -> throw AssemblyError("weird type")
|
||||
@ -159,8 +189,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
DataType.UBYTE -> {
|
||||
when (what) {
|
||||
is ArrayIndexedExpression -> {
|
||||
asmgen.translateExpression(what.arrayvar)
|
||||
asmgen.translateExpression(what.indexer)
|
||||
translateRolRorArrayArgs(what.arrayvar, what.indexer, "ror2", 'b')
|
||||
asmgen.out(" jsr prog8_lib.ror2_array_ub")
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
@ -168,7 +197,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
val number = (what.addressExpression as NumericLiteralValue).number
|
||||
asmgen.out(" lda ${number.toHex()} | lsr a | bcc + | ora #\$80 |+ | sta ${number.toHex()}")
|
||||
} else {
|
||||
asmgen.translateExpression(what.addressExpression)
|
||||
asmgen.assignExpressionToRegister(what.addressExpression, RegisterOrPair.AY)
|
||||
asmgen.out(" jsr prog8_lib.ror2_mem_ub")
|
||||
}
|
||||
}
|
||||
@ -182,8 +211,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
DataType.UWORD -> {
|
||||
when (what) {
|
||||
is ArrayIndexedExpression -> {
|
||||
asmgen.translateExpression(what.arrayvar)
|
||||
asmgen.translateExpression(what.indexer)
|
||||
translateRolRorArrayArgs(what.arrayvar, what.indexer, "ror2", 'w')
|
||||
asmgen.out(" jsr prog8_lib.ror2_array_uw")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
@ -204,8 +232,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
DataType.UBYTE -> {
|
||||
when (what) {
|
||||
is ArrayIndexedExpression -> {
|
||||
asmgen.translateExpression(what.arrayvar)
|
||||
asmgen.translateExpression(what.indexer)
|
||||
translateRolRorArrayArgs(what.arrayvar, what.indexer, "ror", 'b')
|
||||
asmgen.out(" jsr prog8_lib.ror_array_ub")
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
@ -213,15 +240,23 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
val number = (what.addressExpression as NumericLiteralValue).number
|
||||
asmgen.out(" ror ${number.toHex()}")
|
||||
} else {
|
||||
asmgen.translateExpression(what.addressExpression)
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
sta (+) + 1
|
||||
lda P8ESTACK_HI,x
|
||||
sta (+) + 2
|
||||
+ ror ${'$'}ffff ; modified
|
||||
""")
|
||||
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.addressExpression)
|
||||
if(ptrAndIndex!=null) {
|
||||
asmgen.assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.X)
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as FunctionCallStatement).definingSubroutine()!!)
|
||||
asmgen.assignExpressionToRegister(ptrAndIndex.first, RegisterOrPair.AY)
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
asmgen.out("""
|
||||
sta (+) + 1
|
||||
sty (+) + 2
|
||||
+ ror ${'$'}ffff,x ; modified""")
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(what.addressExpression, RegisterOrPair.AY)
|
||||
asmgen.out("""
|
||||
sta (+) + 1
|
||||
sty (+) + 2
|
||||
+ ror ${'$'}ffff ; modified""")
|
||||
}
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
@ -234,8 +269,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
DataType.UWORD -> {
|
||||
when (what) {
|
||||
is ArrayIndexedExpression -> {
|
||||
asmgen.translateExpression(what.arrayvar)
|
||||
asmgen.translateExpression(what.indexer)
|
||||
translateRolRorArrayArgs(what.arrayvar, what.indexer, "ror", 'w')
|
||||
asmgen.out(" jsr prog8_lib.ror_array_uw")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
@ -256,8 +290,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
DataType.UBYTE -> {
|
||||
when (what) {
|
||||
is ArrayIndexedExpression -> {
|
||||
asmgen.translateExpression(what.arrayvar)
|
||||
asmgen.translateExpression(what.indexer)
|
||||
translateRolRorArrayArgs(what.arrayvar, what.indexer, "rol2", 'b')
|
||||
asmgen.out(" jsr prog8_lib.rol2_array_ub")
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
@ -265,7 +298,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
val number = (what.addressExpression as NumericLiteralValue).number
|
||||
asmgen.out(" lda ${number.toHex()} | cmp #\$80 | rol a | sta ${number.toHex()}")
|
||||
} else {
|
||||
asmgen.translateExpression(what.addressExpression)
|
||||
asmgen.assignExpressionToRegister(what.addressExpression, RegisterOrPair.AY)
|
||||
asmgen.out(" jsr prog8_lib.rol2_mem_ub")
|
||||
}
|
||||
}
|
||||
@ -279,8 +312,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
DataType.UWORD -> {
|
||||
when (what) {
|
||||
is ArrayIndexedExpression -> {
|
||||
asmgen.translateExpression(what.arrayvar)
|
||||
asmgen.translateExpression(what.indexer)
|
||||
translateRolRorArrayArgs(what.arrayvar, what.indexer, "rol2", 'w')
|
||||
asmgen.out(" jsr prog8_lib.rol2_array_uw")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
@ -301,8 +333,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
DataType.UBYTE -> {
|
||||
when (what) {
|
||||
is ArrayIndexedExpression -> {
|
||||
asmgen.translateExpression(what.arrayvar)
|
||||
asmgen.translateExpression(what.indexer)
|
||||
translateRolRorArrayArgs(what.arrayvar, what.indexer, "rol", 'b')
|
||||
asmgen.out(" jsr prog8_lib.rol_array_ub")
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
@ -310,15 +341,23 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
val number = (what.addressExpression as NumericLiteralValue).number
|
||||
asmgen.out(" rol ${number.toHex()}")
|
||||
} else {
|
||||
asmgen.translateExpression(what.addressExpression)
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
sta (+) + 1
|
||||
lda P8ESTACK_HI,x
|
||||
sta (+) + 2
|
||||
+ rol ${'$'}ffff ; modified
|
||||
""")
|
||||
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.addressExpression)
|
||||
if(ptrAndIndex!=null) {
|
||||
asmgen.assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.X)
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as FunctionCallStatement).definingSubroutine()!!)
|
||||
asmgen.assignExpressionToRegister(ptrAndIndex.first, RegisterOrPair.AY)
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
asmgen.out("""
|
||||
sta (+) + 1
|
||||
sty (+) + 2
|
||||
+ rol ${'$'}ffff,x ; modified""")
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(what.addressExpression, RegisterOrPair.AY)
|
||||
asmgen.out("""
|
||||
sta (+) + 1
|
||||
sty (+) + 2
|
||||
+ rol ${'$'}ffff ; modified""")
|
||||
}
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
@ -331,8 +370,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
DataType.UWORD -> {
|
||||
when (what) {
|
||||
is ArrayIndexedExpression -> {
|
||||
asmgen.translateExpression(what.arrayvar)
|
||||
asmgen.translateExpression(what.indexer)
|
||||
translateRolRorArrayArgs(what.arrayvar, what.indexer, "rol", 'w')
|
||||
asmgen.out(" jsr prog8_lib.rol_array_uw")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
@ -346,65 +384,109 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcVariousFloatFuncs(fcall: IFunctionCall, func: FSignature, functionName: String) {
|
||||
translateFunctionArguments(fcall.args, func)
|
||||
asmgen.out(" jsr floats.func_$functionName")
|
||||
private fun translateRolRorArrayArgs(arrayvar: IdentifierReference, indexer: ArrayIndex, operation: String, dt: Char) {
|
||||
asmgen.assignExpressionToVariable(AddressOf(arrayvar, arrayvar.position), "prog8_lib.${operation}_array_u${dt}._arg_target", DataType.UWORD, null)
|
||||
val indexerExpr = if(indexer.indexVar!=null) indexer.indexVar!! else indexer.indexNum!!
|
||||
asmgen.assignExpressionToVariable(indexerExpr, "prog8_lib.${operation}_array_u${dt}._arg_index", DataType.UBYTE, null)
|
||||
}
|
||||
|
||||
private fun funcSgn(fcall: IFunctionCall, func: FSignature) {
|
||||
translateFunctionArguments(fcall.args, func)
|
||||
private fun funcVariousFloatFuncs(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, scope: Subroutine?) {
|
||||
translateArguments(fcall.args, func, scope)
|
||||
if(resultToStack)
|
||||
asmgen.out(" jsr floats.func_${func.name}_stack")
|
||||
else
|
||||
asmgen.out(" jsr floats.func_${func.name}_fac1")
|
||||
}
|
||||
|
||||
private fun funcSgn(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, scope: Subroutine?) {
|
||||
translateArguments(fcall.args, func, scope)
|
||||
val dt = fcall.args.single().inferType(program)
|
||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||
DataType.UBYTE -> asmgen.out(" jsr math.sign_ub")
|
||||
DataType.BYTE -> asmgen.out(" jsr math.sign_b")
|
||||
DataType.UWORD -> asmgen.out(" jsr math.sign_uw")
|
||||
DataType.WORD -> asmgen.out(" jsr math.sign_w")
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.sign_f")
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
if(resultToStack) {
|
||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.func_sign_ub_stack")
|
||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.func_sign_b_stack")
|
||||
DataType.UWORD -> asmgen.out(" jsr prog8_lib.func_sign_uw_stack")
|
||||
DataType.WORD -> asmgen.out(" jsr prog8_lib.func_sign_w_stack")
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.func_sign_f_stack")
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
} else {
|
||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.func_sign_ub_into_A")
|
||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.func_sign_b_into_A")
|
||||
DataType.UWORD -> asmgen.out(" jsr prog8_lib.func_sign_uw_into_A")
|
||||
DataType.WORD -> asmgen.out(" jsr prog8_lib.func_sign_w_into_A")
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.func_sign_f_into_A")
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcAnyAll(fcall: IFunctionCall, functionName: String) {
|
||||
outputPushAddressAndLenghtOfArray(fcall.args[0])
|
||||
private fun funcAnyAll(fcall: IFunctionCall, function: FSignature, resultToStack: Boolean) {
|
||||
outputAddressAndLenghtOfArray(fcall.args[0])
|
||||
val dt = fcall.args.single().inferType(program)
|
||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${functionName}_b")
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${functionName}_w")
|
||||
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${functionName}_f")
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
if(resultToStack) {
|
||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${function.name}_b_stack")
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${function.name}_w_stack")
|
||||
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_stack")
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
} else {
|
||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${function.name}_b_into_A")
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${function.name}_w_into_A")
|
||||
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_into_A")
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcMinMaxSum(fcall: IFunctionCall, functionName: String) {
|
||||
outputPushAddressAndLenghtOfArray(fcall.args[0])
|
||||
private fun funcMinMax(fcall: IFunctionCall, function: FSignature, resultToStack: Boolean) {
|
||||
outputAddressAndLenghtOfArray(fcall.args[0])
|
||||
val dt = fcall.args.single().inferType(program)
|
||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||
DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${functionName}_ub")
|
||||
DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_${functionName}_b")
|
||||
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_${functionName}_uw")
|
||||
DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${functionName}_w")
|
||||
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${functionName}_f")
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
if(resultToStack) {
|
||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||
DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${function.name}_ub_stack")
|
||||
DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_${function.name}_b_stack")
|
||||
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_${function.name}_uw_stack")
|
||||
DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${function.name}_w_stack")
|
||||
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_stack")
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
} else {
|
||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||
DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${function.name}_ub_into_A")
|
||||
DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_${function.name}_b_into_A")
|
||||
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_${function.name}_uw_into_AY")
|
||||
DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${function.name}_w_into_AY")
|
||||
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_fac1")
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcStrlen(fcall: IFunctionCall) {
|
||||
val name = asmgen.asmVariableName(fcall.args[0] as IdentifierReference)
|
||||
val type = fcall.args[0].inferType(program)
|
||||
when {
|
||||
type.istype(DataType.STR) -> asmgen.out("""
|
||||
lda #<$name
|
||||
ldy #>$name
|
||||
jsr prog8_lib.strlen
|
||||
sta P8ESTACK_LO,x
|
||||
dex""")
|
||||
type.istype(DataType.UWORD) -> asmgen.out("""
|
||||
lda $name
|
||||
ldy $name+1
|
||||
jsr prog8_lib.strlen
|
||||
sta P8ESTACK_LO,x
|
||||
dex""")
|
||||
else -> throw AssemblyError("strlen requires str or uword arg")
|
||||
private fun funcSum(fcall: IFunctionCall, resultToStack: Boolean) {
|
||||
outputAddressAndLenghtOfArray(fcall.args[0])
|
||||
val dt = fcall.args.single().inferType(program)
|
||||
if(resultToStack) {
|
||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||
DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_sum_ub_stack")
|
||||
DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_sum_b_stack")
|
||||
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_sum_uw_stack")
|
||||
DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_sum_w_stack")
|
||||
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_sum_f_stack")
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
} else {
|
||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||
DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_sum_ub_into_AY")
|
||||
DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_sum_b_into_AY")
|
||||
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_sum_uw_into_AY")
|
||||
DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_sum_w_into_AY")
|
||||
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_sum_f_fac1")
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -444,7 +526,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda #>$secondName
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
jsr floats.swap_floats
|
||||
jsr floats.func_swap_f
|
||||
""")
|
||||
return
|
||||
}
|
||||
@ -452,6 +534,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
|
||||
// optimized simple case: swap two memory locations
|
||||
if(first is DirectMemoryRead && second is DirectMemoryRead) {
|
||||
// TODO optimize swap of two memread values with index, using the same pointer expression/variable, like swap(@(ptr+1), @(ptr+2))
|
||||
val addr1 = (first.addressExpression as? NumericLiteralValue)?.number?.toHex()
|
||||
val addr2 = (second.addressExpression as? NumericLiteralValue)?.number?.toHex()
|
||||
val name1 = if(first.addressExpression is IdentifierReference) asmgen.asmVariableName(first.addressExpression as IdentifierReference) else null
|
||||
@ -480,7 +563,10 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
if(first is ArrayIndexedExpression && second is ArrayIndexedExpression) {
|
||||
val arrayVarName1 = asmgen.asmVariableName(first.arrayvar)
|
||||
val arrayVarName2 = asmgen.asmVariableName(second.arrayvar)
|
||||
val elementDt = first.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
val elementIDt = first.inferType(program)
|
||||
if(!elementIDt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val elementDt = elementIDt.typeOrElse(DataType.STRUCT)
|
||||
|
||||
val firstNum = first.indexer.indexNum
|
||||
val firstVar = first.indexer.indexVar
|
||||
@ -502,7 +588,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
}
|
||||
}
|
||||
|
||||
// all other types of swap() calls are done via the evaluation stack
|
||||
// all other types of swap() calls are done via a temporary variable
|
||||
|
||||
fun targetFromExpr(expr: Expression, datatype: DataType): AsmAssignTarget {
|
||||
return when (expr) {
|
||||
is IdentifierReference -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, datatype, expr.definingSubroutine(), variableAsmName = asmgen.asmVariableName(expr))
|
||||
@ -512,21 +599,43 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
}
|
||||
}
|
||||
|
||||
asmgen.translateExpression(first)
|
||||
asmgen.translateExpression(second)
|
||||
val datatype = first.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
val assignFirst = AsmAssignment(
|
||||
AsmAssignSource(SourceStorageKind.STACK, program, asmgen, datatype),
|
||||
targetFromExpr(first, datatype),
|
||||
false, first.position
|
||||
)
|
||||
val assignSecond = AsmAssignment(
|
||||
AsmAssignSource(SourceStorageKind.STACK, program, asmgen, datatype),
|
||||
targetFromExpr(second, datatype),
|
||||
false, second.position
|
||||
)
|
||||
asmgen.translateNormalAssignment(assignFirst)
|
||||
asmgen.translateNormalAssignment(assignSecond)
|
||||
when(datatype) {
|
||||
in ByteDatatypes, in WordDatatypes -> {
|
||||
asmgen.assignExpressionToVariable(first, "P8ZP_SCRATCH_W1", datatype, null)
|
||||
asmgen.assignExpressionToVariable(second, "P8ZP_SCRATCH_W2", datatype, null)
|
||||
val assignFirst = AsmAssignment(
|
||||
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, datatype, variableAsmName = "P8ZP_SCRATCH_W2"),
|
||||
targetFromExpr(first, datatype),
|
||||
false, first.position
|
||||
)
|
||||
val assignSecond = AsmAssignment(
|
||||
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, datatype, variableAsmName = "P8ZP_SCRATCH_W1"),
|
||||
targetFromExpr(second, datatype),
|
||||
false, second.position
|
||||
)
|
||||
asmgen.translateNormalAssignment(assignFirst)
|
||||
asmgen.translateNormalAssignment(assignSecond)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
// via evaluation stack
|
||||
asmgen.translateExpression(first)
|
||||
asmgen.translateExpression(second)
|
||||
val assignFirst = AsmAssignment(
|
||||
AsmAssignSource(SourceStorageKind.STACK, program, asmgen, DataType.FLOAT),
|
||||
targetFromExpr(first, datatype),
|
||||
false, first.position
|
||||
)
|
||||
val assignSecond = AsmAssignment(
|
||||
AsmAssignSource(SourceStorageKind.STACK, program, asmgen, DataType.FLOAT),
|
||||
targetFromExpr(second, datatype),
|
||||
false, second.position
|
||||
)
|
||||
asmgen.translateNormalAssignment(assignFirst)
|
||||
asmgen.translateNormalAssignment(assignSecond)
|
||||
}
|
||||
else -> throw AssemblyError("weird swap dt")
|
||||
}
|
||||
}
|
||||
|
||||
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexValue1: NumericLiteralValue, arrayVarName2: String, indexValue2: NumericLiteralValue) {
|
||||
@ -759,25 +868,52 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcAbs(fcall: IFunctionCall, func: FSignature) {
|
||||
translateFunctionArguments(fcall.args, func)
|
||||
val dt = fcall.args.single().inferType(program)
|
||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.abs_b")
|
||||
in WordDatatypes -> asmgen.out(" jsr prog8_lib.abs_w")
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.abs_f")
|
||||
else -> throw AssemblyError("weird type")
|
||||
private fun funcAbs(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, scope: Subroutine?) {
|
||||
translateArguments(fcall.args, func, scope)
|
||||
val dt = fcall.args.single().inferType(program).typeOrElse(DataType.STRUCT)
|
||||
if(resultToStack) {
|
||||
when (dt) {
|
||||
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.abs_b_stack")
|
||||
in WordDatatypes -> asmgen.out(" jsr prog8_lib.abs_w_stack")
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.abs_f_stack")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
} else {
|
||||
when (dt) {
|
||||
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.abs_b_into_A")
|
||||
in WordDatatypes -> asmgen.out(" jsr prog8_lib.abs_w_into_AY")
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.abs_f_fac1")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcMkword(fcall: IFunctionCall, func: FSignature) {
|
||||
// trick: push the args in reverse order (msb first, lsb second) this saves some instructions
|
||||
asmgen.translateExpression(fcall.args[1])
|
||||
asmgen.translateExpression(fcall.args[0])
|
||||
asmgen.out(" inx | lda P8ESTACK_LO,x | sta P8ESTACK_HI+1,x")
|
||||
private fun funcRnd(func: FSignature, resultToStack: Boolean) {
|
||||
when(func.name) {
|
||||
"rnd" -> {
|
||||
if(resultToStack)
|
||||
asmgen.out(" jsr prog8_lib.func_rnd_stack")
|
||||
else
|
||||
asmgen.out(" jsr math.randbyte")
|
||||
}
|
||||
"rndw" -> {
|
||||
if(resultToStack)
|
||||
asmgen.out(" jsr prog8_lib.func_rndw_stack")
|
||||
else
|
||||
asmgen.out(" jsr math.randword")
|
||||
}
|
||||
else -> throw AssemblyError("wrong func")
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcMsb(fcall: IFunctionCall) {
|
||||
private fun funcMkword(fcall: IFunctionCall, resultToStack: Boolean) {
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.Y) // msb
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
|
||||
if(resultToStack)
|
||||
asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
|
||||
}
|
||||
|
||||
private fun funcMsb(fcall: IFunctionCall, resultToStack: Boolean) {
|
||||
val arg = fcall.args.single()
|
||||
if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes)
|
||||
throw AssemblyError("msb required word argument")
|
||||
@ -785,47 +921,120 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
throw AssemblyError("msb(const) should have been const-folded away")
|
||||
if (arg is IdentifierReference) {
|
||||
val sourceName = asmgen.asmVariableName(arg)
|
||||
asmgen.out(" lda $sourceName+1 | sta P8ESTACK_LO,x | dex")
|
||||
asmgen.out(" lda $sourceName+1")
|
||||
if (resultToStack)
|
||||
asmgen.out(" sta P8ESTACK_LO,x | dex")
|
||||
} else {
|
||||
asmgen.translateExpression(arg)
|
||||
asmgen.out(" lda P8ESTACK_HI+1,x | sta P8ESTACK_LO+1,x")
|
||||
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
|
||||
if (resultToStack)
|
||||
asmgen.out(" tya | sta P8ESTACK_LO,x | dex")
|
||||
else
|
||||
asmgen.out(" tya")
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcLsb(fcall: IFunctionCall) {
|
||||
private fun funcLsb(fcall: IFunctionCall, resultToStack: Boolean) {
|
||||
val arg = fcall.args.single()
|
||||
if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes)
|
||||
throw AssemblyError("lsb required word argument")
|
||||
if (arg is NumericLiteralValue)
|
||||
throw AssemblyError("lsb(const) should have been const-folded away")
|
||||
|
||||
if (arg is IdentifierReference) {
|
||||
val sourceName = asmgen.asmVariableName(arg)
|
||||
asmgen.out(" lda $sourceName | sta P8ESTACK_LO,x | dex")
|
||||
asmgen.out(" lda $sourceName")
|
||||
if (resultToStack)
|
||||
asmgen.out(" sta P8ESTACK_LO,x | dex")
|
||||
} else {
|
||||
asmgen.translateExpression(arg)
|
||||
// just ignore any high-byte
|
||||
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
|
||||
// NOTE: we rely on the fact that the above assignment to AY, assigns the Lsb to A as the last instruction.
|
||||
// this is required because the compiler assumes the status bits are set according to what A is (lsb)
|
||||
// and will not generate another cmp when lsb() is directly used inside a comparison expression.
|
||||
if (resultToStack)
|
||||
asmgen.out(" sta P8ESTACK_LO,x | dex")
|
||||
}
|
||||
}
|
||||
|
||||
private fun outputPushAddressAndLenghtOfArray(arg: Expression) {
|
||||
private fun outputAddressAndLenghtOfArray(arg: Expression) {
|
||||
// address in P8ZP_SCRATCH_W1, number of elements in A
|
||||
arg as IdentifierReference
|
||||
val identifierName = asmgen.asmVariableName(arg)
|
||||
val size = arg.targetVarDecl(program.namespace)!!.arraysize!!.constIndex()!!
|
||||
asmgen.out("""
|
||||
lda #<$identifierName
|
||||
sta P8ESTACK_LO,x
|
||||
lda #>$identifierName
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
ldy #>$identifierName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$size
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
""")
|
||||
}
|
||||
|
||||
private fun translateFunctionArguments(args: MutableList<Expression>, signature: FSignature) {
|
||||
args.forEach {
|
||||
asmgen.translateExpression(it)
|
||||
private fun translateArguments(args: MutableList<Expression>, signature: FSignature, scope: Subroutine?) {
|
||||
val callConv = signature.callConvention(args.map { it.inferType(program).typeOrElse(DataType.STRUCT) })
|
||||
|
||||
fun getSourceForFloat(value: Expression): AsmAssignSource {
|
||||
return when (value) {
|
||||
is IdentifierReference -> {
|
||||
val addr = AddressOf(value, value.position)
|
||||
AsmAssignSource.fromAstSource(addr, program, asmgen)
|
||||
}
|
||||
is NumericLiteralValue -> {
|
||||
throw AssemblyError("float literals should have been converted into autovar")
|
||||
}
|
||||
else -> {
|
||||
if(scope==null)
|
||||
throw AssemblyError("cannot use float arguments outside of a subroutine scope")
|
||||
|
||||
scope.asmGenInfo.usedFloatEvalResultVar2 = true
|
||||
val variable = IdentifierReference(listOf(subroutineFloatEvalResultVar2), value.position)
|
||||
val addr = AddressOf(variable, value.position)
|
||||
addr.linkParents(value)
|
||||
asmgen.assignExpressionToVariable(value, asmgen.asmVariableName(variable), DataType.FLOAT, scope)
|
||||
AsmAssignSource.fromAstSource(addr, program, asmgen)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
args.zip(callConv.params).zip(signature.parameters).forEach {
|
||||
val paramName = it.second.name
|
||||
val conv = it.first.second
|
||||
val value = it.first.first
|
||||
when {
|
||||
conv.variable -> {
|
||||
val varname = "prog8_lib.func_${signature.name}._arg_${paramName}"
|
||||
val src = when (conv.dt) {
|
||||
DataType.FLOAT -> getSourceForFloat(value)
|
||||
in PassByReferenceDatatypes -> {
|
||||
// put the address of the argument in AY
|
||||
val addr = AddressOf(value as IdentifierReference, value.position)
|
||||
AsmAssignSource.fromAstSource(addr, program, asmgen)
|
||||
}
|
||||
else -> {
|
||||
AsmAssignSource.fromAstSource(value, program, asmgen)
|
||||
}
|
||||
}
|
||||
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, conv.dt, null, variableAsmName = varname)
|
||||
val assign = AsmAssignment(src, tgt, false, value.position)
|
||||
asmgen.translateNormalAssignment(assign)
|
||||
}
|
||||
conv.reg != null -> {
|
||||
val src = when (conv.dt) {
|
||||
DataType.FLOAT -> getSourceForFloat(value)
|
||||
in PassByReferenceDatatypes -> {
|
||||
// put the address of the argument in AY
|
||||
val addr = AddressOf(value as IdentifierReference, value.position)
|
||||
AsmAssignSource.fromAstSource(addr, program, asmgen)
|
||||
}
|
||||
else -> {
|
||||
AsmAssignSource.fromAstSource(value, program, asmgen)
|
||||
}
|
||||
}
|
||||
val tgt = AsmAssignTarget.fromRegisters(conv.reg, null, program, asmgen)
|
||||
val assign = AsmAssignment(src, tgt, false, value.position)
|
||||
asmgen.translateNormalAssignment(assign)
|
||||
}
|
||||
else -> throw AssemblyError("callconv")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,15 +1,13 @@
|
||||
package prog8.compiler.target.c64.codegen
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.ArrayElementTypes
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.RegisterOrPair
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.RangeExpr
|
||||
import prog8.ast.statements.ForLoop
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.c64.codegen.assignment.AsmAssignSource
|
||||
import prog8.compiler.target.c64.codegen.assignment.AsmAssignTarget
|
||||
import prog8.compiler.target.c64.codegen.assignment.AsmAssignment
|
||||
import prog8.compiler.target.c64.codegen.assignment.TargetStorageKind
|
||||
import prog8.compiler.toHex
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
@ -18,7 +16,7 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
|
||||
internal fun translate(stmt: ForLoop) {
|
||||
val iterableDt = stmt.iterable.inferType(program)
|
||||
if(!iterableDt.isKnown)
|
||||
throw AssemblyError("can't determine iterable dt")
|
||||
throw AssemblyError("unknown dt")
|
||||
when(stmt.iterable) {
|
||||
is RangeExpr -> {
|
||||
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange()
|
||||
@ -51,23 +49,17 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
|
||||
val incdec = if(stepsize==1) "inc" else "dec"
|
||||
// loop over byte range via loopvar
|
||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||
asmgen.translateExpression(range.to)
|
||||
asmgen.translateExpression(range.from)
|
||||
asmgen.assignExpressionToVariable(range.from, varname, ArrayElementTypes.getValue(iterableDt), null)
|
||||
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayElementTypes.getValue(iterableDt), null)
|
||||
asmgen.out(loopLabel)
|
||||
asmgen.translate(stmt.body)
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
sta $varname
|
||||
lda P8ESTACK_LO+1,x
|
||||
sta $modifiedLabel+1
|
||||
$loopLabel""")
|
||||
asmgen.translate(stmt.body)
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
$modifiedLabel cmp #0 ; modified
|
||||
beq $endLabel
|
||||
$incdec $varname
|
||||
jmp $loopLabel
|
||||
$endLabel inx""")
|
||||
lda $varname
|
||||
$modifiedLabel cmp #0 ; modified
|
||||
beq $endLabel
|
||||
$incdec $varname
|
||||
jmp $loopLabel
|
||||
$endLabel""")
|
||||
|
||||
} else {
|
||||
|
||||
@ -75,36 +67,29 @@ $endLabel inx""")
|
||||
|
||||
// loop over byte range via loopvar
|
||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||
asmgen.translateExpression(range.to)
|
||||
asmgen.translateExpression(range.from)
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
sta $varname
|
||||
lda P8ESTACK_LO+1,x
|
||||
sta $modifiedLabel+1
|
||||
$loopLabel""")
|
||||
asmgen.assignExpressionToVariable(range.from, varname, ArrayElementTypes.getValue(iterableDt), null)
|
||||
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayElementTypes.getValue(iterableDt), null)
|
||||
asmgen.out(loopLabel)
|
||||
asmgen.translate(stmt.body)
|
||||
asmgen.out("""
|
||||
lda $varname""")
|
||||
if(stepsize>0) {
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc #$stepsize
|
||||
sta $varname
|
||||
$modifiedLabel cmp #0 ; modified
|
||||
bcc $loopLabel
|
||||
beq $loopLabel""")
|
||||
lda $varname
|
||||
clc
|
||||
adc #$stepsize
|
||||
sta $varname
|
||||
$modifiedLabel cmp #0 ; modified
|
||||
bmi $loopLabel
|
||||
beq $loopLabel""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
sec
|
||||
sbc #${stepsize.absoluteValue}
|
||||
sta $varname
|
||||
$modifiedLabel cmp #0 ; modified
|
||||
bcs $loopLabel""")
|
||||
lda $varname
|
||||
sec
|
||||
sbc #${stepsize.absoluteValue}
|
||||
sta $varname
|
||||
$modifiedLabel cmp #0 ; modified
|
||||
bpl $loopLabel""")
|
||||
}
|
||||
asmgen.out("""
|
||||
$endLabel inx""")
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
||||
@ -113,13 +98,11 @@ $endLabel inx""")
|
||||
// words, step 1 or -1
|
||||
|
||||
stepsize == 1 || stepsize == -1 -> {
|
||||
asmgen.translateExpression(range.to)
|
||||
assignLoopvar(stmt, range)
|
||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||
assignLoopvar(stmt, range)
|
||||
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
|
||||
asmgen.out("""
|
||||
lda P8ESTACK_HI+1,x
|
||||
sta $modifiedLabel+1
|
||||
lda P8ESTACK_LO+1,x
|
||||
sty $modifiedLabel+1
|
||||
sta $modifiedLabel2+1
|
||||
$loopLabel""")
|
||||
asmgen.translate(stmt.body)
|
||||
@ -146,21 +129,17 @@ $modifiedLabel2 cmp #0 ; modified
|
||||
jmp $loopLabel""")
|
||||
}
|
||||
asmgen.out(endLabel)
|
||||
asmgen.out(" inx")
|
||||
}
|
||||
stepsize > 0 -> {
|
||||
|
||||
// (u)words, step >= 2
|
||||
asmgen.translateExpression(range.to)
|
||||
asmgen.out("""
|
||||
lda P8ESTACK_HI+1,x
|
||||
sta $modifiedLabel+1
|
||||
lda P8ESTACK_LO+1,x
|
||||
sta $modifiedLabel2+1
|
||||
""")
|
||||
assignLoopvar(stmt, range)
|
||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||
asmgen.out(loopLabel)
|
||||
assignLoopvar(stmt, range)
|
||||
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
|
||||
asmgen.out("""
|
||||
sty $modifiedLabel+1
|
||||
sta $modifiedLabel2+1
|
||||
$loopLabel""")
|
||||
asmgen.translate(stmt.body)
|
||||
|
||||
if (iterableDt == DataType.ARRAY_UW) {
|
||||
@ -179,7 +158,7 @@ $modifiedLabel2 lda #0 ; modified
|
||||
cmp $varname
|
||||
bcc $endLabel
|
||||
bcs $loopLabel
|
||||
$endLabel inx""")
|
||||
$endLabel""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
@ -196,22 +175,19 @@ $modifiedLabel lda #0 ; modified
|
||||
bvc +
|
||||
eor #$80
|
||||
+ bpl $loopLabel
|
||||
$endLabel inx""")
|
||||
$endLabel""")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
|
||||
// (u)words, step <= -2
|
||||
asmgen.translateExpression(range.to)
|
||||
asmgen.out("""
|
||||
lda P8ESTACK_HI+1,x
|
||||
sta $modifiedLabel+1
|
||||
lda P8ESTACK_LO+1,x
|
||||
sta $modifiedLabel2+1
|
||||
""")
|
||||
assignLoopvar(stmt, range)
|
||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||
asmgen.out(loopLabel)
|
||||
assignLoopvar(stmt, range)
|
||||
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
|
||||
asmgen.out("""
|
||||
sty $modifiedLabel+1
|
||||
sta $modifiedLabel2+1
|
||||
$loopLabel""")
|
||||
asmgen.translate(stmt.body)
|
||||
|
||||
if(iterableDt==DataType.ARRAY_UW) {
|
||||
@ -229,7 +205,7 @@ $modifiedLabel cmp #0 ; modified
|
||||
lda $varname
|
||||
$modifiedLabel2 cmp #0 ; modified
|
||||
bcs $loopLabel
|
||||
$endLabel inx""")
|
||||
$endLabel""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
@ -247,7 +223,7 @@ $modifiedLabel sbc #0 ; modified
|
||||
bvc +
|
||||
eor #$80
|
||||
+ bpl $loopLabel
|
||||
$endLabel inx""")
|
||||
$endLabel""")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -609,10 +585,6 @@ $endLabel""")
|
||||
asmgen.loopEndLabels.pop()
|
||||
}
|
||||
|
||||
private fun assignLoopvar(stmt: ForLoop, range: RangeExpr) {
|
||||
val target = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, stmt.loopVarDt(program).typeOrElse(DataType.STRUCT), stmt.definingSubroutine(), variableAsmName=asmgen.asmVariableName(stmt.loopVar))
|
||||
val src = AsmAssignSource.fromAstSource(range.from, program, asmgen).adjustSignedUnsigned(target)
|
||||
val assign = AsmAssignment(src, target, false, range.position)
|
||||
asmgen.translateNormalAssignment(assign)
|
||||
}
|
||||
private fun assignLoopvar(stmt: ForLoop, range: RangeExpr) =
|
||||
asmgen.assignExpressionToVariable(range.from, asmgen.asmVariableName(stmt.loopVar), stmt.loopVarDt(program).typeOrElse(DataType.STRUCT), stmt.definingSubroutine())
|
||||
}
|
||||
|
@ -5,23 +5,54 @@ import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.RegisterOrStatusflag
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.statements.SubroutineParameter
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.compiler.target.CpuType
|
||||
import prog8.compiler.target.c64.codegen.assignment.*
|
||||
|
||||
|
||||
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||
|
||||
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}")
|
||||
val saveX = CpuRegister.X in sub.asmClobbers || sub.regXasResult() || sub.regXasParam()
|
||||
if(saveX)
|
||||
asmgen.saveRegister(CpuRegister.X, preserveStatusRegisterAfterCall, (stmt as Node).definingSubroutine())
|
||||
internal fun translateFunctionCallStatement(stmt: IFunctionCall) {
|
||||
saveXbeforeCall(stmt)
|
||||
translateFunctionCall(stmt)
|
||||
restoreXafterCall(stmt)
|
||||
// just ignore any result values from the function call.
|
||||
}
|
||||
|
||||
internal fun saveXbeforeCall(stmt: IFunctionCall) {
|
||||
val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
||||
if(sub.shouldSaveX()) {
|
||||
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
|
||||
val (keepAonEntry: Boolean, keepAonReturn: Boolean) = sub.shouldKeepA()
|
||||
if(regSaveOnStack)
|
||||
asmgen.saveRegisterStack(CpuRegister.X, keepAonEntry)
|
||||
else
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, (stmt as Node).definingSubroutine()!!)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun restoreXafterCall(stmt: IFunctionCall) {
|
||||
val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
||||
if(sub.shouldSaveX()) {
|
||||
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
|
||||
val (keepAonEntry: Boolean, keepAonReturn: Boolean) = sub.shouldKeepA()
|
||||
|
||||
if(regSaveOnStack)
|
||||
asmgen.restoreRegisterStack(CpuRegister.X, keepAonReturn)
|
||||
else
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun translateFunctionCall(stmt: IFunctionCall) {
|
||||
// Output only the code to setup the parameters and perform the actual call
|
||||
// NOTE: does NOT output the code to deal with the result values!
|
||||
// NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!!
|
||||
// (you can use subroutine.shouldSaveX() and saveX()/restoreX() routines as a help for this)
|
||||
|
||||
val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
||||
val subName = asmgen.asmSymbolName(stmt.target)
|
||||
if(stmt.args.isNotEmpty()) {
|
||||
if(sub.asmParameterRegisters.isEmpty()) {
|
||||
@ -33,39 +64,45 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
// via registers
|
||||
if(sub.parameters.size==1) {
|
||||
// just a single parameter, no risk of clobbering registers
|
||||
argumentViaRegister(sub, sub.parameters.withIndex().single(), stmt.args[0])
|
||||
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)
|
||||
}
|
||||
// There's no risk of clobbering for these simple argument types. Optimize the register loading directly from these values.
|
||||
val argsInfo = sub.parameters.withIndex().zip(stmt.args).zip(sub.asmParameterRegisters)
|
||||
val (vregsArgsInfo, otherRegsArgsInfo) = argsInfo.partition { it.second.registerOrPair in Cx16VirtualRegisters }
|
||||
for(arg in vregsArgsInfo)
|
||||
argumentViaRegister(sub, arg.first.first, arg.first.second)
|
||||
for(arg in otherRegsArgsInfo)
|
||||
argumentViaRegister(sub, arg.first.first, arg.first.second)
|
||||
}
|
||||
else -> {
|
||||
// Risk of clobbering due to complex expression args. Work via the stack.
|
||||
// Risk of clobbering due to complex expression args. Evaluate first, then assign registers.
|
||||
registerArgsViaStackEvaluation(stmt, sub)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
asmgen.out(" jsr $subName")
|
||||
|
||||
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(sub.inline && asmgen.options.optimize) {
|
||||
if(sub.containsDefinedVariables())
|
||||
throw AssemblyError("can't inline sub with vars")
|
||||
if(!sub.isAsmSubroutine && sub.parameters.isNotEmpty())
|
||||
throw AssemblyError("can't inline a non-asm subroutine with parameters")
|
||||
asmgen.out(" \t; inlined routine follows: ${sub.name} from ${sub.position}")
|
||||
val statements = sub.statements.filter { it !is ParameterVarDecl && it !is Directive }
|
||||
statements.forEach { asmgen.translate(it) }
|
||||
}
|
||||
else {
|
||||
asmgen.out(" jsr $subName")
|
||||
}
|
||||
|
||||
if(saveX)
|
||||
asmgen.restoreRegister(CpuRegister.X, preserveStatusRegisterAfterCall)
|
||||
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller
|
||||
}
|
||||
|
||||
private fun registerArgsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) {
|
||||
@ -86,8 +123,8 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
asmgen.out(" inx") // align estack pointer
|
||||
|
||||
for(argi in stmt.args.zip(sub.asmParameterRegisters).withIndex()) {
|
||||
val plusIdxStr = if(argi.index==0) "" else "+${argi.index}"
|
||||
when {
|
||||
argi.value.second.stack -> TODO("asmsub @stack parameter")
|
||||
argi.value.second.statusflag == Statusflag.Pc -> {
|
||||
require(argForCarry == null)
|
||||
argForCarry = argi
|
||||
@ -102,15 +139,40 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
argForAregister = argi
|
||||
}
|
||||
argi.value.second.registerOrPair == RegisterOrPair.Y -> {
|
||||
asmgen.out(" ldy P8ESTACK_LO+${argi.index},x")
|
||||
asmgen.out(" ldy P8ESTACK_LO$plusIdxStr,x")
|
||||
}
|
||||
argi.value.second.registerOrPair in Cx16VirtualRegisters -> {
|
||||
// immediately output code to load the virtual register, to avoid clobbering the A register later
|
||||
when (sub.parameters[argi.index].type) {
|
||||
in ByteDatatypes -> {
|
||||
// only load the lsb of the virtual register
|
||||
asmgen.out("""
|
||||
lda P8ESTACK_LO$plusIdxStr,x
|
||||
sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}
|
||||
""")
|
||||
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
|
||||
asmgen.out(" stz cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}+1")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}+1")
|
||||
}
|
||||
in WordDatatypes ->
|
||||
asmgen.out("""
|
||||
lda P8ESTACK_LO$plusIdxStr,x
|
||||
sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}
|
||||
lda P8ESTACK_HI$plusIdxStr,x
|
||||
sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}+1
|
||||
""")
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird argument")
|
||||
}
|
||||
}
|
||||
|
||||
if(argForCarry!=null) {
|
||||
val plusIdxStr = if(argForCarry.index==0) "" else "+${argForCarry.index}"
|
||||
asmgen.out("""
|
||||
lda P8ESTACK_LO+${argForCarry.index},x
|
||||
lda P8ESTACK_LO$plusIdxStr,x
|
||||
beq +
|
||||
sec
|
||||
bcs ++
|
||||
@ -119,21 +181,23 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
|
||||
if(argForAregister!=null) {
|
||||
val plusIdxStr = if(argForAregister.index==0) "" else "+${argForAregister.index}"
|
||||
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")
|
||||
RegisterOrPair.A -> asmgen.out(" lda P8ESTACK_LO$plusIdxStr,x")
|
||||
RegisterOrPair.AY -> asmgen.out(" lda P8ESTACK_LO$plusIdxStr,x | ldy P8ESTACK_HI$plusIdxStr,x")
|
||||
else -> throw AssemblyError("weird arg")
|
||||
}
|
||||
}
|
||||
|
||||
if(argForXregister!=null) {
|
||||
val plusIdxStr = if(argForXregister.index==0) "" else "+${argForXregister.index}"
|
||||
|
||||
if(argForAregister!=null)
|
||||
asmgen.out(" pha")
|
||||
when(argForXregister.value.second.registerOrPair) {
|
||||
RegisterOrPair.X -> asmgen.out(" lda P8ESTACK_LO+${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")
|
||||
RegisterOrPair.X -> asmgen.out(" lda P8ESTACK_LO$plusIdxStr,x | tax")
|
||||
RegisterOrPair.AX -> asmgen.out(" ldy P8ESTACK_LO$plusIdxStr,x | lda P8ESTACK_HI$plusIdxStr,x | tax | tya")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldy P8ESTACK_HI$plusIdxStr,x | lda P8ESTACK_LO$plusIdxStr,x | tax")
|
||||
else -> throw AssemblyError("weird arg")
|
||||
}
|
||||
if(argForAregister!=null)
|
||||
@ -150,23 +214,20 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
// pass parameter via a regular variable (not via registers)
|
||||
val valueIDt = value.inferType(program)
|
||||
if(!valueIDt.isKnown)
|
||||
throw AssemblyError("arg type unknown")
|
||||
throw AssemblyError("unknown dt")
|
||||
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
|
||||
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
|
||||
throw AssemblyError("argument type incompatible")
|
||||
|
||||
val varName = asmgen.asmVariableName(sub.scopedname+"."+parameter.value.name)
|
||||
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, parameter.value.type, sub, variableAsmName = varName)
|
||||
val source = AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(tgt)
|
||||
val asgn = AsmAssignment(source, tgt, false, Position.DUMMY)
|
||||
asmgen.translateNormalAssignment(asgn)
|
||||
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("arg type unknown")
|
||||
throw AssemblyError("unknown dt")
|
||||
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
|
||||
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
|
||||
throw AssemblyError("argument type incompatible")
|
||||
@ -174,20 +235,12 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
val paramRegister = sub.asmParameterRegisters[parameter.index]
|
||||
val statusflag = paramRegister.statusflag
|
||||
val register = paramRegister.registerOrPair
|
||||
val stack = paramRegister.stack
|
||||
val requiredDt = parameter.value.type
|
||||
if(requiredDt!=valueDt) {
|
||||
if(valueDt largerThan requiredDt)
|
||||
throw AssemblyError("can only convert byte values to word param types")
|
||||
}
|
||||
when {
|
||||
stack -> {
|
||||
// push arg onto the stack
|
||||
// note: argument order is reversed (first argument will be deepest on the stack)
|
||||
asmgen.translateExpression(value)
|
||||
if(requiredDt!=valueDt)
|
||||
asmgen.signExtendStackLsb(valueDt)
|
||||
}
|
||||
statusflag!=null -> {
|
||||
if(requiredDt!=valueDt)
|
||||
throw AssemblyError("for statusflag, byte value is required")
|
||||
@ -212,17 +265,13 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
""")
|
||||
}
|
||||
else -> {
|
||||
asmgen.translateExpression(value)
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
|
||||
asmgen.out("""
|
||||
inx
|
||||
pha
|
||||
lda P8ESTACK_LO,x
|
||||
beq +
|
||||
sec
|
||||
bcs ++
|
||||
+ clc
|
||||
+ pla
|
||||
""")
|
||||
beq +
|
||||
sec
|
||||
bcs ++
|
||||
+ clc
|
||||
+""")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -230,18 +279,20 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
else -> {
|
||||
// via register or register pair
|
||||
val target = AsmAssignTarget.fromRegisters(register!!, sub, program, asmgen)
|
||||
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")
|
||||
val scratchTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UBYTE, sub, scratchVar)
|
||||
val source = AsmAssignSource.fromAstSource(value, program, asmgen)
|
||||
asmgen.translateNormalAssignment(AsmAssignment(source, scratchTarget, false, value.position))
|
||||
asmgen.assignExpressionToVariable(value, scratchVar, DataType.UBYTE, sub)
|
||||
asmgen.signExtendVariableLsb(scratchVar, valueDt)
|
||||
val src = AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, DataType.UWORD, scratchVar)
|
||||
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, Position.DUMMY))
|
||||
asmgen.assignVariableToRegister(scratchVar, register)
|
||||
}
|
||||
else {
|
||||
val target: AsmAssignTarget =
|
||||
if(parameter.value.type in ByteDatatypes && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters))
|
||||
AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, parameter.value.type, sub, register = register)
|
||||
else
|
||||
AsmAssignTarget.fromRegisters(register, sub, program, asmgen)
|
||||
val src = if(valueDt in PassByReferenceDatatypes) {
|
||||
if(value is IdentifierReference) {
|
||||
val addr = AddressOf(value, Position.DUMMY)
|
||||
|
@ -19,7 +19,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
when {
|
||||
targetIdent!=null -> {
|
||||
val what = asmgen.asmVariableName(targetIdent)
|
||||
when (stmt.target.inferType(program, stmt).typeOrElse(DataType.STRUCT)) {
|
||||
when (stmt.target.inferType(program).typeOrElse(DataType.STRUCT)) {
|
||||
in ByteDatatypes -> asmgen.out(if (incr) " inc $what" else " dec $what")
|
||||
in WordDatatypes -> {
|
||||
if(incr)
|
||||
@ -54,14 +54,8 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
asmgen.out("+\tdec ${'$'}ffff\t; modified")
|
||||
}
|
||||
else -> {
|
||||
asmgen.translateExpression(addressExpr)
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
sta (+) + 1
|
||||
lda P8ESTACK_HI,x
|
||||
sta (+) + 2
|
||||
""")
|
||||
asmgen.assignExpressionToRegister(addressExpr, RegisterOrPair.AY)
|
||||
asmgen.out(" sta (+) + 1 | sty (+) + 2")
|
||||
if(incr)
|
||||
asmgen.out("+\tinc ${'$'}ffff\t; modified")
|
||||
else
|
||||
@ -97,7 +91,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
else
|
||||
{
|
||||
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.A)
|
||||
asmgen.saveRegister(CpuRegister.X, false, scope)
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, scope!!)
|
||||
asmgen.out(" tax")
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> {
|
||||
@ -111,7 +105,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
lda $asmArrayvarname,x
|
||||
bne +
|
||||
dec $asmArrayvarname+1,x
|
||||
+ dec $asmArrayvarname
|
||||
+ dec $asmArrayvarname,x
|
||||
""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
@ -125,7 +119,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
else -> throw AssemblyError("weird array elt dt")
|
||||
}
|
||||
asmgen.restoreRegister(CpuRegister.X, false)
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,10 +3,7 @@ package prog8.compiler.target.c64.codegen.assignment
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.DirectMemoryWrite
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.c64.codegen.AsmGen
|
||||
|
||||
@ -52,13 +49,16 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
|
||||
lateinit var origAssign: AsmAssignment
|
||||
|
||||
init {
|
||||
if(register!=null && datatype !in IntegerDatatypes)
|
||||
throw AssemblyError("register must be integer type")
|
||||
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 dt = inferType(program, assign).typeOrElse(DataType.STRUCT)
|
||||
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)
|
||||
@ -75,6 +75,24 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
|
||||
RegisterOrPair.AX,
|
||||
RegisterOrPair.AY,
|
||||
RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UWORD, scope, register = registers)
|
||||
RegisterOrPair.FAC1,
|
||||
RegisterOrPair.FAC2 -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.FLOAT, scope, register = registers)
|
||||
RegisterOrPair.R0,
|
||||
RegisterOrPair.R1,
|
||||
RegisterOrPair.R2,
|
||||
RegisterOrPair.R3,
|
||||
RegisterOrPair.R4,
|
||||
RegisterOrPair.R5,
|
||||
RegisterOrPair.R6,
|
||||
RegisterOrPair.R7,
|
||||
RegisterOrPair.R8,
|
||||
RegisterOrPair.R9,
|
||||
RegisterOrPair.R10,
|
||||
RegisterOrPair.R11,
|
||||
RegisterOrPair.R12,
|
||||
RegisterOrPair.R13,
|
||||
RegisterOrPair.R14,
|
||||
RegisterOrPair.R15 -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UWORD, scope, register = registers)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -101,6 +119,14 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
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)
|
||||
@ -121,33 +147,30 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value)
|
||||
}
|
||||
else -> {
|
||||
if(value is FunctionCall) {
|
||||
// functioncall.
|
||||
val asmSub = value.target.targetStatement(program.namespace)
|
||||
if(asmSub is Subroutine && asmSub.isAsmSubroutine) {
|
||||
when (asmSub.asmReturnvaluesRegisters.count { rr -> rr.registerOrPair!=null }) {
|
||||
0 -> throw AssemblyError("can't translate zero return values in assignment")
|
||||
1 -> {
|
||||
// assignment generation itself must make sure the status register is correct after the subroutine call, if status register is involved!
|
||||
val reg = asmSub.asmReturnvaluesRegisters.single { rr->rr.registerOrPair!=null }.registerOrPair!!
|
||||
val dt = when(reg) {
|
||||
RegisterOrPair.A,
|
||||
RegisterOrPair.X,
|
||||
RegisterOrPair.Y -> DataType.UBYTE
|
||||
RegisterOrPair.AX,
|
||||
RegisterOrPair.AY,
|
||||
RegisterOrPair.XY -> DataType.UWORD
|
||||
}
|
||||
return AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt, expression = value)
|
||||
}
|
||||
else -> throw AssemblyError("can't translate multiple return values in assignment")
|
||||
}
|
||||
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 || rr.second.statusflag!=null }?.first
|
||||
?: throw AssemblyError("can't translate zero return values in assignment")
|
||||
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)
|
||||
}
|
||||
is BuiltinFunctionStatementPlaceholder -> {
|
||||
val returnType = value.inferType(program)
|
||||
if(!returnType.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.typeOrElse(DataType.STRUCT), expression = value)
|
||||
}
|
||||
else -> {
|
||||
throw AssemblyError("weird call")
|
||||
}
|
||||
}
|
||||
|
||||
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
return AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt, expression = value)
|
||||
}
|
||||
else -> {
|
||||
val dt = value.inferType(program)
|
||||
if(!dt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt.typeOrElse(DataType.STRUCT), expression = value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -21,7 +21,6 @@ internal object CX16MachineDefinition: IMachineDefinition {
|
||||
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
|
||||
|
||||
@ -29,7 +28,6 @@ internal object CX16MachineDefinition: IMachineDefinition {
|
||||
|
||||
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")
|
||||
@ -38,8 +36,7 @@ internal object CX16MachineDefinition: IMachineDefinition {
|
||||
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 cmdline = listOf(emulator, "-scale", "2", "-run", "-prg", "$programName.prg")
|
||||
val processb = ProcessBuilder(cmdline).inheritIO()
|
||||
val process: Process
|
||||
try {
|
||||
@ -74,8 +71,8 @@ internal object CX16MachineDefinition: IMachineDefinition {
|
||||
|
||||
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
|
||||
override val SCRATCH_B1 = 0x7a // temp storage for a single byte
|
||||
override val SCRATCH_REG = 0x7b // temp storage for a register, must be B1+1
|
||||
override val SCRATCH_W1 = 0x7c // temp storage 1 for a word $7c+$7d
|
||||
override val SCRATCH_W2 = 0x7e // temp storage 2 for a word $7e+$7f
|
||||
|
||||
|
@ -15,96 +15,133 @@ 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),
|
||||
"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
|
||||
"sizeof" to FSignature(true, listOf(FParam("object", DataType.values().toSet())), DataType.UBYTE, ::builtinSizeof),
|
||||
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),
|
||||
FSignature("offsetof" , true, listOf(FParam("object", DataType.values().toSet())), DataType.UBYTE, ::builtinOffsetof),
|
||||
// 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("msb", setOf(DataType.UBYTE)), FParam("lsb", 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(
|
||||
FParam("from", IterableDatatypes + DataType.UWORD),
|
||||
FParam("to", IterableDatatypes + DataType.UWORD),
|
||||
FParam("numbytes", setOf(DataType.UBYTE))), null),
|
||||
"memset" to FSignature(false, listOf(
|
||||
FParam("address", IterableDatatypes + DataType.UWORD),
|
||||
FParam("numbytes", setOf(DataType.UWORD)),
|
||||
FParam("bytevalue", ByteDatatypes)), null),
|
||||
"memsetw" to FSignature(false, listOf(
|
||||
FParam("address", IterableDatatypes + DataType.UWORD),
|
||||
FParam("numwords", setOf(DataType.UWORD)),
|
||||
FParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null),
|
||||
"strlen" to FSignature(true, listOf(FParam("string", setOf(DataType.STR))), DataType.UBYTE, ::builtinStrlen),
|
||||
"substr" to FSignature(false, listOf(
|
||||
FParam("source", IterableDatatypes + DataType.UWORD),
|
||||
FParam("target", IterableDatatypes + DataType.UWORD),
|
||||
FParam("start", setOf(DataType.UBYTE)),
|
||||
FParam("length", setOf(DataType.UBYTE))), null),
|
||||
"leftstr" to FSignature(false, listOf(
|
||||
FParam("source", IterableDatatypes + DataType.UWORD),
|
||||
FParam("target", IterableDatatypes + DataType.UWORD),
|
||||
FParam("length", setOf(DataType.UBYTE))), null),
|
||||
"rightstr" to FSignature(false, listOf(
|
||||
FParam("source", IterableDatatypes + DataType.UWORD),
|
||||
FParam("target", IterableDatatypes + DataType.UWORD),
|
||||
FParam("length", setOf(DataType.UBYTE))), null),
|
||||
"strcmp" to FSignature(false, listOf(FParam("s1", IterableDatatypes + DataType.UWORD), FParam("s2", IterableDatatypes + DataType.UWORD)), DataType.BYTE, null)
|
||||
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("memory" , true, listOf(FParam("name", setOf(DataType.STR)), FParam("size", setOf(DataType.UWORD))), DataType.UWORD),
|
||||
FSignature("swap" , false, listOf(FParam("first", NumericDatatypes), FParam("second", NumericDatatypes)), null),
|
||||
|
||||
)
|
||||
|
||||
val BuiltinFunctions = functionSignatures.associateBy { it.name }
|
||||
|
||||
|
||||
fun builtinMax(array: List<Number>): Number = array.maxByOrNull { it.toDouble() }!!
|
||||
|
||||
fun builtinMin(array: List<Number>): Number = array.minByOrNull { it.toDouble() }!!
|
||||
@ -144,17 +181,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())) {
|
||||
@ -242,6 +279,28 @@ private fun builtinAbs(args: List<Expression>, position: Position, program: Prog
|
||||
}
|
||||
}
|
||||
|
||||
private fun builtinOffsetof(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||
// 1 arg, type = anything, result type = ubyte
|
||||
if(args.size!=1)
|
||||
throw SyntaxError("offsetof requires one argument", position)
|
||||
val idref = args[0] as? IdentifierReference
|
||||
?: throw SyntaxError("offsetof argument should be an identifier", position)
|
||||
|
||||
val vardecl = idref.targetVarDecl(program.namespace)!!
|
||||
val struct = vardecl.struct
|
||||
if (struct == null || vardecl.datatype == DataType.STRUCT)
|
||||
throw SyntaxError("offsetof can only be used on struct members", position)
|
||||
|
||||
val membername = idref.nameInSource.last()
|
||||
var offset = 0
|
||||
for(member in struct.statements) {
|
||||
if((member as VarDecl).name == membername)
|
||||
return NumericLiteralValue(DataType.UBYTE, offset, position)
|
||||
offset += member.datatype.memorySize()
|
||||
}
|
||||
throw SyntaxError("undefined struct member", position)
|
||||
}
|
||||
|
||||
private fun builtinSizeof(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||
// 1 arg, type = anything, result type = ubyte
|
||||
if(args.size!=1)
|
||||
@ -278,23 +337,6 @@ private fun builtinSizeof(args: List<Expression>, position: Position, program: P
|
||||
}
|
||||
}
|
||||
|
||||
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]
|
||||
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)
|
||||
|
@ -38,22 +38,26 @@ internal class BinExprSplitter(private val program: Program) : AstWalker() {
|
||||
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:
|
||||
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 LEFTEXPR ==> <operator>
|
||||
X = BinExpr X = LeftExpr
|
||||
<operator> followed by
|
||||
/ \ IF 'X' not used X = BinExpr
|
||||
/ \ IN expression ==> <operator>
|
||||
/ \ / \
|
||||
LeftExpr. RightExpr. / \
|
||||
/ \ / \ X RightExpr.
|
||||
.. .. .. ..
|
||||
X RightExpr.
|
||||
|
||||
|
||||
*/
|
||||
if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target, program.namespace)) {
|
||||
if (!assignment.isAugmentable) {
|
||||
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)
|
||||
@ -71,8 +75,11 @@ X = BinExpr X = LeftExpr
|
||||
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.arrayindexed!=null)
|
||||
if (target.identifier!=null || target.memoryAddress!=null)
|
||||
target.isInRegularRAM(namespace)
|
||||
else
|
||||
false
|
||||
|
@ -117,12 +117,9 @@ 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 and are never removed as 'unused'
|
||||
if (decl.autogeneratedDontRemove || decl.datatype==DataType.STRUCT)
|
||||
addNodeAndParentScopes(decl)
|
||||
}
|
||||
|
||||
if (decl.datatype == DataType.STRUCT)
|
||||
else if(decl.parent is Block && decl.definingModule().isLibraryModule)
|
||||
addNodeAndParentScopes(decl)
|
||||
|
||||
super.visit(decl)
|
||||
|
@ -7,6 +7,7 @@ import prog8.ast.expressions.*
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModification
|
||||
import prog8.ast.statements.*
|
||||
import kotlin.math.pow
|
||||
|
||||
|
||||
internal class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
@ -97,21 +98,63 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
val leftconst = expr.left.constValue(program)
|
||||
val rightconst = expr.right.constValue(program)
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
|
||||
val subExpr: BinaryExpression? = when {
|
||||
leftconst!=null -> expr.right as? BinaryExpression
|
||||
rightconst!=null -> expr.left as? BinaryExpression
|
||||
else -> null
|
||||
if(expr.operator == "**" && leftconst!=null) {
|
||||
// optimize various simple cases of ** :
|
||||
// optimize away 1 ** x into just 1 and 0 ** x into just 0
|
||||
// optimize 2 ** x into (1<<x) if both operands are integer.
|
||||
val leftDt = leftconst.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
when (leftconst.number.toDouble()) {
|
||||
0.0 -> {
|
||||
val value = NumericLiteralValue(leftDt, 0, expr.position)
|
||||
modifications += IAstModification.ReplaceNode(expr, value, parent)
|
||||
}
|
||||
1.0 -> {
|
||||
val value = NumericLiteralValue(leftDt, 1, expr.position)
|
||||
modifications += IAstModification.ReplaceNode(expr, value, parent)
|
||||
}
|
||||
2.0 -> {
|
||||
if(rightconst!=null) {
|
||||
val value = NumericLiteralValue(leftDt, 2.0.pow(rightconst.number.toDouble()), expr.position)
|
||||
modifications += IAstModification.ReplaceNode(expr, value, parent)
|
||||
} else {
|
||||
val rightDt = expr.right.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
||||
val targetDt =
|
||||
when (parent) {
|
||||
is Assignment -> parent.target.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
is VarDecl -> parent.datatype
|
||||
else -> leftDt
|
||||
}
|
||||
val one = NumericLiteralValue(targetDt, 1, expr.position)
|
||||
val shift = BinaryExpression(one, "<<", expr.right, expr.position)
|
||||
modifications += IAstModification.ReplaceNode(expr, shift, parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(subExpr!=null) {
|
||||
val subleftconst = subExpr.left.constValue(program)
|
||||
val subrightconst = subExpr.right.constValue(program)
|
||||
if ((subleftconst != null && subrightconst == null) || (subleftconst==null && subrightconst!=null)) {
|
||||
// try reordering.
|
||||
val change = groupTwoConstsTogether(expr, subExpr,
|
||||
|
||||
if(expr.inferType(program).istype(DataType.FLOAT)) {
|
||||
val subExpr: BinaryExpression? = when {
|
||||
leftconst != null -> expr.right as? BinaryExpression
|
||||
rightconst != null -> expr.left as? BinaryExpression
|
||||
else -> null
|
||||
}
|
||||
if (subExpr != null) {
|
||||
val subleftconst = subExpr.left.constValue(program)
|
||||
val subrightconst = subExpr.right.constValue(program)
|
||||
if ((subleftconst != null && subrightconst == null) || (subleftconst == null && subrightconst != null)) {
|
||||
// try reordering.
|
||||
val change = groupTwoFloatConstsTogether(
|
||||
expr, subExpr,
|
||||
leftconst != null, rightconst != null,
|
||||
subleftconst != null, subrightconst != null)
|
||||
return change?.let { listOf(it) } ?: noModifications
|
||||
subleftconst != null, subrightconst != null
|
||||
)
|
||||
if (change != null)
|
||||
modifications += change
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,10 +162,10 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
|
||||
if(leftconst != null && rightconst != null) {
|
||||
val evaluator = ConstExprEvaluator()
|
||||
val result = evaluator.evaluate(leftconst, expr.operator, rightconst)
|
||||
return listOf(IAstModification.ReplaceNode(expr, result, parent))
|
||||
modifications += IAstModification.ReplaceNode(expr, result, parent)
|
||||
}
|
||||
|
||||
return noModifications
|
||||
return modifications
|
||||
}
|
||||
|
||||
override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||
@ -187,7 +230,8 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
|
||||
val rangeTo = iterableRange.to as? NumericLiteralValue
|
||||
if(rangeFrom==null || rangeTo==null) return noModifications
|
||||
|
||||
val loopvar = forLoop.loopVar.targetVarDecl(program.namespace)!!
|
||||
val loopvar = forLoop.loopVar.targetVarDecl(program.namespace) ?: throw UndefinedSymbolError(forLoop.loopVar)
|
||||
|
||||
val stepLiteral = iterableRange.step as? NumericLiteralValue
|
||||
when(loopvar.datatype) {
|
||||
DataType.UBYTE -> {
|
||||
@ -258,13 +302,15 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
|
||||
}
|
||||
}
|
||||
|
||||
private fun groupTwoConstsTogether(expr: BinaryExpression,
|
||||
subExpr: BinaryExpression,
|
||||
leftIsConst: Boolean,
|
||||
rightIsConst: Boolean,
|
||||
subleftIsConst: Boolean,
|
||||
subrightIsConst: Boolean): IAstModification?
|
||||
private fun groupTwoFloatConstsTogether(expr: BinaryExpression,
|
||||
subExpr: BinaryExpression,
|
||||
leftIsConst: Boolean,
|
||||
rightIsConst: Boolean,
|
||||
subleftIsConst: Boolean,
|
||||
subrightIsConst: Boolean): IAstModification?
|
||||
{
|
||||
// NOTE: THIS IS ONLY VALID ON FLOATING POINT CONSTANTS
|
||||
|
||||
// todo: this implements only a small set of possible reorderings at this time
|
||||
if(expr.operator==subExpr.operator) {
|
||||
// both operators are the same.
|
||||
|
@ -13,18 +13,23 @@ 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() {
|
||||
internal class VarConstantValueTypeAdjuster(private val program: Program, private val errors: ErrorReporter) : 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)
|
||||
try {
|
||||
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))
|
||||
// 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))
|
||||
}
|
||||
} catch (x: UndefinedSymbolError) {
|
||||
errors.err(x.message, x.position)
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
@ -47,11 +52,22 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
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
|
||||
try {
|
||||
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
|
||||
}
|
||||
} catch (x: UndefinedSymbolError) {
|
||||
errors.err(x.message, x.position)
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,11 +94,16 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
}
|
||||
} 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
|
||||
try {
|
||||
val cval = arraysize.indexVar?.constValue(program) ?: arraysize.origExpression?.constValue(program)
|
||||
if (cval != null) {
|
||||
arraysize.indexVar = null
|
||||
arraysize.origExpression = null
|
||||
arraysize.indexNum = cval
|
||||
}
|
||||
} catch (x: UndefinedSymbolError) {
|
||||
errors.err(x.message, x.position)
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ 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
|
||||
@ -98,6 +99,12 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
if (leftVal != null && expr.operator in associativeOperators && rightVal == null)
|
||||
return listOf(IAstModification.SwapOperands(expr))
|
||||
|
||||
// NonBinaryExpression <associativeoperator> BinaryExpression --> BinaryExpression <associativeoperator> NonBinaryExpression
|
||||
if (expr.operator in associativeOperators && expr.left !is BinaryExpression && expr.right is BinaryExpression) {
|
||||
if(parent !is Assignment || !(expr.left isSameAs parent.target))
|
||||
return listOf(IAstModification.SwapOperands(expr))
|
||||
}
|
||||
|
||||
// X + (-A) --> X - A
|
||||
if (expr.operator == "+" && (expr.right as? PrefixExpression)?.operator == "-") {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
@ -338,7 +345,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
if (leftVal == null && rightVal == null)
|
||||
return null
|
||||
|
||||
val (expr2, _, rightVal2) = reorderAssociative(expr, leftVal)
|
||||
val (expr2, _, rightVal2) = reorderAssociativeWithConstant(expr, leftVal)
|
||||
if (rightVal2 != null) {
|
||||
// right value is a constant, see if we can optimize
|
||||
val rightConst: NumericLiteralValue = rightVal2
|
||||
@ -477,7 +484,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)
|
||||
@ -559,7 +569,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
if (leftVal == null && rightVal == null)
|
||||
return null
|
||||
|
||||
val (expr2, _, rightVal2) = reorderAssociative(expr, leftVal)
|
||||
val (expr2, _, rightVal2) = reorderAssociativeWithConstant(expr, leftVal)
|
||||
if (rightVal2 != null) {
|
||||
// right value is a constant, see if we can optimize
|
||||
val leftValue: Expression = expr2.left
|
||||
@ -606,8 +616,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)
|
||||
@ -617,8 +629,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
if (amount >= 16) {
|
||||
return NumericLiteralValue(targetDt, 0, expr.position)
|
||||
} else if (amount >= 8) {
|
||||
// TODO is this correct???
|
||||
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(lsb, NumericLiteralValue.optimalInteger(0, expr.position)), expr.position)
|
||||
}
|
||||
@ -640,7 +651,10 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
if (amount == 0) {
|
||||
return expr.left
|
||||
}
|
||||
when (expr.left.inferType(program).typeOrElse(DataType.STRUCT)) {
|
||||
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)
|
||||
@ -655,12 +669,15 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
DataType.UWORD -> {
|
||||
if (amount >= 16) {
|
||||
return NumericLiteralValue.optimalInteger(0, expr.position)
|
||||
} else if (amount >= 8) {
|
||||
}
|
||||
else if (amount >= 8) {
|
||||
val msb = FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
|
||||
if (amount == 8) {
|
||||
return TypecastExpression(msb, DataType.UWORD, true, expr.position)
|
||||
// mkword(0, msb(v))
|
||||
val zero = NumericLiteralValue(DataType.UBYTE, 0, expr.position)
|
||||
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(zero, msb), expr.position)
|
||||
}
|
||||
return BinaryExpression(msb, ">>", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position)
|
||||
return TypecastExpression(BinaryExpression(msb, ">>", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position), DataType.UWORD, true, expr.position)
|
||||
}
|
||||
}
|
||||
DataType.WORD -> {
|
||||
@ -675,17 +692,17 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
return null
|
||||
}
|
||||
|
||||
private fun reorderAssociative(expr: BinaryExpression, leftVal: NumericLiteralValue?): ReorderedAssociativeBinaryExpr {
|
||||
private fun reorderAssociativeWithConstant(expr: BinaryExpression, leftVal: NumericLiteralValue?): BinExprWithConstants {
|
||||
if (expr.operator in associativeOperators && leftVal != null) {
|
||||
// swap left and right so that right is always the constant
|
||||
val tmp = expr.left
|
||||
expr.left = expr.right
|
||||
expr.right = tmp
|
||||
return ReorderedAssociativeBinaryExpr(expr, expr.right.constValue(program), leftVal)
|
||||
return BinExprWithConstants(expr, expr.right.constValue(program), leftVal)
|
||||
}
|
||||
return ReorderedAssociativeBinaryExpr(expr, leftVal, expr.right.constValue(program))
|
||||
return BinExprWithConstants(expr, leftVal, expr.right.constValue(program))
|
||||
}
|
||||
|
||||
private data class ReorderedAssociativeBinaryExpr(val expr: BinaryExpression, val leftVal: NumericLiteralValue?, val rightVal: NumericLiteralValue?)
|
||||
private data class BinExprWithConstants(val expr: BinaryExpression, val leftVal: NumericLiteralValue?, val rightVal: NumericLiteralValue?)
|
||||
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import prog8.ast.base.ErrorReporter
|
||||
|
||||
|
||||
internal fun Program.constantFold(errors: ErrorReporter) {
|
||||
val valuetypefixer = VarConstantValueTypeAdjuster(this)
|
||||
val valuetypefixer = VarConstantValueTypeAdjuster(this, errors)
|
||||
valuetypefixer.visit(this)
|
||||
if(errors.isEmpty()) {
|
||||
valuetypefixer.applyModifications()
|
||||
|
@ -39,7 +39,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
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()) {
|
||||
if(subroutine.containsNoCodeNorVars() && !subroutine.inline) {
|
||||
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
|
||||
val removals = callgraph.calledBy.getValue(subroutine).map {
|
||||
IAstModification.Remove(it, it.definingScope())
|
||||
@ -323,7 +323,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
val op1 = binExpr.operator
|
||||
val op2 = rExpr.operator
|
||||
|
||||
if(rExpr.left is NumericLiteralValue && op2 in setOf("+", "*", "&", "|")) {
|
||||
if(rExpr.left is NumericLiteralValue && op2 in associativeOperators) {
|
||||
// associative operator, make sure the constant numeric value is second (right)
|
||||
return listOf(IAstModification.SwapOperands(rExpr))
|
||||
}
|
||||
@ -377,7 +377,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
}
|
||||
|
||||
val targetIDt = assignment.target.inferType(program, assignment)
|
||||
val targetIDt = assignment.target.inferType(program)
|
||||
if(!targetIDt.isKnown)
|
||||
throw FatalAstException("can't infer type of assignment target")
|
||||
|
||||
@ -441,6 +441,47 @@ internal class StatementOptimizer(private val program: Program,
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
|
||||
fun returnViaIntermediary(value: Expression): Iterable<IAstModification>? {
|
||||
val returnDt = returnStmt.definingSubroutine()!!.returntypes.single()
|
||||
if (returnDt in IntegerDatatypes) {
|
||||
// first assign to intermediary, then return that register
|
||||
val returnValueIntermediary =
|
||||
when(returnDt) {
|
||||
DataType.UBYTE -> IdentifierReference(listOf("prog8_lib", "retval_interm_ub"), returnStmt.position)
|
||||
DataType.BYTE -> IdentifierReference(listOf("prog8_lib", "retval_interm_b"), returnStmt.position)
|
||||
DataType.UWORD -> IdentifierReference(listOf("prog8_lib", "retval_interm_uw"), returnStmt.position)
|
||||
DataType.WORD -> IdentifierReference(listOf("prog8_lib", "retval_interm_w"), returnStmt.position)
|
||||
else -> throw FatalAstException("weird return dt")
|
||||
}
|
||||
val tgt = AssignTarget(returnValueIntermediary, null, null, returnStmt.position)
|
||||
val assign = Assignment(tgt, value, returnStmt.position)
|
||||
val returnReplacement = Return(returnValueIntermediary, returnStmt.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(returnStmt, assign, parent as INameScope),
|
||||
IAstModification.ReplaceNode(returnStmt, returnReplacement, parent)
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
when(returnStmt.value) {
|
||||
is PrefixExpression -> {
|
||||
val mod = returnViaIntermediary(returnStmt.value!!)
|
||||
if(mod!=null)
|
||||
return mod
|
||||
}
|
||||
is BinaryExpression -> {
|
||||
val mod = returnViaIntermediary(returnStmt.value!!)
|
||||
if(mod!=null)
|
||||
return mod
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
return super.after(returnStmt, parent)
|
||||
}
|
||||
|
||||
private fun hasBreak(scope: INameScope): Boolean {
|
||||
|
||||
class Searcher: IAstVisitor
|
||||
|
@ -4,6 +4,7 @@ import prog8.ast.INameScope
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModification
|
||||
import prog8.ast.statements.*
|
||||
@ -93,7 +94,14 @@ internal class UnusedCodeRemover(private val program: Program, private val error
|
||||
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)
|
||||
// only remove the second assignment if its value is a simple expression!
|
||||
when(assign2.value) {
|
||||
is PrefixExpression,
|
||||
is BinaryExpression,
|
||||
is TypecastExpression,
|
||||
is FunctionCall -> { /* don't remove */ }
|
||||
else -> linesToRemove.add(assign1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ internal class ModuleImporter {
|
||||
// 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) }
|
||||
|
||||
@ -100,9 +100,53 @@ internal class ModuleImporter {
|
||||
return moduleAst
|
||||
}
|
||||
|
||||
private fun discoverImportedModuleFile(name: String, source: Path, position: Position?): Path {
|
||||
private fun executeImportDirective(program: Program, import: Directive, source: Path): Module? {
|
||||
if(import.directive!="%import" || import.args.size!=1 || import.args[0].name==null)
|
||||
throw SyntaxError("invalid import directive", import.position)
|
||||
val moduleName = import.args[0].name!!
|
||||
if("$moduleName.p8" == import.position.file)
|
||||
throw SyntaxError("cannot import self", import.position)
|
||||
|
||||
val existing = program.modules.singleOrNull { it.name == moduleName }
|
||||
if(existing!=null)
|
||||
return null
|
||||
|
||||
val rsc = tryGetModuleFromResource("$moduleName.p8")
|
||||
val importedModule =
|
||||
if(rsc!=null) {
|
||||
// load the module from the embedded resource
|
||||
val (resource, resourcePath) = rsc
|
||||
resource.use {
|
||||
println("importing '$moduleName' (library)")
|
||||
importModule(program, CharStreams.fromStream(it), Paths.get("@embedded@/$resourcePath"), true)
|
||||
}
|
||||
} else {
|
||||
val modulePath = tryGetModuleFromFile(moduleName, source, import.position)
|
||||
importModule(program, modulePath)
|
||||
}
|
||||
|
||||
importedModule.checkImportedValid()
|
||||
return importedModule
|
||||
}
|
||||
|
||||
private fun tryGetModuleFromResource(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
|
||||
}
|
||||
|
||||
private fun tryGetModuleFromFile(name: String, source: Path, position: Position?): Path {
|
||||
val fileName = "$name.p8"
|
||||
val locations = if(source.toString().isEmpty()) mutableListOf<Path>() else mutableListOf(source.parent)
|
||||
val locations = if(source.toString().isEmpty()) mutableListOf<Path>() else mutableListOf(source.parent ?: Path.of("."))
|
||||
|
||||
val propPath = System.getProperty("prog8.libdir")
|
||||
if(propPath!=null)
|
||||
@ -119,48 +163,4 @@ internal class ModuleImporter {
|
||||
|
||||
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? {
|
||||
if(import.directive!="%import" || import.args.size!=1 || import.args[0].name==null)
|
||||
throw SyntaxError("invalid import directive", import.position)
|
||||
val moduleName = import.args[0].name!!
|
||||
if("$moduleName.p8" == import.position.file)
|
||||
throw SyntaxError("cannot import self", import.position)
|
||||
|
||||
val existing = program.modules.singleOrNull { it.name == moduleName }
|
||||
if(existing!=null)
|
||||
return null
|
||||
|
||||
val rsc = tryGetEmbeddedResource("$moduleName.p8")
|
||||
val importedModule =
|
||||
if(rsc!=null) {
|
||||
// load the module from the embedded resource
|
||||
val (resource, resourcePath) = rsc
|
||||
resource.use {
|
||||
println("importing '$moduleName' (library)")
|
||||
importModule(program, CharStreams.fromStream(it), Paths.get("@embedded@/$resourcePath"), true)
|
||||
}
|
||||
} else {
|
||||
val modulePath = discoverImportedModuleFile(moduleName, source, import.position)
|
||||
importModule(program, modulePath)
|
||||
}
|
||||
|
||||
importedModule.checkImportedValid()
|
||||
return importedModule
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +0,0 @@
|
||||
package prog8.server.dbus
|
||||
|
||||
//import org.freedesktop.dbus.interfaces.DBusInterface
|
||||
//
|
||||
//
|
||||
//interface IrmenDbusTest: DBusInterface
|
||||
//{
|
||||
// fun Status(address: String): Map<Int, String>
|
||||
//}
|
||||
//
|
||||
//
|
||||
//internal class TestService: IrmenDbusTest {
|
||||
// override fun Status(address: String): Map<Int, String> {
|
||||
// return mapOf(
|
||||
// 5 to "hello",
|
||||
// 42 to address
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// override fun isRemote() = true
|
||||
// override fun getObjectPath() = "/razorvine/TestService"
|
||||
//}
|
@ -1,17 +0,0 @@
|
||||
package prog8.server.dbus
|
||||
|
||||
|
||||
//import org.freedesktop.dbus.connections.impl.DBusConnection
|
||||
//
|
||||
//
|
||||
//fun main() {
|
||||
// DBusConnection.getConnection(DBusConnection.DBusBusType.SESSION).use {
|
||||
// println(it.names.toList())
|
||||
// println(it.uniqueName)
|
||||
// println(it.address)
|
||||
// println(it.machineId)
|
||||
// val obj = it.getRemoteObject("local.net.razorvine.dbus.test", "/razorvine/TestService", IrmenDbusTest::class.java)
|
||||
// println(obj.Status("irmen"))
|
||||
// }
|
||||
//}
|
||||
//
|
@ -1,18 +0,0 @@
|
||||
package prog8.server.dbus
|
||||
|
||||
//import org.freedesktop.dbus.connections.impl.DBusConnection
|
||||
//
|
||||
//
|
||||
//fun main() {
|
||||
// DBusConnection.getConnection(DBusConnection.DBusBusType.SESSION).use {
|
||||
// it.requestBusName("local.net.razorvine.dbus.test")
|
||||
// println(it.names.toList())
|
||||
// println(it.uniqueName)
|
||||
// println(it.address)
|
||||
// println(it.machineId)
|
||||
// val service = TestService()
|
||||
// it.exportObject(service.objectPath, service)
|
||||
//
|
||||
// Thread.sleep(100000)
|
||||
// }
|
||||
//}
|
@ -5,8 +5,6 @@ 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.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
@ -18,8 +16,8 @@ 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 java.nio.file.Path
|
||||
import kotlin.test.*
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
@ -288,6 +286,22 @@ class TestC64Zeropage {
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -463,7 +477,7 @@ class TestMemory {
|
||||
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)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
subroutine.linkParents(ParentSentinel)
|
||||
return target
|
||||
}
|
||||
@ -483,7 +497,7 @@ class TestMemory {
|
||||
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)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
subroutine.linkParents(ParentSentinel)
|
||||
assertTrue(target.isInRegularRAM(target.definingScope()))
|
||||
}
|
||||
@ -495,7 +509,7 @@ class TestMemory {
|
||||
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)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
subroutine.linkParents(ParentSentinel)
|
||||
assertTrue(target.isInRegularRAM(target.definingScope()))
|
||||
}
|
||||
@ -507,7 +521,7 @@ class TestMemory {
|
||||
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)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
subroutine.linkParents(ParentSentinel)
|
||||
assertFalse(target.isInRegularRAM(target.definingScope()))
|
||||
}
|
||||
@ -519,7 +533,7 @@ class TestMemory {
|
||||
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)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
subroutine.linkParents(ParentSentinel)
|
||||
assertTrue(target.isInRegularRAM(target.definingScope()))
|
||||
}
|
||||
@ -532,7 +546,7 @@ class TestMemory {
|
||||
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)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
subroutine.linkParents(ParentSentinel)
|
||||
assertTrue(target.isInRegularRAM(target.definingScope()))
|
||||
}
|
||||
@ -545,7 +559,7 @@ class TestMemory {
|
||||
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)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
subroutine.linkParents(ParentSentinel)
|
||||
assertFalse(target.isInRegularRAM(target.definingScope()))
|
||||
}
|
||||
|
96
dbusCompilerService/build.gradle
Normal file
96
dbusCompilerService/build.gradle
Normal file
@ -0,0 +1,96 @@
|
||||
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'application'
|
||||
id "org.jetbrains.kotlin.jvm" version "1.4.21"
|
||||
id 'com.github.johnrengelman.shadow' version '6.1.0'
|
||||
}
|
||||
|
||||
targetCompatibility = 11
|
||||
sourceCompatibility = 11
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
maven { url "https://kotlin.bintray.com/kotlinx" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.1'
|
||||
implementation "com.github.hypfvieh:dbus-java:3.2.4"
|
||||
implementation "org.slf4j:slf4j-simple:1.7.30"
|
||||
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2'
|
||||
testImplementation 'org.hamcrest:hamcrest-junit:2.0.0.0'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.2'
|
||||
}
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
// verbose = true
|
||||
// freeCompilerArgs += "-XXLanguage:+NewInference"
|
||||
}
|
||||
}
|
||||
|
||||
compileTestKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/src"]
|
||||
}
|
||||
resources {
|
||||
srcDirs = ["${project.projectDir}/res"]
|
||||
}
|
||||
}
|
||||
test {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/test"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
startScripts.enabled = true
|
||||
|
||||
application {
|
||||
mainClass = 'prog8.dbus.TestdbusKt'
|
||||
mainClassName = 'prog8.dbus.TestdbusKt' // deprecated
|
||||
applicationName = 'testdbus'
|
||||
}
|
||||
|
||||
artifacts {
|
||||
archives shadowJar
|
||||
}
|
||||
|
||||
|
||||
shadowJar {
|
||||
archiveBaseName = 'prog8compilerservicedbus'
|
||||
archiveVersion = '1.0'
|
||||
// minimize()
|
||||
}
|
||||
|
||||
|
||||
test {
|
||||
// Enable JUnit 5 (Gradle 4.6+).
|
||||
useJUnitPlatform()
|
||||
|
||||
// Always run tests, even when nothing changed.
|
||||
dependsOn 'cleanTest'
|
||||
|
||||
// Show test results.
|
||||
testLogging {
|
||||
events "skipped", "failed"
|
||||
}
|
||||
}
|
||||
|
||||
task wrapper(type: Wrapper) {
|
||||
gradleVersion = '6.7'
|
||||
}
|
14
dbusCompilerService/dbusCompilerService.iml
Normal file
14
dbusCompilerService/dbusCompilerService.iml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$/../dbusCompilerService">
|
||||
<sourceFolder url="file://$MODULE_DIR$/../dbusCompilerService/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="11" jdkType="JavaSDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="library" name="kotlinx-cli-jvm" level="project" />
|
||||
<orderEntry type="library" name="dbus-java-3.2.4" level="project" />
|
||||
</component>
|
||||
</module>
|
BIN
dbusCompilerService/lib/dbus-java-3.2.4.jar
Normal file
BIN
dbusCompilerService/lib/dbus-java-3.2.4.jar
Normal file
Binary file not shown.
22
dbusCompilerService/src/prog8/dbus/IrmenDbusTest.kt
Normal file
22
dbusCompilerService/src/prog8/dbus/IrmenDbusTest.kt
Normal file
@ -0,0 +1,22 @@
|
||||
package prog8.dbus
|
||||
|
||||
import org.freedesktop.dbus.interfaces.DBusInterface
|
||||
|
||||
|
||||
interface IrmenDbusTest: DBusInterface
|
||||
{
|
||||
fun Status(address: String): Map<Int, String>
|
||||
}
|
||||
|
||||
|
||||
internal class TestService: IrmenDbusTest {
|
||||
override fun Status(address: String): Map<Int, String> {
|
||||
return mapOf(
|
||||
5 to "hello",
|
||||
42 to address
|
||||
)
|
||||
}
|
||||
|
||||
override fun isRemote() = true
|
||||
override fun getObjectPath() = "/razorvine/TestService"
|
||||
}
|
17
dbusCompilerService/src/prog8/dbus/clientmain.kt
Normal file
17
dbusCompilerService/src/prog8/dbus/clientmain.kt
Normal file
@ -0,0 +1,17 @@
|
||||
package prog8.dbus
|
||||
|
||||
|
||||
import org.freedesktop.dbus.connections.impl.DBusConnection
|
||||
|
||||
|
||||
fun main() {
|
||||
DBusConnection.getConnection(DBusConnection.DBusBusType.SESSION).use {
|
||||
println(it.names.toList())
|
||||
println(it.uniqueName)
|
||||
println(it.address)
|
||||
println(it.machineId)
|
||||
val obj = it.getRemoteObject("local.net.razorvine.dbus.test", "/razorvine/TestService", IrmenDbusTest::class.java)
|
||||
println(obj.Status("irmen"))
|
||||
}
|
||||
}
|
||||
|
18
dbusCompilerService/src/prog8/dbus/testdbus.kt
Normal file
18
dbusCompilerService/src/prog8/dbus/testdbus.kt
Normal file
@ -0,0 +1,18 @@
|
||||
package prog8.dbus
|
||||
|
||||
import org.freedesktop.dbus.connections.impl.DBusConnection
|
||||
|
||||
|
||||
fun main() {
|
||||
DBusConnection.getConnection(DBusConnection.DBusBusType.SESSION).use {
|
||||
it.requestBusName("local.net.razorvine.dbus.test")
|
||||
println(it.names.toList())
|
||||
println(it.uniqueName)
|
||||
println(it.address)
|
||||
println(it.machineId)
|
||||
val service = TestService()
|
||||
it.exportObject(service.objectPath, service)
|
||||
|
||||
Thread.sleep(100000)
|
||||
}
|
||||
}
|
@ -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
|
||||
^^^^^^^^^^^^^
|
||||
|
@ -10,10 +10,10 @@ Prog8 documentation - |version|
|
||||
What is Prog8?
|
||||
--------------
|
||||
|
||||
This is an experimental compiled programming language targeting the 8-bit
|
||||
This is a 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.
|
||||
`6510 <https://en.wikipedia.org/wiki/MOS_Technology_6510>`_ /
|
||||
`65c02 <https://en.wikipedia.org/wiki/MOS_Technology_65C02>`_ microprocessors.
|
||||
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>`_.
|
||||
The language aims to provide many conveniences over raw assembly code (even when using a macro assembler),
|
||||
@ -38,6 +38,28 @@ This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/license
|
||||
:alt: Fully playable tetris clone
|
||||
|
||||
|
||||
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``, ``sort`` and ``reverse``
|
||||
- Supports the sixteen 'virtual' 16-bit registers R0 .. R15 from the Commander X16, also on the C64.
|
||||
- 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
|
||||
------------
|
||||
|
||||
@ -52,7 +74,7 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
||||
|
||||
sub start() {
|
||||
; clear the sieve, to reset starting situation on subsequent runs
|
||||
memset(sieve, 256, false)
|
||||
sys.memset(sieve, 256, false)
|
||||
; calculate primes
|
||||
txt.print("prime numbers up to 255:\n\n")
|
||||
ubyte amount=0
|
||||
@ -64,10 +86,10 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
||||
txt.print(", ")
|
||||
amount++
|
||||
}
|
||||
txt.chrout('\n')
|
||||
txt.nl()
|
||||
txt.print("number of primes (expected 54): ")
|
||||
txt.print_ub(amount)
|
||||
txt.chrout('\n')
|
||||
txt.nl()
|
||||
}
|
||||
|
||||
sub find_next_prime() -> ubyte {
|
||||
@ -104,37 +126,6 @@ when the exact same program is compiled for the Commander X16 target, and run on
|
||||
|
||||
|
||||
|
||||
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 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
|
||||
- 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``, ``substr``, ``sort`` and ``reverse`` (and others)
|
||||
- Assembling the generated code into a program wil be done by an external cross-assembler tool.
|
||||
- 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)!
|
||||
|
||||
|
||||
.. _requirements:
|
||||
|
||||
Required tools
|
||||
@ -142,15 +133,18 @@ 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.
|
||||
A recent precompiled .exe (only 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/>`_.
|
||||
If you're targeting the CommanderX16, there's the `x16emu <https://github.com/commanderx16/x16-emulator>`_.
|
||||
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'*)
|
||||
@ -172,6 +166,7 @@ If you're targeting the CommanderX16, there's the `x16emu <https://github.com/co
|
||||
programming.rst
|
||||
syntaxreference.rst
|
||||
libraries.rst
|
||||
technical.rst
|
||||
todo.rst
|
||||
|
||||
|
||||
|
@ -16,6 +16,10 @@ 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.
|
||||
|
||||
For full details on what is available in the libraries, look at their source code here:
|
||||
https://github.com/irmen/prog8/tree/master/compiler/res/prog8lib
|
||||
|
||||
|
||||
.. 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.
|
||||
@ -30,6 +34,72 @@ as ROM/kernal subroutine definitions, memory location constants, and utility sub
|
||||
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.
|
||||
|
||||
sys (part of syslib)
|
||||
--------------------
|
||||
``target``
|
||||
A constant ubyte value designating the target machine that the program is compiled for.
|
||||
Notice that this is a compile-time constant value and is not determined on the
|
||||
system when the program is running.
|
||||
The following return values are currently defined:
|
||||
|
||||
- 16 = compiled for CommanderX16 with 65C02 CPU
|
||||
- 64 = compiled for Commodore-64 with 6502/6510 CPU
|
||||
|
||||
``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!
|
||||
|
||||
``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!
|
||||
|
||||
``rsave()``
|
||||
Saves the CPU registers and the status flags.
|
||||
You can now more or less 'safely' use the registers directly, until you
|
||||
restore them again so the generated code can carry on normally.
|
||||
Note: it's not needed to rsave() before an asm subroutine that clobbers the X register
|
||||
(which is used as the internal evaluation stack pointer).
|
||||
The compiler will take care of this situation automatically.
|
||||
Note: the 16 bit 'virtual' registers of the Commander X16 are *not* saved.
|
||||
|
||||
``rrestore()``
|
||||
Restores the CPU registers and the status flags from previously saved values.
|
||||
Note: the 16 bit 'virtual' registers of the Commander X16 are *not* restored.
|
||||
|
||||
``read_flags() -> ubyte``
|
||||
Returns the current value of the CPU status register.
|
||||
|
||||
``set_carry()``
|
||||
Sets the CPU status register Carry flag.
|
||||
|
||||
``clear_carry()``
|
||||
Clears the CPU status register Carry flag.
|
||||
|
||||
``set_irqd()``
|
||||
Sets the CPU status register Interrupt Disable flag.
|
||||
|
||||
``clear_irqd()``
|
||||
Clears the CPU status register Interrupt Disable flag.
|
||||
|
||||
``progend()``
|
||||
Returns the last address of the program in memory + 1.
|
||||
Can be used to load dynamic data after the program, instead of hardcoding something.
|
||||
|
||||
|
||||
conv
|
||||
----
|
||||
Routines to convert strings to numbers or vice versa.
|
||||
@ -54,12 +124,62 @@ diskio
|
||||
------
|
||||
Provides several routines that deal with disk drive I/O, such as:
|
||||
|
||||
- show directory
|
||||
- list files on disk, optionally filtering by a simple pattern with ? and *
|
||||
- show disk directory as-is
|
||||
- display disk drive status
|
||||
- load and save data from and to the disk
|
||||
- delete and rename files on the disk
|
||||
|
||||
|
||||
string
|
||||
------
|
||||
Provides string manipulation routines.
|
||||
|
||||
``length(str) -> ubyte length``
|
||||
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``
|
||||
|
||||
``left(source, length, target)``
|
||||
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).
|
||||
|
||||
``right(source, length, target)``
|
||||
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).
|
||||
|
||||
``slice(source, start, length, target)``
|
||||
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).
|
||||
|
||||
``find(string, char) -> uword address``
|
||||
Locates the first position of the given character in the string, returns the string starting
|
||||
with this character or $0000 if the character is not found.
|
||||
|
||||
``compare(string1, string2) -> ubyte result``
|
||||
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 string.compare for you under water automatically).
|
||||
|
||||
``copy(from, to) -> ubyte length``
|
||||
Copy a string to another, overwriting that one. Returns the length of the string that was copied.
|
||||
Often you don't have to call this explicitly and can just write ``string1 = string2``
|
||||
but this function is useful if you're dealing with addresses for instance.
|
||||
|
||||
``lower(string)``
|
||||
Lowercases the petscii-string in place.
|
||||
|
||||
``upper(string)``
|
||||
Uppercases the petscii-string in place.
|
||||
|
||||
|
||||
floats
|
||||
------
|
||||
Provides definitions for the ROM/kernel subroutines and utility routines dealing with floating
|
||||
@ -68,12 +188,36 @@ point variables. This includes ``print_f``, the routine used to print floating
|
||||
|
||||
graphics
|
||||
--------
|
||||
High-res monochrome bitmap graphics routines:
|
||||
Monochrome bitmap graphics routines, fixed 320*200 resolution:
|
||||
|
||||
- clearing the screen
|
||||
- drawing lines
|
||||
- drawing circles and discs (filled circles)
|
||||
- plotting individual pixels
|
||||
- drawing individual pixels
|
||||
- drawing lines, rectangles, filled rectangles, circles, discs
|
||||
|
||||
This library is available both on the C64 and the Cx16.
|
||||
It uses the ROM based graphics routines on the latter, and it is a very small library because of that.
|
||||
That also means though that it is constrained to 320*200 resolution on the Cx16 as well.
|
||||
Use the ``gfx2`` library if you want full-screen graphics or non-monochrome drawing.
|
||||
|
||||
|
||||
gfx2 (cx16 only)
|
||||
-----------------
|
||||
Full-screen multicolor bitmap graphics routines, available on the Cx16 machine only.
|
||||
|
||||
- multiple full-screen resolutions: 640 * 480 monochrome, and 320 * 240 monochrome and 256 colors
|
||||
- clearing screen, switching screen mode, also back to text mode is possible.
|
||||
- drawing individual pixels
|
||||
- drawing lines, rectangles, filled rectangles, circles, discs
|
||||
- drawing text inside the bitmap
|
||||
- in monochrome mode, it's possible to use a stippled drawing pattern to simulate a shade of gray.
|
||||
|
||||
|
||||
palette (cx16 only)
|
||||
--------------------
|
||||
Available for the Cx16 target. Various routines to set the display color palette.
|
||||
There are also a few better looking Commodore-64 color palettes available here,
|
||||
because the Commander X16's default colors for this (the first 16 colors) are too saturated
|
||||
and are quite different than how they looked on a VIC-II chip in a C-64.
|
||||
|
||||
|
||||
math
|
||||
|
@ -309,18 +309,21 @@ read the syntax reference on strings.
|
||||
|
||||
|
||||
.. hint::
|
||||
Strings and uwords (=memory address) can often be interchanged.
|
||||
Strings/arrays 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.
|
||||
For regular assignments you still need to use an explicit ``&`` (address-of) to take
|
||||
the address of the string or array.
|
||||
|
||||
.. caution::
|
||||
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
|
||||
It's probably best to avoid changing the contents in strings and treat them as static.
|
||||
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!
|
||||
The same is true for arrays.
|
||||
This is because the changes persist in memory. If your program exits and is restarted
|
||||
(without reloading it from disk), it will then start working with the modified strings
|
||||
instead of the original ones!
|
||||
The same is true for arrays! So be careful to (re)initialize them if needed.
|
||||
|
||||
|
||||
Structs
|
||||
@ -373,6 +376,26 @@ address you specified, and setting the varible will directly modify that memory
|
||||
&word SCREENCOLORS = $d020 ; a 16-bit word at the addres $d020-$d021
|
||||
|
||||
|
||||
Direct access to memory locations
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Normally memory locations are accessed by a *memory mapped* name, such as ``c64.BGCOL0`` that is defined
|
||||
as the memory mapped address $d021.
|
||||
|
||||
If you want to access a memory location directly (by using the address itself or via an uword pointer variable),
|
||||
without defining a memory mapped location, you can do so by enclosing the address in ``@(...)``::
|
||||
|
||||
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
|
||||
|
||||
This is the official syntax to 'dereference a pointer' as it is often named in other languages.
|
||||
You can actually also use the array indexing notation for this. It will be silently converted into
|
||||
the direct memory access expression as explained above. Note that this also means that unlike regular arrays,
|
||||
the index is not limited to an ubyte value. You can use a full uword to index a pointer variable like this::
|
||||
|
||||
pointervar[999] = 0 ; set memory byte to zero at location pointervar + 999.
|
||||
|
||||
|
||||
Converting types into other types
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@ -478,6 +501,12 @@ condition meaning
|
||||
|
||||
So ``if_cc goto target`` will directly translate into the single CPU instruction ``BCC target``.
|
||||
|
||||
.. caution::
|
||||
These special ``if_XX`` branching statements are only useful in certain specific situations where you are *certain*
|
||||
that the status register (still) contains the correct status bits.
|
||||
This is not always the case after a fuction call or other operations!
|
||||
If in doubt, check the generated assembly code!
|
||||
|
||||
.. note::
|
||||
For now, the symbols used or declared in the statement block(s) are shared with
|
||||
the same scope the if statement itself is in.
|
||||
@ -527,18 +556,6 @@ a fixed amount of memory which will not change. (You *can* change the value of
|
||||
that there is a loss of precision. You can use builtin functions such as ``round`` and ``lsb`` to convert
|
||||
to a smaller datatype, or revert to integer arithmetic.
|
||||
|
||||
Direct access to memory locations
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Normally memory locations are accessed by a *memory mapped* name, such as ``c64.BGCOL0`` that is defined
|
||||
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 ``@(...)``::
|
||||
|
||||
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
|
||||
|
||||
|
||||
Expressions
|
||||
-----------
|
||||
@ -622,6 +639,13 @@ Defining a subroutine
|
||||
Subroutines are parts of the code that can be repeatedly invoked using a subroutine call from elsewhere.
|
||||
Their definition, using the ``sub`` statement, includes the specification of the required parameters and return value.
|
||||
Subroutines can be defined in a Block, but also nested inside another subroutine. Everything is scoped accordingly.
|
||||
With ``asmsub`` you can define a low-level subroutine that is implemented in inline assembly and takes any parameters
|
||||
in registers directly.
|
||||
|
||||
Trivial subroutines can be tagged as inline to tell the compiler to copy their code
|
||||
in-place to the locations where the subroutine is called, rather than inserting an actual call and return to the
|
||||
subroutine. This may increase code size significantly and can only be used in limited scenarios, so YMMV.
|
||||
|
||||
|
||||
Calling a subroutine
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
@ -762,58 +786,8 @@ sort(array)
|
||||
Sorting strings alphabetically has to be programmed yourself if you need it.
|
||||
|
||||
|
||||
Strings and memory blocks
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
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 ``txt`` block (part of the ``textio`` 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!
|
||||
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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".
|
||||
|
||||
@ -824,6 +798,12 @@ 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.
|
||||
|
||||
.. note::
|
||||
The arguments to the mkword() function are in 'natural' order that is first the msb then the lsb.
|
||||
Don't get confused by how the system actually stores this 16-bit word value in memory (which is
|
||||
in little-endian format, so lsb first then msb)
|
||||
|
||||
|
||||
rnd()
|
||||
returns a pseudo-random byte from 0..255
|
||||
|
||||
@ -857,37 +837,31 @@ 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).
|
||||
|
||||
rsave()
|
||||
Saves the CPU registers and the status flags.
|
||||
You can now more or less 'safely' use the registers directly, until you
|
||||
restore them again so the generated code can carry on normally.
|
||||
Note: it's not needed to rsave() before an asm subroutine that clobbers the X register
|
||||
(which is used as the internal evaluation stack pointer).
|
||||
The compiler will take care of this situation automatically.
|
||||
|
||||
rrestore()
|
||||
Restores the CPU registers and the status flags from previously saved values.
|
||||
|
||||
read_flags()
|
||||
Returns the current value of the CPU status register.
|
||||
|
||||
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)
|
||||
offsetof(membername)
|
||||
Number of bytes from the start of a struct variable that this member variable is located.
|
||||
For now, this only works on members of a declared struct variable and not yet on members
|
||||
referenced from the struct type itself. This might be improved in a future version of the language.
|
||||
|
||||
swap(x, y)
|
||||
Swap the values of numerical variables (or memory locations) x and y in a fast way.
|
||||
|
||||
memory(name, size)
|
||||
Returns the address of the first location of a statically "reserved" block of memory of the given size in bytes,
|
||||
with the given name. Requesting the address of such a named memory block again later with
|
||||
the same name, will result in the same address as before.
|
||||
When reusing blocks in that way, it is required that the size argument is the same,
|
||||
otherwise you'll get a compilation error.
|
||||
This routine can be used to "reserve" parts of the memory where a normal byte array variable would
|
||||
not suffice; for instance if you need more than 256 consecutive bytes.
|
||||
The return value is just a simple uword address so it cannot be used as an array in your program.
|
||||
You can only treat it as a pointer or use it in inline assembly.
|
||||
|
||||
|
||||
Library routines
|
||||
----------------
|
||||
|
@ -122,7 +122,8 @@ Directives
|
||||
|
||||
- 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.
|
||||
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
|
||||
@ -334,6 +335,10 @@ directly access the memory. Enclose a numeric expression or literal with ``@(...
|
||||
@($d020) = 0 ; set the c64 screen border to black ("poke 53280,0")
|
||||
@(vic+$20) = 6 ; a dynamic expression to 'calculate' the address
|
||||
|
||||
The array indexing notation is syntactic sugar for such a direct memory access expression::
|
||||
|
||||
pointervar[999] = 0 ; equivalent to @(pointervar+999) = 0
|
||||
|
||||
|
||||
Constants
|
||||
^^^^^^^^^
|
||||
@ -516,11 +521,11 @@ You can still call the subroutine and not store the results.
|
||||
|
||||
**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.
|
||||
It will then store the result value in a variable if required, and *try to keep the status register untouched
|
||||
after the call* so you can often use a conditional branch statement for that. But the latter is tricky,
|
||||
make sure you check the generated assembly code.
|
||||
|
||||
If there really are multiple return values (other than a combined 16 bit return value in 2 registers),
|
||||
If there really are multiple relevant 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.
|
||||
|
||||
@ -530,7 +535,7 @@ Subroutine definitions
|
||||
|
||||
The syntax is::
|
||||
|
||||
sub <identifier> ( [parameters] ) [ -> returntype ] {
|
||||
[inline] sub <identifier> ( [parameters] ) [ -> returntype ] {
|
||||
... statements ...
|
||||
}
|
||||
|
||||
@ -543,6 +548,9 @@ The open curly brace must immediately follow the subroutine result specification
|
||||
and can have nothing following it. The close curly brace must be on its own line as well.
|
||||
The parameters is a (possibly empty) comma separated list of "<datatype> <parametername>" pairs specifying the input parameters.
|
||||
The return type has to be specified if the subroutine returns a value.
|
||||
The ``inline`` keyword makes their code copied in-place to the locations where the subroutine is called,
|
||||
rather than having an actual call and return to the subroutine. This is meant for trivial subroutines only
|
||||
as it can increase code size significantly.
|
||||
|
||||
|
||||
Assembly / ROM subroutines
|
||||
@ -556,7 +564,6 @@ This defines the ``LOAD`` subroutine at ROM memory address $FFD5, taking argumen
|
||||
and returning stuff in several registers as well. The ``clobbers`` clause is used to signify to the compiler
|
||||
what CPU registers are clobbered by the call instead of being unchanged or returning a meaningful result value.
|
||||
|
||||
|
||||
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::
|
||||
|
||||
@ -575,6 +582,26 @@ the parameters are strictly passed via cpu registers), are defined with ``asmsub
|
||||
|
||||
the statement body of such a subroutine should consist of just an inline assembly block.
|
||||
|
||||
The ``@ <register>`` part is required for rom and assembly-subroutines, as it specifies for the compiler
|
||||
what cpu registers should take the routine's arguments. You can use the regular set of registers
|
||||
(A, X, Y), the special 16-bit register pairs to take word values (AX, AY and XY) and even a processor status
|
||||
flag such as Carry (Pc).
|
||||
|
||||
.. note::
|
||||
Asmsubs can also be tagged as ``inline asmsub`` to make trivial pieces of assembly inserted
|
||||
directly instead of a call to them. Note that it is literal copy-paste of code that is done,
|
||||
so make sure the assembly is actually written to behave like such - which probably means you
|
||||
don't want a ``rts`` or ``jmp`` in it!
|
||||
|
||||
|
||||
.. note::
|
||||
The 'virtual' 16-bit registers from the Commander X16 can also be used as ``R0`` .. ``R15`` .
|
||||
This means you don't have to set them up manually before calling a subroutine that takes
|
||||
one or more parameters in those 'registers'. You can just list the arguments directly.
|
||||
*This also works on the Commodore-64!* (however they are not as efficient there because they're not in zeropage)
|
||||
In prog8 and assembly code these 'registers' are directly accessible too via
|
||||
``cx16.r0`` .. ``cx16.r15`` (they're memory mapped uword values)
|
||||
|
||||
|
||||
Expressions
|
||||
-----------
|
||||
@ -720,6 +747,13 @@ The XX corresponds to one of the eigth branching instructions so the possibiliti
|
||||
``if_cs``, ``if_cc``, ``if_eq``, ``if_ne``, ``if_pl``, ``if_mi``, ``if_vs`` and ``if_vc``.
|
||||
It can also be one of the four aliases that are easier to read: ``if_z``, ``if_nz``, ``if_pos`` and ``if_neg``.
|
||||
|
||||
.. caution::
|
||||
These special ``if_XX`` branching statements are only useful in certain specific situations where you are *certain*
|
||||
that the status register (still) contains the correct status bits.
|
||||
This is not always the case after a fuction call or other operations!
|
||||
If in doubt, check the generated assembly code!
|
||||
|
||||
|
||||
when statement ('jump table')
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
The structure of a when statement is like this::
|
||||
|
@ -11,8 +11,8 @@ Prog8 targets the following hardware:
|
||||
|
||||
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.
|
||||
- 'c64': the well-known Commodore-64
|
||||
- 'cx16': the `CommanderX16 <https://www.commanderx16.com/>`_ conceived by the 8-Bit Guy.
|
||||
|
||||
This chapter explains the relevant system details of these machines.
|
||||
|
||||
@ -131,21 +131,8 @@ The status register (P) carry flag and interrupt disable flag can be written via
|
||||
builtin functions (``set_carry()``, ``clear_carry()``, ``set_irqd()``, ``clear_irqd()``),
|
||||
and read via the ``read_flags()`` function.
|
||||
|
||||
|
||||
Subroutine Calling Conventions
|
||||
------------------------------
|
||||
|
||||
**Kernel/assembly subroutines:**
|
||||
Arguments and results are passed via registers.
|
||||
Sometimes the status register's Carry flag is used as well (as a boolean flag).
|
||||
Special care should be taken when the subroutine clobbers the X register.
|
||||
If it does, X must be saved before and restored after the call.
|
||||
|
||||
**Normal user defined subroutines:**
|
||||
Arguments and result values are passed via global variables stored in memory
|
||||
*These are not allocated on a stack* so it is not possible to create recursive calls!
|
||||
The result value(s) of a subroutine are returned on the evaluation stack,
|
||||
to make it possible to use subroutines in expressions.
|
||||
The 16 'virtual' 16-bit registers that are defined on the Commander X16 machine are not real hardware
|
||||
registers and are just 16 memory-mapped word values that you *can* access directly.
|
||||
|
||||
|
||||
IRQ Handling
|
||||
|
85
docs/source/technical.rst
Normal file
85
docs/source/technical.rst
Normal file
@ -0,0 +1,85 @@
|
||||
===============
|
||||
Technical stuff
|
||||
===============
|
||||
|
||||
All variables are static in memory
|
||||
----------------------------------
|
||||
|
||||
All variables are allocated statically, there is no concept of dynamic heap or stack frames.
|
||||
Essentially all variables are global (but scoped) and can be accessed and modified anywhere,
|
||||
but care should be taken ofcourse to avoid unexpected side effects.
|
||||
|
||||
Especially when you're dealing with interrupts or re-entrant routines: don't modify variables
|
||||
that you not own or else you will break stuff.
|
||||
|
||||
|
||||
Software stack for expression evaluation
|
||||
----------------------------------------
|
||||
|
||||
Prog8 uses a software stack to evaluate complex expressions that it can't calculate in-place or
|
||||
directly into the target variable, register, or memory location.
|
||||
|
||||
'software stack' means: seperated and not using the processor's hardware stack.
|
||||
|
||||
The software stack is implemented as follows:
|
||||
|
||||
- 2 pages of memory are allocated for this, exact locations vary per machine target.
|
||||
For the C-64 they are set at $ce00 and $cf00 (so $ce00-$cfff is reserved).
|
||||
For the Commander X16 they are set at $0400 and $0500 (so $0400-$05ff are reserved).
|
||||
- these are the high and low bytes of the values on the stack (it's a 'split 16 bit word stack')
|
||||
- for byte values just the lsb page is used, for word values both pages
|
||||
- float values (5 bytes) are chopped up into 2 words and 1 byte on this stack.
|
||||
- the X register is permanently allocated to be the stack pointer in the software stack.
|
||||
- you can use the X register as long as you're not using the software stack.
|
||||
But you *must* make sure it is saved and restored after the code that modifies it,
|
||||
otherwise the evaluation stack gets corrupted.
|
||||
|
||||
Subroutine Calling Convention
|
||||
-----------------------------
|
||||
|
||||
Calling a subroutine requires three steps:
|
||||
|
||||
#. preparing the arguments (if any) and passing them to the routine
|
||||
#. calling the routine
|
||||
#. preparig the return value (if any) and returning that from the call.
|
||||
|
||||
|
||||
Calling the routine is just a simple JSR instruction, but the other two work like this:
|
||||
|
||||
|
||||
``asmsub`` routines
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
These are usually declarations of kernel (ROM) routines or low-level assembly only routines,
|
||||
that have their arguments solely passed into specific registers.
|
||||
Sometimes even via a processor status flag such as the Carry flag.
|
||||
Return values also via designated registers.
|
||||
The processor status flag is preserved on returning so you can immediately act on that for instance
|
||||
via a special branch instruction such as ``if_z`` or ``if_cs`` etc.
|
||||
|
||||
|
||||
regular subroutines
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
- subroutine parameters are just variables scoped to the subroutine.
|
||||
- the arguments passed in a call are evaluated (using the eval-stack if needed) and then
|
||||
copied into those variables.
|
||||
This sometimes can seem inefficient but it's required to allow subroutines to work locally
|
||||
with their parameters and allow them to modify them as required, without changing the
|
||||
variables used in the call's arguments. If you want to get rid of this overhead you'll
|
||||
have to make an ``asmsub`` routine in assembly instead.
|
||||
- the return value is passed back to the caller via cpu register(s):
|
||||
Byte values will be put in ``A`` .
|
||||
Word values will be put in ``A`` + ``Y`` register pair.
|
||||
Float values will be put in the ``FAC1`` float 'register' (Basic allocated this somewhere in ram).
|
||||
|
||||
|
||||
Calls to builtin functions are treated in a special way:
|
||||
Generally if they have a single argument it's passed in a register or register pair.
|
||||
Multiple arguments are passed like a normal subroutine, into variables.
|
||||
Some builtin functions have a fully custom implementation.
|
||||
|
||||
|
||||
The compiler will warn about routines that are called and that return a value, if you're not
|
||||
doing something with that returnvalue. This can be on purpuse if you're simply not interested in it.
|
||||
Use the ``void`` keyword in front of the subroutine call to get rid of the warning in that case.
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user