mirror of
https://github.com/irmen/prog8.git
synced 2025-06-15 02:23:36 +00:00
Compare commits
332 Commits
Author | SHA1 | Date | |
---|---|---|---|
52e8a44517 | |||
59f33658ad | |||
e0315bffdc | |||
cd28d0c0e0 | |||
0baa2c8b23 | |||
4977d1fbd5 | |||
3b7a92f1b4 | |||
f6920172dd | |||
93bfc8f5f4 | |||
39b7655264 | |||
8b75ceb412 | |||
c39fc4010d | |||
8df778a515 | |||
5134ea76bf | |||
3ba37df29d | |||
e221d674d9 | |||
251f947293 | |||
41e1e1cbb0 | |||
da1bc351d2 | |||
43c0afdea0 | |||
add5bfa2ec | |||
34babfb5de | |||
4f6c45c86c | |||
e6220a464c | |||
8dcd49934a | |||
bedc3bdb56 | |||
83ceb0fde9 | |||
1d299c56e0 | |||
0d735c2ccc | |||
4094f89d4a | |||
cf1e8b194a | |||
74e5644f55 | |||
b5dc5fc615 | |||
7a7270d769 | |||
7549ddcd2b | |||
08f0303178 | |||
0d7a291b81 | |||
2265ae9600 | |||
cba502e87a | |||
ac94236614 | |||
ddf1be2a13 | |||
b7694686c2 | |||
63332c0530 | |||
8a504f8eee | |||
106fc5daa4 | |||
7accb73993 | |||
e9aa6a0956 | |||
df20467e03 | |||
ecbd9d739e | |||
8af17c295a | |||
329b28cad1 | |||
452c29574d | |||
5bedc1b333 | |||
0bf6d2f72c | |||
c09b8af491 | |||
260bcd3a55 | |||
6b5211ad12 | |||
a92ec14989 | |||
b3348eb22b | |||
bec5a261e5 | |||
4b53641e1d | |||
00071d53d5 | |||
6902834568 | |||
fa2d87f3dd | |||
44019d1a61 | |||
6f74fb49bd | |||
a303b39cf0 | |||
3e63a29c59 | |||
261c0fc9b6 | |||
895b30f7e5 | |||
b985604e22 | |||
f7953e4ef3 | |||
63483d1f0e | |||
8b981f03bf | |||
d0d0910bf2 | |||
57ac820767 | |||
b8bda867b6 | |||
05d3a2450c | |||
d40788adfa | |||
83fbf86b1c | |||
e876008427 | |||
2b43353eb4 | |||
a74403c347 | |||
2f4c6c8697 | |||
238d8197f5 | |||
53a600d87b | |||
2a0ffaf45d | |||
936b046ed9 | |||
378dcfe351 | |||
0a330b9288 | |||
a88b40d6c1 | |||
09f25ffbd9 | |||
ab1232d742 | |||
a7f56fe0fc | |||
58a9452c36 | |||
6d8c4f403f | |||
88b80fed90 | |||
acdbd0c391 | |||
d9a8cfed8c | |||
122796fbba | |||
510ca042c9 | |||
125f6205f2 | |||
8136f3df5c | |||
38d06a7e94 | |||
49db10539a | |||
8efe4c6267 | |||
04d8bb8fbf | |||
08aa13c90c | |||
d1febc0208 | |||
5980e58ac6 | |||
e1dc283d4b | |||
8be234973c | |||
7def8ff2cd | |||
340b1c2e42 | |||
7e0f7ba438 | |||
fefd9b52a8 | |||
afd155ac4f | |||
ee724eb4f1 | |||
2f1f20ea11 | |||
063bcf17d8 | |||
72509eef44 | |||
2da28864e9 | |||
4278f64682 | |||
59ae3c3fcd | |||
7fa21fbdff | |||
e95af7498e | |||
79c75adac1 | |||
d212f69d89 | |||
edf5e69d39 | |||
574eb0d174 | |||
8bd4914e2f | |||
5ebaaff64b | |||
5c9e0c9f51 | |||
8132edbb08 | |||
d29ce78c86 | |||
94bc9d7a69 | |||
e8faec0932 | |||
69ca4fe304 | |||
cd99fe46fd | |||
4825b4dc68 | |||
8d0607ef58 | |||
225295a7d8 | |||
4cd74daf53 | |||
6eb9118197 | |||
d0bd2f522c | |||
661c757236 | |||
aaa20093ef | |||
1eecdd6fa3 | |||
800b5b2a43 | |||
9d17421c66 | |||
0edd50e956 | |||
288d4f08b3 | |||
526e4b8bdc | |||
e0c5ccc16b | |||
ebc2c614d7 | |||
29f5a85158 | |||
8af2380a47 | |||
431f2a2088 | |||
e05ea887f6 | |||
95c0425151 | |||
47cbc7b1f9 | |||
e7b75d591c | |||
99f7d469f4 | |||
8a6ef17fbf | |||
5f337a0bd9 | |||
87862f772a | |||
3ab641aa21 | |||
3efa8da8e0 | |||
3e28ed4fe4 | |||
44949460ed | |||
83cc19ad6f | |||
66bb98c479 | |||
ff3f985658 | |||
2ba6c9ccbe | |||
3eaf111e7d | |||
30da26b9a9 | |||
e35ad0cc8f | |||
1a36302cf1 | |||
82a28bb555 | |||
c1ce0be451 | |||
c0a5f8fef0 | |||
702cf304d0 | |||
4dee8b6048 | |||
ec665e0cc1 | |||
aec3b82476 | |||
e83796b5b9 | |||
8eb69d6eda | |||
74b5124a42 | |||
b9706a180b | |||
8aeb8a9bb7 | |||
8f2e166a22 | |||
fdd91170dc | |||
c40ddb061b | |||
353d6cfc55 | |||
f37564c49c | |||
157484d94b | |||
7626c9fff7 | |||
1f55f9fc49 | |||
2554bc7ef8 | |||
7cb4100419 | |||
2d3b7eb878 | |||
4d01a78731 | |||
a03e36828a | |||
260fb65b06 | |||
9fb8526136 | |||
26fc5ff5e2 | |||
5060f0bb19 | |||
beaf6d449b | |||
4d68b508a2 | |||
cd825e386d | |||
095c8b2309 | |||
8b6eb74c58 | |||
aba437e5a2 | |||
efe3ed499b | |||
5595564a1f | |||
439761cb67 | |||
bee6c65293 | |||
10145b946b | |||
ebf4b50059 | |||
07cce3b3fc | |||
f2c19afd95 | |||
d159e70e1c | |||
ac693a2541 | |||
1e988116ce | |||
ec9e722927 | |||
4cd5e8c378 | |||
b759d5e06a | |||
1469033c1e | |||
c15fd75df7 | |||
73524e01a6 | |||
9e54e11113 | |||
01ac5f29db | |||
67a2241e32 | |||
72b6dc3de7 | |||
6f5b645995 | |||
458ad1de57 | |||
216f48b7c1 | |||
b2d1757e5a | |||
6e53eb9d5c | |||
e5ee5be9c5 | |||
bd237b2b95 | |||
d31cf766eb | |||
56d530ff04 | |||
0bbb2240f2 | |||
1c8e4dba73 | |||
4a9956c4a4 | |||
59c0e6ae32 | |||
94c30fc21e | |||
8bb3b3be20 | |||
85e3c2c5a2 | |||
4be381c597 | |||
6ff5470cf1 | |||
151dcfdef9 | |||
c282b4cb9f | |||
c426f4626c | |||
0e3c92626e | |||
5099525e24 | |||
e22b4cbb67 | |||
2b48828179 | |||
3e181362dd | |||
71fd98e39e | |||
71cd8b6d51 | |||
ad75fcbf7e | |||
f8b04a6357 | |||
d8fcbb78d3 | |||
8408bf3789 | |||
3e1185658e | |||
d778cdcd61 | |||
90b303fc03 | |||
eb86b1270d | |||
a1f0cc878b | |||
f2e2720b15 | |||
ec8cfe1591 | |||
22eac159e5 | |||
956b0c3fa7 | |||
a6427e0949 | |||
22031f39b0 | |||
c4673d3a67 | |||
e83e021541 | |||
c1f2ecd413 | |||
46fbe01df9 | |||
8647a8290e | |||
bac51f4b31 | |||
582aab180a | |||
5fb714fcb2 | |||
3994de77d0 | |||
24c8d1f1f4 | |||
110f877dcc | |||
9cd3a9f8e8 | |||
1464050bf5 | |||
95e9e1b550 | |||
bda1c1c1eb | |||
d020a7974a | |||
a51fad3aab | |||
3cd32778bb | |||
8d67056f84 | |||
e986973b5e | |||
448c934cba | |||
96ef7ba55d | |||
4372de1e7e | |||
af0fb88adf | |||
066233eee8 | |||
b6f85d10b0 | |||
6f75413c09 | |||
d45fe4ce74 | |||
e828c013e6 | |||
988459f744 | |||
7c701bdf3f | |||
446fc35d5c | |||
bec9cc7047 | |||
961380acb6 | |||
84c0685a60 | |||
629222f103 | |||
8c448e5bc2 | |||
b5fa6c2d0a | |||
680b2df08a | |||
09bd47f98b | |||
7f69f9ce4f | |||
4179b4e543 | |||
66364554c4 | |||
43f2448789 | |||
130cee1e70 | |||
b976360248 | |||
225bfc4164 | |||
d7ceda4d82 | |||
14d091e60a | |||
2809668ef4 | |||
bafb86e00b | |||
f5db31b8ff | |||
e1d0dbed0c | |||
1d1fe364d0 | |||
2b9316c4ff |
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>
|
28
README.md
28
README.md
@ -20,36 +20,36 @@ 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
|
||||
- 'when' statement to provide a concise jump table alternative to if/elseif chains
|
||||
- structs to group together sets of variables and manipulate them at once
|
||||
- floating point operations (requires the C64 Basic ROM routines for this)
|
||||
- abstracting away low level aspects such as ZeroPage handling, program startup, explicit memory addresses
|
||||
- various code optimizations (code structure, logical and numerical expressions, unused code removal...)
|
||||
- inline assembly allows you to have full control when every cycle or byte matters
|
||||
- 'when' statement to provide a concise jump table alternative to if/elseif chains
|
||||
- many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``, ``sort`` and ``reverse``
|
||||
- structs to group together sets of variables and manipulate them at once
|
||||
- convenience abstractions for low level aspects such as ZeroPage handling, program startup, explicit memory addresses
|
||||
- fast execution speed due to compilation to native assembly code
|
||||
- inline assembly allows you to have full control when every cycle or byte matters
|
||||
|
||||
*Rapid edit-compile-run-debug cycle:*
|
||||
|
||||
- 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), the main target.
|
||||
- "cx16": [CommanderX16](https://www.commanderx16.com) (65c02 CPU) .
|
||||
- If you only use standard kernel and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
|
||||
|
||||
|
||||
@ -61,7 +61,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).
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.10"
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.20"
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
// id "org.jetbrains.kotlin.jvm" version "1.4.10"
|
||||
// id "org.jetbrains.kotlin.jvm" version "1.4.20"
|
||||
id 'application'
|
||||
id 'org.jetbrains.dokka' version "0.9.18"
|
||||
id 'com.github.johnrengelman.shadow' version '5.2.0'
|
||||
@ -15,8 +15,8 @@ plugins {
|
||||
apply plugin: "kotlin"
|
||||
apply plugin: "java"
|
||||
|
||||
targetCompatibility = 1.8
|
||||
sourceCompatibility = 1.8
|
||||
targetCompatibility = 11
|
||||
sourceCompatibility = 11
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
@ -45,7 +45,7 @@ dependencies {
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
jvmTarget = "11"
|
||||
// verbose = true
|
||||
// freeCompilerArgs += "-XXLanguage:+NewInference"
|
||||
}
|
||||
@ -53,7 +53,7 @@ compileKotlin {
|
||||
|
||||
compileTestKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
jvmTarget = "11"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
|
||||
<orderEntry type="jdk" jdkName="11" jdkType="JavaSDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="module" module-name="parser" />
|
||||
|
@ -55,13 +55,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 +134,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 +145,7 @@ stack_ub2float .proc
|
||||
tay
|
||||
lda #0
|
||||
jsr GIVAYF
|
||||
jmp push_fac1_as_result
|
||||
jmp push_fac1._internal
|
||||
.pend
|
||||
|
||||
stack_uw2float .proc
|
||||
@ -92,7 +155,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 +209,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,27 +245,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
|
||||
|
||||
pop_float_to_indexed_var .proc
|
||||
; -- pop the float on the stack, to the memory in the array at A/Y indexed by the byte on stack
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
jsr prog8_lib.pop_index_times_5
|
||||
jsr prog8_lib.add_a_to_zpword
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jmp pop_float
|
||||
.pend
|
||||
|
||||
copy_float .proc
|
||||
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
|
||||
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
|
||||
@ -284,9 +310,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
|
||||
@ -295,6 +323,7 @@ push_fac1_as_result .proc
|
||||
jmp push_float
|
||||
.pend
|
||||
|
||||
|
||||
pow_f .proc
|
||||
; -- push f1 ** f2 on stack
|
||||
lda #<fmath_float2
|
||||
@ -310,8 +339,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
|
||||
@ -321,7 +349,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
|
||||
@ -331,7 +359,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
|
||||
@ -341,7 +369,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
|
||||
@ -351,23 +379,108 @@ mul_f .proc
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr FMULT
|
||||
jmp push_fac1_as_result
|
||||
jmp push_fac1._internal
|
||||
.pend
|
||||
|
||||
neg_f .proc
|
||||
; -- push -flt back on stack
|
||||
jsr pop_float_fac1
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr NEGOP
|
||||
jmp push_fac1_as_result
|
||||
; -- toggle the sign bit on the stack
|
||||
lda P8ESTACK_HI+3,x
|
||||
eor #$80
|
||||
sta P8ESTACK_HI+3,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
abs_f .proc
|
||||
; -- push abs(float) on stack (as float)
|
||||
jsr pop_float_fac1
|
||||
var_fac1_less_f .proc
|
||||
; -- is the float in FAC1 < the variable AY?
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr ABS
|
||||
jmp push_fac1_as_result
|
||||
jsr FCOMP
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
cmp #255
|
||||
beq +
|
||||
lda #0
|
||||
rts
|
||||
+ lda #1
|
||||
rts
|
||||
.pend
|
||||
|
||||
var_fac1_lesseq_f .proc
|
||||
; -- is the float in FAC1 <= the variable AY?
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr FCOMP
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
cmp #0
|
||||
beq +
|
||||
cmp #255
|
||||
beq +
|
||||
lda #0
|
||||
rts
|
||||
+ lda #1
|
||||
rts
|
||||
.pend
|
||||
|
||||
var_fac1_greater_f .proc
|
||||
; -- is the float in FAC1 > the variable AY?
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr FCOMP
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
cmp #1
|
||||
beq +
|
||||
lda #0
|
||||
+ rts
|
||||
.pend
|
||||
|
||||
var_fac1_greatereq_f .proc
|
||||
; -- is the float in FAC1 >= the variable AY?
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr FCOMP
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
cmp #0
|
||||
beq +
|
||||
cmp #1
|
||||
beq +
|
||||
lda #0
|
||||
rts
|
||||
+ lda #1
|
||||
rts
|
||||
.pend
|
||||
|
||||
var_fac1_notequal_f .proc
|
||||
; -- are the floats numbers in FAC1 and the variable AY *not* identical?
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr FCOMP
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
and #1
|
||||
rts
|
||||
.pend
|
||||
|
||||
vars_equal_f .proc
|
||||
; -- are the mflpt5 numbers in P8ZP_SCRATCH_W1 and AY identical?
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
bne _false
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
bne _false
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
bne _false
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
bne _false
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
bne _false
|
||||
lda #1
|
||||
rts
|
||||
_false lda #0
|
||||
rts
|
||||
.pend
|
||||
|
||||
equal_f .proc
|
||||
@ -407,6 +520,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
|
||||
@ -468,252 +615,33 @@ _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
|
||||
|
||||
|
||||
set_0_array_float .proc
|
||||
; -- set a float in an array to zero (index on stack, array in SCRATCH_ZPWORD1)
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
; -- set a float in an array to zero (index in A, array in P8ZP_SCRATCH_W1)
|
||||
sta P8ZP_SCRATCH_B1
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc P8ESTACK_LO,x
|
||||
adc P8ZP_SCRATCH_B1
|
||||
tay
|
||||
lda #0
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
@ -730,13 +658,12 @@ set_0_array_float .proc
|
||||
|
||||
|
||||
set_array_float .proc
|
||||
; -- set a float in an array to a value (index on stack, float in SCRATCH_ZPWORD1, array in SCRATCH_ZPWORD2)
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
; -- set a float in an array to a value (index in A, float in P8ZP_SCRATCH_W1, array in P8ZP_SCRATCH_W2)
|
||||
sta P8ZP_SCRATCH_B1
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc P8ESTACK_LO,x
|
||||
adc P8ZP_SCRATCH_B1
|
||||
adc P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
bcc +
|
||||
@ -747,16 +674,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
|
||||
|
@ -207,11 +207,12 @@ sub print_f (float value) {
|
||||
jsr c64.CHROUT
|
||||
iny
|
||||
bne -
|
||||
ldx floats_store_reg
|
||||
+ rts
|
||||
+ ldx floats_store_reg
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
%asminclude "library:c64/floats.asm", ""
|
||||
%asminclude "library:c64/floats_funcs.asm", ""
|
||||
|
||||
}
|
||||
|
437
compiler/res/prog8lib/c64/floats_funcs.asm
Normal file
437
compiler/res/prog8lib/c64/floats_funcs.asm
Normal file
@ -0,0 +1,437 @@
|
||||
; --- floating point builtin functions
|
||||
|
||||
|
||||
abs_f_stack .proc
|
||||
; -- push abs(AY) on stack
|
||||
jsr floats.MOVFM
|
||||
jsr floats.ABS
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
abs_f_fac1 .proc
|
||||
; -- FAC1 = abs(AY)
|
||||
jsr floats.MOVFM
|
||||
jmp floats.ABS
|
||||
.pend
|
||||
|
||||
func_atan_stack .proc
|
||||
jsr func_atan_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_atan_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr ATN
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_ceil_stack .proc
|
||||
jsr func_ceil_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_ceil_fac1 .proc
|
||||
; -- ceil: tr = int(f); if tr==f -> return else return tr+1
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
ldx #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr MOVMF
|
||||
jsr INT
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr FCOMP
|
||||
cmp #0
|
||||
beq +
|
||||
lda #<FL_ONE_const
|
||||
ldy #>FL_ONE_const
|
||||
jsr FADD
|
||||
+ ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_floor_stack .proc
|
||||
jsr func_floor_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_floor_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr INT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_round_stack .proc
|
||||
jsr func_round_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_round_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr FADDH
|
||||
jsr INT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sin_stack .proc
|
||||
jsr func_sin_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_sin_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr SIN
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cos_stack .proc
|
||||
jsr func_cos_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_cos_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr COS
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_tan_stack .proc
|
||||
jsr func_tan_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_tan_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr TAN
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_rad_stack .proc
|
||||
jsr func_rad_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_rad_fac1 .proc
|
||||
; -- convert degrees to radians (d * pi / 180)
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #<_pi_div_180
|
||||
ldy #>_pi_div_180
|
||||
jsr FMULT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
_pi_div_180 .byte 123, 14, 250, 53, 18 ; pi / 180
|
||||
.pend
|
||||
|
||||
func_deg_stack .proc
|
||||
jsr func_deg_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_deg_fac1 .proc
|
||||
; -- convert radians to degrees (d * (1/ pi * 180))
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #<_one_over_pi_div_180
|
||||
ldy #>_one_over_pi_div_180
|
||||
jsr FMULT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
_one_over_pi_div_180 .byte 134, 101, 46, 224, 211 ; 1 / (pi * 180)
|
||||
.pend
|
||||
|
||||
func_ln_stack .proc
|
||||
jsr func_ln_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_ln_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr LOG
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_log2_stack .proc
|
||||
jsr func_log2_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_log2_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr LOG
|
||||
jsr MOVEF
|
||||
lda #<FL_LOG2
|
||||
ldy #>FL_LOG2
|
||||
jsr MOVFM
|
||||
jsr FDIVT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sign_f_stack .proc
|
||||
jsr func_sign_f_into_A
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sign_f_into_A .proc
|
||||
jsr MOVFM
|
||||
jmp SIGN
|
||||
.pend
|
||||
|
||||
func_sqrt_stack .proc
|
||||
jsr func_sqrt_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_sqrt_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr SQR
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_rndf_stack .proc
|
||||
jsr func_rndf_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_rndf_fac1 .proc
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #1
|
||||
jsr FREADSA
|
||||
jsr RND ; rng into fac1
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_swap_f .proc
|
||||
; -- swap floats pointed to by SCRATCH_ZPWORD1, SCRATCH_ZPWORD2
|
||||
ldy #4
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
pha
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
pla
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
dey
|
||||
bpl -
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_reverse_f .proc
|
||||
; --- reverse an array of floats (array in P8ZP_SCRATCH_W1, num elements in A)
|
||||
_left_index = P8ZP_SCRATCH_W2
|
||||
_right_index = P8ZP_SCRATCH_W2+1
|
||||
_loop_count = P8ZP_SCRATCH_REG
|
||||
pha
|
||||
jsr a_times_5
|
||||
sec
|
||||
sbc #5
|
||||
sta _right_index
|
||||
lda #0
|
||||
sta _left_index
|
||||
pla
|
||||
lsr a
|
||||
sta _loop_count
|
||||
_loop ; push the left indexed float on the stack
|
||||
ldy _left_index
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
pha
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
pha
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
pha
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
pha
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
pha
|
||||
; copy right index float to left index float
|
||||
ldy _right_index
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
ldy _left_index
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
inc _left_index
|
||||
inc _right_index
|
||||
ldy _right_index
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
ldy _left_index
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
inc _left_index
|
||||
inc _right_index
|
||||
ldy _right_index
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
ldy _left_index
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
inc _left_index
|
||||
inc _right_index
|
||||
ldy _right_index
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
ldy _left_index
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
inc _left_index
|
||||
inc _right_index
|
||||
ldy _right_index
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
ldy _left_index
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
; pop the float off the stack into the right index float
|
||||
ldy _right_index
|
||||
pla
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
pla
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
pla
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
pla
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
pla
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
inc _left_index
|
||||
lda _right_index
|
||||
sec
|
||||
sbc #9
|
||||
sta _right_index
|
||||
dec _loop_count
|
||||
bne _loop
|
||||
rts
|
||||
|
||||
.pend
|
||||
|
||||
|
||||
|
||||
a_times_5 .proc
|
||||
sta P8ZP_SCRATCH_B1
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc P8ZP_SCRATCH_B1
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_any_f_into_A .proc
|
||||
jsr a_times_5
|
||||
jmp prog8_lib.func_any_b_into_A
|
||||
.pend
|
||||
|
||||
func_all_f_into_A .proc
|
||||
jsr a_times_5
|
||||
jmp prog8_lib.func_all_b_into_A
|
||||
.pend
|
||||
|
||||
func_any_f_stack .proc
|
||||
jsr a_times_5
|
||||
jmp prog8_lib.func_any_b_stack
|
||||
.pend
|
||||
|
||||
func_all_f_stack .proc
|
||||
jsr a_times_5
|
||||
jmp prog8_lib.func_all_b_stack
|
||||
.pend
|
||||
|
||||
func_max_f_stack .proc
|
||||
jsr func_max_f_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_max_f_fac1 .proc
|
||||
; -- max(array) -> fac1, array in P8ZP_SCRATCH_W1, num elts in A
|
||||
_loop_count = P8ZP_SCRATCH_REG
|
||||
stx floats_store_reg
|
||||
sta _loop_count
|
||||
lda #255
|
||||
sta _minmax_cmp+1 ; modifying
|
||||
lda #<_largest_neg_float
|
||||
ldy #>_largest_neg_float
|
||||
_minmax_entry jsr MOVFM
|
||||
- lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jsr FCOMP
|
||||
_minmax_cmp cmp #255 ; modified
|
||||
bne +
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jsr MOVFM
|
||||
+ lda P8ZP_SCRATCH_W1
|
||||
clc
|
||||
adc #5
|
||||
sta P8ZP_SCRATCH_W1
|
||||
bcc +
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
+ dec _loop_count
|
||||
bne -
|
||||
ldx floats_store_reg
|
||||
rts
|
||||
_largest_neg_float .byte 255,255,255,255,255 ; largest negative float -1.7014118345e+38
|
||||
.pend
|
||||
|
||||
func_min_f_stack .proc
|
||||
jsr func_min_f_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_min_f_fac1 .proc
|
||||
; -- min(array) -> fac1, array in P8ZP_SCRATCH_W1, num elts in A
|
||||
sta func_max_f_fac1._loop_count
|
||||
lda #1
|
||||
sta func_max_f_fac1._minmax_cmp+1
|
||||
lda #<_largest_pos_float
|
||||
ldy #>_largest_pos_float
|
||||
jmp func_max_f_fac1._minmax_entry
|
||||
_largest_pos_float .byte 255,127,255,255,255 ; largest positive float
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
func_sum_f_stack .proc
|
||||
jsr func_sum_f_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_sum_f_fac1 .proc
|
||||
; -- sum(array) -> fac1, array in P8ZP_SCRATCH_W1, num elts in A
|
||||
_loop_count = P8ZP_SCRATCH_REG
|
||||
stx floats_store_reg
|
||||
sta _loop_count
|
||||
lda #<FL_ZERO_const
|
||||
ldy #>FL_ZERO_const
|
||||
jsr MOVFM
|
||||
- lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jsr FADD
|
||||
lda P8ZP_SCRATCH_W1
|
||||
clc
|
||||
adc #5
|
||||
sta P8ZP_SCRATCH_W1
|
||||
bcc +
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
+ dec _loop_count
|
||||
bne -
|
||||
ldx floats_store_reg
|
||||
rts
|
||||
.pend
|
@ -12,11 +12,20 @@ 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)
|
||||
txt.fill_screen(pixelcolor << 4 | bgcolor, 0)
|
||||
@ -33,8 +42,8 @@ graphics {
|
||||
}
|
||||
word @zp d = 0
|
||||
ubyte positive_ix = true
|
||||
word @zp dx = x2 - x1 as word
|
||||
word @zp dy = y2 as word - y1 as word
|
||||
word @zp dx = x2-x1 as word
|
||||
word @zp dy = y2-y1
|
||||
if dx < 0 {
|
||||
dx = -dx
|
||||
positive_ix = false
|
||||
@ -149,21 +158,17 @@ graphics {
|
||||
ubyte ycenter_plus_xx = ycenter + xx
|
||||
ubyte ycenter_min_xx = ycenter - xx
|
||||
|
||||
for internal_plotx in xcenter to xcenter+xx {
|
||||
internal_plotx = xcenter-xx
|
||||
repeat xx*2+1 {
|
||||
internal_plot(ycenter_plus_yy)
|
||||
internal_plot(ycenter_min_yy)
|
||||
internal_plotx++
|
||||
}
|
||||
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_plotx = xcenter-yy
|
||||
repeat yy*2+1 {
|
||||
internal_plot(ycenter_plus_xx)
|
||||
internal_plot(ycenter_min_xx)
|
||||
internal_plotx++
|
||||
}
|
||||
yy++
|
||||
if decisionOver2<=0
|
||||
|
@ -204,23 +204,23 @@ romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial
|
||||
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
|
||||
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte address @ Y) ; set logical file parameters
|
||||
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
|
||||
romsub $FFC0 = OPEN() clobbers(A,X,Y) ; (via 794 ($31A)) open a logical file
|
||||
romsub $FFC0 = OPEN() clobbers(X,Y) -> ubyte @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
|
||||
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
|
||||
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) ; (via 798 ($31E)) define an input channel
|
||||
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> ubyte @Pc ; (via 798 ($31E)) define an input channel
|
||||
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
|
||||
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
|
||||
romsub $FFCF = CHRIN() clobbers(Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
|
||||
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y ; (via 816 ($330)) load from device
|
||||
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
||||
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
||||
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock
|
||||
romsub $FFE1 = STOP() clobbers(A,X) -> ubyte @ Pz, ubyte @ Pc ; (via 808 ($328)) check the STOP key
|
||||
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @ A ; (via 810 ($32A)) get a character
|
||||
romsub $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
|
||||
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @Pc, ubyte @ A ; (via 810 ($32A)) get a character
|
||||
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
|
||||
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
|
||||
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
|
||||
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use c64scr.plot for a 'safe' wrapper that preserves X.
|
||||
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
|
||||
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
|
||||
|
||||
; ---- end of C64 ROM kernal routines ----
|
||||
@ -251,8 +251,7 @@ asmsub init_system() {
|
||||
sta c64.COLOR
|
||||
lda #0
|
||||
sta c64.BGCOL0
|
||||
tax
|
||||
tay
|
||||
jsr disable_runstop_and_charsetswitch
|
||||
clc
|
||||
clv
|
||||
cli
|
||||
@ -270,6 +269,32 @@ asmsub reset_system() {
|
||||
}}
|
||||
}
|
||||
|
||||
sub wait(uword jiffies) {
|
||||
uword current_time = 0
|
||||
c64.SETTIM(0,0,0)
|
||||
|
||||
while current_time < jiffies {
|
||||
; read clock
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr c64.RDTIM
|
||||
sta current_time
|
||||
stx current_time+1
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
asmsub disable_runstop_and_charsetswitch() {
|
||||
%asm {{
|
||||
lda #$80
|
||||
sta 657 ; disable charset switching
|
||||
lda #239
|
||||
sta 808 ; disable run/stop key
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_irqvec_excl() clobbers(A) {
|
||||
%asm {{
|
||||
sei
|
||||
|
@ -16,7 +16,7 @@ const ubyte DEFAULT_HEIGHT = 25
|
||||
|
||||
|
||||
sub clear_screen() {
|
||||
clear_screenchars(' ')
|
||||
txt.chrout(147)
|
||||
}
|
||||
|
||||
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
|
||||
@ -38,13 +38,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 +53,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 +83,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 +123,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 +159,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 +195,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 -
|
||||
|
||||
|
@ -249,7 +249,26 @@ output .text "0000", $00 ; 0-terminated output buffer (to make printing ea
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str2uword(str string @ AY) -> uword @ AY {
|
||||
|
||||
asmsub str2ubyte(str string @ AY) clobbers(Y) -> ubyte @A {
|
||||
; -- returns the unsigned byte value of the string number argument in AY
|
||||
; the number may NOT be preceded by a + sign and may NOT contain spaces
|
||||
; (any non-digit character will terminate the number string that is parsed)
|
||||
%asm {{
|
||||
jmp str2uword
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str2byte(str string @ AY) clobbers(Y) -> ubyte @A {
|
||||
; -- returns the signed byte value of the string number argument in AY
|
||||
; the number may be preceded by a + or - sign but may NOT contain spaces
|
||||
; (any non-digit character will terminate the number string that is parsed)
|
||||
%asm {{
|
||||
jmp str2word
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str2uword(str string @ AY) -> uword @ AY {
|
||||
; -- returns the unsigned word value of the string number argument in AY
|
||||
; the number may NOT be preceded by a + sign and may NOT contain spaces
|
||||
; (any non-digit character will terminate the number string that is parsed)
|
||||
@ -303,7 +322,7 @@ _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)
|
||||
@ -358,4 +377,77 @@ _negative .byte 0
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub hex2uword(str string @ AY) -> uword @AY {
|
||||
; -- hexadecimal string with or without '$' to uword.
|
||||
; string may be in petscii or c64-screencode encoding.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #0
|
||||
sty P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
_loop ldy #0
|
||||
sty P8ZP_SCRATCH_B1
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
beq _stop
|
||||
cmp #'$'
|
||||
beq _skip
|
||||
cmp #7
|
||||
bcc _add_nine
|
||||
cmp #'9'
|
||||
beq _calc
|
||||
bcs _add_nine
|
||||
_calc asl P8ZP_SCRATCH_W1
|
||||
rol P8ZP_SCRATCH_W1+1
|
||||
asl P8ZP_SCRATCH_W1
|
||||
rol P8ZP_SCRATCH_W1+1
|
||||
asl P8ZP_SCRATCH_W1
|
||||
rol P8ZP_SCRATCH_W1+1
|
||||
asl P8ZP_SCRATCH_W1
|
||||
rol P8ZP_SCRATCH_W1+1
|
||||
and #$0f
|
||||
clc
|
||||
adc P8ZP_SCRATCH_B1
|
||||
ora P8ZP_SCRATCH_W1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
_skip inc P8ZP_SCRATCH_W2
|
||||
bne _loop
|
||||
inc P8ZP_SCRATCH_W2+1
|
||||
bne _loop
|
||||
_stop lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
rts
|
||||
_add_nine ldy #9
|
||||
sty P8ZP_SCRATCH_B1
|
||||
bne _calc
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub bin2uword(str string @ AY) -> uword @AY {
|
||||
; -- binary string with or without '%' to uword.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #0
|
||||
sty P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
_loop lda (P8ZP_SCRATCH_W2),y
|
||||
beq _stop
|
||||
cmp #'%'
|
||||
beq +
|
||||
asl P8ZP_SCRATCH_W1
|
||||
rol P8ZP_SCRATCH_W1+1
|
||||
and #1
|
||||
ora P8ZP_SCRATCH_W1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
+ inc P8ZP_SCRATCH_W2
|
||||
bne _loop
|
||||
inc P8ZP_SCRATCH_W2+1
|
||||
bne _loop
|
||||
_stop lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
|
87
compiler/res/prog8lib/cx16/c64colors.p8
Normal file
87
compiler/res/prog8lib/cx16/c64colors.p8
Normal file
@ -0,0 +1,87 @@
|
||||
%target cx16
|
||||
|
||||
c64colors {
|
||||
uword[] 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[] 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[] 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
|
||||
]
|
||||
|
||||
ubyte color
|
||||
|
||||
sub set_palette_pepto() {
|
||||
for color in 0 to 15 {
|
||||
uword cc = colorpalette_pepto[color]
|
||||
cx16.vpoke(1, $fa00 + color*2, lsb(cc)) ; G, B
|
||||
cx16.vpoke(1, $fa01 + color*2, msb(cc)) ; R
|
||||
}
|
||||
}
|
||||
|
||||
sub set_palette_light() {
|
||||
for color in 0 to 15 {
|
||||
uword cc = colorpalette_light[color]
|
||||
cx16.vpoke(1, $fa00 + color*2, lsb(cc)) ; G, B
|
||||
cx16.vpoke(1, $fa01 + color*2, msb(cc)) ; R
|
||||
}
|
||||
}
|
||||
|
||||
sub set_palette_dark() {
|
||||
for color in 0 to 15 {
|
||||
uword cc = colorpalette_dark[color]
|
||||
cx16.vpoke(1, $fa00 + color*2, lsb(cc)) ; G, B
|
||||
cx16.vpoke(1, $fa01 + color*2, msb(cc)) ; R
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
@ -78,10 +79,10 @@ asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
|
||||
%asm {{
|
||||
phx
|
||||
sta P8ZP_SCRATCH_REG
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_B1
|
||||
tya
|
||||
ldy P8ZP_SCRATCH_REG
|
||||
ldy P8ZP_SCRATCH_W2
|
||||
jsr GIVAYF ; load it as signed... correct afterwards
|
||||
lda P8ZP_SCRATCH_B1
|
||||
bpl +
|
||||
@ -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_REG
|
||||
sta P8ZP_SCRATCH_W2
|
||||
tya
|
||||
ldy P8ZP_SCRATCH_REG
|
||||
ldy P8ZP_SCRATCH_W2
|
||||
jmp GIVAYF ; this uses the inverse order, Y/A
|
||||
}}
|
||||
}
|
||||
@ -149,5 +150,6 @@ sub print_f (float value) {
|
||||
}
|
||||
|
||||
%asminclude "library:c64/floats.asm", ""
|
||||
%asminclude "library:c64/floats_funcs.asm", ""
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
%target cx16
|
||||
%import syslib
|
||||
%import textio
|
||||
|
||||
; bitmap pixel graphics module for the CommanderX16
|
||||
; wraps the graphics functions that are in ROM.
|
||||
@ -17,6 +18,13 @@ graphics {
|
||||
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()
|
||||
@ -35,7 +43,7 @@ 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
|
||||
ubyte @zp xx = radius
|
||||
@ -86,7 +94,7 @@ graphics {
|
||||
; 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
|
||||
; cx16.GRAPH_draw_oval(true) ; currently this call is not implemented on cx16, does a BRK
|
||||
|
||||
ubyte xx = radius
|
||||
ubyte yy = 0
|
||||
@ -99,42 +107,30 @@ graphics {
|
||||
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.r0 = xcenter-xx
|
||||
cx16.r1 = ycenter_plus_yy
|
||||
cx16.FB_cursor_position()
|
||||
repeat xx*2+1
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r1 = ycenter_min_yy
|
||||
cx16.FB_cursor_position()
|
||||
|
||||
cx16.r0 = xcenter-xx
|
||||
cx16.r1 = ycenter_min_yy
|
||||
cx16.FB_cursor_position()
|
||||
repeat xx*2+1
|
||||
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.r0 = xcenter-yy
|
||||
cx16.r1 = ycenter_plus_xx
|
||||
cx16.FB_cursor_position()
|
||||
repeat yy*2+1
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r1 = ycenter_min_yy
|
||||
cx16.FB_cursor_position()
|
||||
|
||||
cx16.r0 = xcenter-yy
|
||||
cx16.r1 = ycenter_min_xx
|
||||
cx16.FB_cursor_position()
|
||||
repeat yy*2+1
|
||||
cx16.FB_set_pixel(1)
|
||||
}
|
||||
for plotx in xcenter to xcenter+yy {
|
||||
cx16.r0 = plotx
|
||||
cx16.r1 = ycenter_plus_xx
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r1 = ycenter_min_xx
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
}
|
||||
for plotx in xcenter-yy to xcenter {
|
||||
cx16.r0 = plotx
|
||||
cx16.r1 = ycenter_plus_xx
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r1 = ycenter_min_xx
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
}
|
||||
|
||||
yy++
|
||||
if decisionOver2<=0
|
||||
decisionOver2 += 2*yy+1
|
||||
|
@ -12,48 +12,48 @@ c64 {
|
||||
|
||||
; ---- kernal routines, these are the same as on the Commodore-64 (hence the same block name) ----
|
||||
|
||||
; STROUT --> use screen.print
|
||||
; CLEARSCR -> use screen.clear_screen
|
||||
; HOMECRSR -> use screen.plot
|
||||
; STROUT --> use txt.print
|
||||
; CLEARSCR -> use txt.clear_screen
|
||||
; HOMECRSR -> use txt.plot
|
||||
|
||||
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
|
||||
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
|
||||
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
|
||||
romsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
|
||||
romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
|
||||
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
|
||||
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
|
||||
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
|
||||
romsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
|
||||
romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
|
||||
romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
|
||||
romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
|
||||
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
|
||||
romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> ubyte @A, uword @ XY ; read/set top of memory pointer, returns number of banks in A
|
||||
romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
|
||||
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
|
||||
romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set top of memory pointer
|
||||
romsub $FF9C = MEMBOT(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
|
||||
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
|
||||
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
|
||||
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
|
||||
romsub $FFA5 = ACPTR() -> ubyte @ A ; (alias: IECIN) input byte from serial bus
|
||||
romsub $FFA8 = CIOUT(ubyte databyte @ A) ; (alias: IECOUT) output byte to serial bus
|
||||
romsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK
|
||||
romsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN
|
||||
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
|
||||
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
|
||||
romsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK
|
||||
romsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN
|
||||
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
|
||||
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
|
||||
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
|
||||
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte address @ Y) ; set logical file parameters
|
||||
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
|
||||
romsub $FFC0 = OPEN() clobbers(A,X,Y) ; (via 794 ($31A)) open a logical file
|
||||
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
|
||||
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) ; (via 798 ($31E)) define an input channel
|
||||
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
|
||||
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
|
||||
romsub $FFCF = CHRIN() clobbers(Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||
romsub $FFC0 = OPEN() clobbers(X,Y) -> ubyte @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
|
||||
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
|
||||
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> ubyte @Pc ; (via 798 ($31E)) define an input channel
|
||||
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
|
||||
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
|
||||
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
|
||||
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y ; (via 816 ($330)) load from device
|
||||
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
||||
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
||||
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock
|
||||
romsub $FFE1 = STOP() clobbers(A,X) -> ubyte @ Pz, ubyte @ Pc ; (via 808 ($328)) check the STOP key
|
||||
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @ A ; (via 810 ($32A)) get a character
|
||||
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
|
||||
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
|
||||
romsub $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
|
||||
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @Pc, ubyte @ A ; (via 810 ($32A)) get a character
|
||||
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
|
||||
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
|
||||
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
|
||||
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use screen.plot for a 'safe' wrapper that preserves X.
|
||||
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
|
||||
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
|
||||
|
||||
}
|
||||
@ -169,20 +169,18 @@ cx16 {
|
||||
; ---- Commander X-16 additions on top of C64 kernal routines ----
|
||||
; spelling of the names is taken from the Commander X-16 rom sources
|
||||
|
||||
; TODO specify the correct clobbers for alle these functions, for simplicity all 3 regs are marked clobbered atm
|
||||
|
||||
; supported C128 additions
|
||||
romsub $ff4a = close_all() clobbers(A,X,Y)
|
||||
romsub $ff59 = lkupla() clobbers(A,X,Y)
|
||||
romsub $ff5c = lkupsa() clobbers(A,X,Y)
|
||||
romsub $ff4a = close_all(ubyte device @A) clobbers(A,X,Y)
|
||||
romsub $ff59 = lkupla(ubyte la @A) clobbers(A,X,Y)
|
||||
romsub $ff5c = lkupsa(ubyte sa @Y) clobbers(A,X,Y)
|
||||
romsub $ff5f = screen_set_mode(ubyte mode @A) clobbers(A, X, Y) -> ubyte @Pc
|
||||
romsub $ff62 = screen_set_charset(ubyte charset @A, uword charsetptr @XY) clobbers(A,X,Y) ; incompatible with C128 dlchr()
|
||||
romsub $ff65 = pfkey() clobbers(A,X,Y)
|
||||
romsub $ff6e = jsrfar() clobbers(A,X,Y)
|
||||
romsub $ff74 = fetch() clobbers(A,X,Y)
|
||||
romsub $ff77 = stash() clobbers(A,X,Y)
|
||||
romsub $ff7a = cmpare() clobbers(A,X,Y)
|
||||
romsub $ff7d = primm() clobbers(A,X,Y)
|
||||
; not yet supported: romsub $ff65 = pfkey() clobbers(A,X,Y)
|
||||
romsub $ff6e = jsrfar()
|
||||
romsub $ff74 = fetch(ubyte bank @X, ubyte index @Y) clobbers(X) -> ubyte @A
|
||||
romsub $ff77 = stash(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X)
|
||||
romsub $ff7a = cmpare(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X)
|
||||
romsub $ff7d = primm()
|
||||
|
||||
; X16 additions
|
||||
romsub $ff44 = macptr() clobbers(A,X,Y)
|
||||
@ -193,7 +191,9 @@ 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) ; outout args: r0, r1, r2, r3L
|
||||
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) ; outout args: r0, r1, r2, r3L
|
||||
|
||||
; TODO specify the correct clobbers for alle these functions below, we now assume all 3 regs are clobbered
|
||||
|
||||
; high level graphics & fonts
|
||||
romsub $ff20 = GRAPH_init() clobbers(A,X,Y) ; uses vectors=r0
|
||||
@ -220,7 +220,7 @@ romsub $ff08 = FB_get_pixels() clobbers(A,X,Y) ; uses ptr=r0, count=r1
|
||||
romsub $ff0b = FB_set_pixel(ubyte color @A) clobbers(A,X,Y)
|
||||
romsub $ff0e = FB_set_pixels() clobbers(A,X,Y) ; uses ptr=r0, count=r1
|
||||
romsub $ff11 = FB_set_8_pixels(ubyte pattern @A, ubyte color @X) clobbers(A,X,Y)
|
||||
romsub $ff14 = FB_set_8_pixels_opaque(ubyte pattern @A, ubyte color1 @X, ubyte color2 @Y) clobbers(A,X,Y) ; also uses mask=r0L
|
||||
romsub $ff14 = FB_set_8_pixels_opaque(ubyte mask @A, ubyte color1 @X, ubyte color2 @Y) clobbers(A,X,Y) ; also uses r0L=pattern
|
||||
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
|
||||
@ -242,9 +242,96 @@ romsub $fecf = entropy_get() -> ubyte @A, ubyte @X, ubyte @Y
|
||||
romsub $fecc = monitor() clobbers(A,X,Y)
|
||||
|
||||
|
||||
|
||||
; ---- end of kernal routines ----
|
||||
|
||||
; ---- utilities -----
|
||||
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 {{
|
||||
stz cx16.VERA_CTRL
|
||||
and #1
|
||||
sta cx16.VERA_ADDR_H
|
||||
sty cx16.VERA_ADDR_M
|
||||
stx cx16.VERA_ADDR_L
|
||||
lda cx16.VERA_DATA0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub vpoke(ubyte bank, uword address, ubyte value) {
|
||||
; -- write a single byte to VERA's video memory
|
||||
; note: inefficient when writing multiple sequential bytes!
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
lda bank
|
||||
and #1
|
||||
sta cx16.VERA_ADDR_H
|
||||
lda address
|
||||
sta cx16.VERA_ADDR_L
|
||||
lda address+1
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda value
|
||||
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
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
|
||||
sub wait(uword jiffies) {
|
||||
uword current_time = 0
|
||||
c64.SETTIM(0,0,0)
|
||||
|
||||
while current_time < jiffies {
|
||||
; read clock
|
||||
%asm {{
|
||||
phx
|
||||
jsr c64.RDTIM
|
||||
sta current_time
|
||||
stx current_time+1
|
||||
plx
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
; ---- system stuff -----
|
||||
|
||||
asmsub init_system() {
|
||||
; Initializes the machine to a sane starting state.
|
||||
; Called automatically by the loader program logic.
|
||||
|
@ -16,7 +16,7 @@ const ubyte DEFAULT_HEIGHT = 60
|
||||
|
||||
|
||||
sub clear_screen() {
|
||||
clear_screenchars(' ')
|
||||
txt.chrout(147)
|
||||
}
|
||||
|
||||
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
|
||||
@ -134,13 +134,16 @@ _la lda #0 ; modified
|
||||
ubyte[16] color_to_charcode = [$90,$05,$1c,$9f,$9c,$1e,$1f,$9e,$81,$95,$96,$97,$98,$99,$9a,$9b]
|
||||
|
||||
sub color (ubyte txtcol) {
|
||||
c64.CHROUT(color_to_charcode[txtcol & 15])
|
||||
txtcol &= 15
|
||||
c64.CHROUT(color_to_charcode[txtcol])
|
||||
}
|
||||
|
||||
sub color2 (ubyte txtcol, ubyte bgcol) {
|
||||
c64.CHROUT(color_to_charcode[bgcol & 15])
|
||||
txtcol &= 15
|
||||
bgcol &= 15
|
||||
c64.CHROUT(color_to_charcode[bgcol])
|
||||
c64.CHROUT(1) ; switch fg and bg colors
|
||||
c64.CHROUT(color_to_charcode[txtcol & 15])
|
||||
c64.CHROUT(color_to_charcode[txtcol])
|
||||
}
|
||||
|
||||
sub lowercase() {
|
||||
@ -151,10 +154,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
|
||||
@ -195,10 +197,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
|
||||
@ -247,10 +248,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
|
||||
@ -297,10 +297,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
|
||||
|
29
compiler/res/prog8lib/cx16logo.p8
Normal file
29
compiler/res/prog8lib/cx16logo.p8
Normal file
@ -0,0 +1,29 @@
|
||||
%import textio
|
||||
|
||||
cx16logo {
|
||||
sub logo_at(ubyte column, ubyte row) {
|
||||
uword strptr
|
||||
for strptr in logo_lines {
|
||||
txt.plot(column, row)
|
||||
txt.print(strptr)
|
||||
row++
|
||||
}
|
||||
}
|
||||
|
||||
sub logo() {
|
||||
uword strptr
|
||||
for strptr in logo_lines
|
||||
txt.print(strptr)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
str[] logo_lines = [
|
||||
"\uf10d\uf11a\uf139\uf11b \uf11a\uf13a\uf11b\n",
|
||||
"\uf10b\uf11a▎\uf139\uf11b \uf11a\uf13a\uf130\uf11b\n",
|
||||
"\uf10f\uf11a▌ \uf139\uf11b \uf11a\uf13a \uf11b▌\n",
|
||||
"\uf102 \uf132\uf11a▖\uf11b \uf11a▗\uf11b\uf132\n",
|
||||
"\uf10e ▂\uf11a▘\uf11b \uf11a▝\uf11b▂\n",
|
||||
"\uf104 \uf11a \uf11b\uf13a\uf11b \uf139\uf11a \uf11b\n",
|
||||
"\uf101\uf130\uf13a \uf139▎\uf100"
|
||||
]
|
||||
}
|
374
compiler/res/prog8lib/diskio.p8
Normal file
374
compiler/res/prog8lib/diskio.p8
Normal file
@ -0,0 +1,374 @@
|
||||
%import textio
|
||||
%import syslib
|
||||
|
||||
; Note: this code is compatible with C64 and CX16.
|
||||
|
||||
diskio {
|
||||
|
||||
|
||||
sub directory(ubyte drivenumber) -> ubyte {
|
||||
; -- Prints the directory contents of disk drive 8-11 to the screen. Returns success.
|
||||
|
||||
c64.SETNAM(1, "$")
|
||||
c64.SETLFS(13, drivenumber, 0)
|
||||
void c64.OPEN() ; open 13,8,0,"$"
|
||||
if_cs
|
||||
goto io_error
|
||||
void c64.CHKIN(13) ; use #13 as input channel
|
||||
if_cs
|
||||
goto io_error
|
||||
|
||||
repeat 4 {
|
||||
void c64.CHRIN() ; skip the 4 prologue bytes
|
||||
}
|
||||
|
||||
; while not key pressed / EOF encountered, read data.
|
||||
ubyte status = c64.READST()
|
||||
while not status {
|
||||
ubyte low = c64.CHRIN()
|
||||
ubyte high = c64.CHRIN()
|
||||
txt.print_uw(mkword(high, low))
|
||||
txt.chrout(' ')
|
||||
ubyte @zp char
|
||||
do {
|
||||
char = c64.CHRIN()
|
||||
txt.chrout(char)
|
||||
} until char==0
|
||||
txt.chrout('\n')
|
||||
void c64.CHRIN() ; skip 2 bytes
|
||||
void c64.CHRIN()
|
||||
status = c64.READST()
|
||||
void c64.STOP()
|
||||
if_z
|
||||
break
|
||||
}
|
||||
|
||||
io_error:
|
||||
status = c64.READST()
|
||||
c64.CLRCHN() ; restore default i/o devices
|
||||
c64.CLOSE(13)
|
||||
|
||||
if status and status != 64 { ; 64=end of file
|
||||
txt.print("\ni/o error, status: ")
|
||||
txt.print_ub(status)
|
||||
txt.chrout('\n')
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
; internal variables for the iterative file lister / loader
|
||||
ubyte list_suffixmatch
|
||||
ubyte list_pattern_size
|
||||
ubyte list_skip_disk_name
|
||||
uword list_pattern
|
||||
uword list_blocks
|
||||
ubyte iteration_in_progress = false
|
||||
str list_filename = "?" * 32
|
||||
|
||||
|
||||
; ----- get a list of files (uses iteration functions internally -----
|
||||
|
||||
sub list_files(ubyte drivenumber, uword pattern, ubyte suffixmatch, uword name_ptrs, ubyte max_names) -> ubyte {
|
||||
; -- fill the array 'name_ptrs' with (pointers to) the names of the files requested.
|
||||
ubyte[256] names_buffer
|
||||
ubyte[256] names_buffer1 ; to store a bit more names
|
||||
uword buf_ptr = &names_buffer
|
||||
ubyte files_found = 0
|
||||
if lf_start_list(drivenumber, pattern, suffixmatch) {
|
||||
while lf_next_entry() {
|
||||
@(name_ptrs) = lsb(buf_ptr)
|
||||
name_ptrs++
|
||||
@(name_ptrs) = msb(buf_ptr)
|
||||
name_ptrs++
|
||||
buf_ptr += strcopy(diskio.list_filename, buf_ptr) + 1
|
||||
files_found++
|
||||
if buf_ptr - &names_buffer > (len(names_buffer) + len(names_buffer1) - 18)
|
||||
break
|
||||
if files_found == max_names
|
||||
break
|
||||
}
|
||||
lf_end_list()
|
||||
}
|
||||
return files_found
|
||||
}
|
||||
|
||||
; ----- iterative file lister functions -----
|
||||
|
||||
sub lf_start_list(ubyte drivenumber, uword pattern, ubyte suffixmatch) -> ubyte {
|
||||
; -- start an iterative file listing with optional prefix or suffix name matching.
|
||||
; note: only a single iteration loop can be active at a time!
|
||||
lf_end_list()
|
||||
list_pattern = pattern
|
||||
list_suffixmatch = suffixmatch
|
||||
list_skip_disk_name = true
|
||||
iteration_in_progress = true
|
||||
if pattern==0
|
||||
list_pattern_size = 0
|
||||
else
|
||||
list_pattern_size = strlen(pattern)
|
||||
|
||||
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_z
|
||||
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 list_pattern_size {
|
||||
; do filename matching
|
||||
if list_suffixmatch
|
||||
rightstr(list_filename, filename, list_pattern_size)
|
||||
else
|
||||
leftstr(list_filename, filename, list_pattern_size)
|
||||
if strcmp(filename, list_pattern)==0
|
||||
return true
|
||||
} else
|
||||
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 -----
|
||||
|
||||
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(strlen(filenameptr), filenameptr)
|
||||
c64.SETLFS(11, drivenumber, 0)
|
||||
void c64.OPEN() ; open 11,8,0,"filename"
|
||||
if_cc {
|
||||
iteration_in_progress = true
|
||||
void c64.CHKIN(11) ; use #11 as input channel
|
||||
if_cc
|
||||
return true
|
||||
}
|
||||
f_close()
|
||||
return false
|
||||
}
|
||||
|
||||
sub f_read(uword bufferpointer, uword buffersize) -> uword {
|
||||
if not iteration_in_progress
|
||||
return 0
|
||||
|
||||
uword actual = 0
|
||||
void c64.CHKIN(11) ; use #11 as input channel again
|
||||
repeat buffersize {
|
||||
ubyte data = c64.CHRIN()
|
||||
@(bufferpointer) = data
|
||||
bufferpointer++
|
||||
actual++
|
||||
ubyte status = c64.READST()
|
||||
if status==64
|
||||
f_close() ; end of file, close it
|
||||
if status
|
||||
return actual
|
||||
}
|
||||
return actual
|
||||
}
|
||||
|
||||
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
|
||||
goto io_error
|
||||
void c64.CHKIN(15) ; use #15 as input channel
|
||||
if_cs
|
||||
goto io_error
|
||||
|
||||
while not c64.READST() {
|
||||
@(messageptr) = c64.CHRIN()
|
||||
messageptr++
|
||||
}
|
||||
|
||||
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) -> ubyte {
|
||||
c64.SETNAM(strlen(filenameptr), filenameptr)
|
||||
c64.SETLFS(1, drivenumber, 0)
|
||||
uword end_address = address + size
|
||||
|
||||
%asm {{
|
||||
lda address
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda address+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #<P8ZP_SCRATCH_W1
|
||||
ldx end_address
|
||||
ldy end_address+1
|
||||
jsr c64.SAVE
|
||||
php
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
plp
|
||||
}}
|
||||
|
||||
ubyte result=0
|
||||
if_cc
|
||||
result = c64.READST()==0
|
||||
|
||||
c64.CLRCHN()
|
||||
c64.CLOSE(1)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
sub load(ubyte drivenumber, uword filenameptr, uword address_override) -> uword {
|
||||
c64.SETNAM(strlen(filenameptr), filenameptr)
|
||||
ubyte secondary = 1
|
||||
uword end_of_load = 0
|
||||
if address_override
|
||||
secondary = 0
|
||||
c64.SETLFS(1, drivenumber, secondary)
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #0
|
||||
ldx address_override
|
||||
ldy address_override+1
|
||||
jsr c64.LOAD
|
||||
bcs +
|
||||
stx end_of_load
|
||||
sty end_of_load+1
|
||||
+ ldx P8ZP_SCRATCH_REG
|
||||
}}
|
||||
|
||||
c64.CLRCHN()
|
||||
c64.CLOSE(1)
|
||||
|
||||
if end_of_load
|
||||
return end_of_load - address_override
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
str filename = "0:??????????????????????????????????????"
|
||||
|
||||
sub delete(ubyte drivenumber, uword filenameptr) {
|
||||
; -- delete a file on the drive
|
||||
ubyte flen = strlen(filenameptr)
|
||||
filename[0] = 's'
|
||||
filename[1] = ':'
|
||||
memcopy(filenameptr, &filename+2, flen+1)
|
||||
c64.SETNAM(flen+2, filename)
|
||||
c64.SETLFS(1, drivenumber, 15)
|
||||
void c64.OPEN()
|
||||
c64.CLRCHN()
|
||||
c64.CLOSE(1)
|
||||
}
|
||||
|
||||
sub rename(ubyte drivenumber, uword oldfileptr, uword newfileptr) {
|
||||
; -- rename a file on the drive
|
||||
ubyte flen_old = strlen(oldfileptr)
|
||||
ubyte flen_new = strlen(newfileptr)
|
||||
filename[0] = 'r'
|
||||
filename[1] = ':'
|
||||
memcopy(newfileptr, &filename+2, flen_new)
|
||||
filename[flen_new+2] = '='
|
||||
memcopy(oldfileptr, &filename+3+flen_new, flen_old+1)
|
||||
c64.SETNAM(3+flen_new+flen_old, filename)
|
||||
c64.SETLFS(1, drivenumber, 15)
|
||||
void c64.OPEN()
|
||||
c64.CLRCHN()
|
||||
c64.CLOSE(1)
|
||||
}
|
||||
}
|
@ -774,6 +774,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
|
||||
@ -781,7 +806,7 @@ mul_byte_3 .proc
|
||||
sta P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
clc
|
||||
adc P8P_P8ZP_SCRATCH_REG
|
||||
adc P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
@ -1241,48 +1266,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 +1352,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
|
||||
@ -1469,3 +1508,37 @@ shift_right_w_3 .proc
|
||||
jmp shift_right_w_7._shift3
|
||||
.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:
|
||||
|
||||
lsr_byte_A .proc
|
||||
; -- lsr signed byte in A times the value in Y (assume >0)
|
||||
cmp #0
|
||||
bmi _negative
|
||||
- lsr a
|
||||
dey
|
||||
bne -
|
||||
rts
|
||||
_negative lsr a
|
||||
ora #$80
|
||||
dey
|
||||
bne _negative
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
1369
compiler/res/prog8lib/prog8_funcs.asm
Normal file
1369
compiler/res/prog8lib/prog8_funcs.asm
Normal file
File diff suppressed because it is too large
Load Diff
1075
compiler/res/prog8lib/prog8_lib.asm
Normal file
1075
compiler/res/prog8lib/prog8_lib.asm
Normal file
File diff suppressed because it is too large
Load Diff
@ -5,5 +5,6 @@
|
||||
; indent format: TABS, size=8
|
||||
|
||||
prog8_lib {
|
||||
%asminclude "library:prog8lib.asm", ""
|
||||
%asminclude "library:prog8_lib.asm", ""
|
||||
%asminclude "library:prog8_funcs.asm", ""
|
||||
}
|
File diff suppressed because it is too large
Load Diff
48
compiler/res/prog8lib/test_stack.p8
Normal file
48
compiler/res/prog8lib/test_stack.p8
Normal file
@ -0,0 +1,48 @@
|
||||
%import textio
|
||||
|
||||
test_stack {
|
||||
|
||||
asmsub test() {
|
||||
%asm {{
|
||||
stx _saveX
|
||||
lda #13
|
||||
jsr txt.chrout
|
||||
lda #'-'
|
||||
ldy #12
|
||||
- jsr txt.chrout
|
||||
dey
|
||||
bne -
|
||||
lda #13
|
||||
jsr txt.chrout
|
||||
lda #'x'
|
||||
jsr txt.chrout
|
||||
lda #'='
|
||||
jsr txt.chrout
|
||||
lda _saveX
|
||||
jsr txt.print_ub
|
||||
lda #' '
|
||||
jsr txt.chrout
|
||||
lda #'s'
|
||||
jsr txt.chrout
|
||||
lda #'p'
|
||||
jsr txt.chrout
|
||||
lda #'='
|
||||
jsr txt.chrout
|
||||
tsx
|
||||
txa
|
||||
jsr txt.print_ub
|
||||
lda #13
|
||||
jsr txt.chrout
|
||||
lda #'-'
|
||||
ldy #12
|
||||
- jsr txt.chrout
|
||||
dey
|
||||
bne -
|
||||
lda #13
|
||||
jsr txt.chrout
|
||||
ldx _saveX
|
||||
rts
|
||||
_saveX .byte 0
|
||||
}}
|
||||
}
|
||||
}
|
@ -1 +1 @@
|
||||
4.3
|
||||
5.4
|
||||
|
@ -38,6 +38,7 @@ private fun compileMain(args: Array<String>) {
|
||||
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)
|
||||
@ -54,32 +55,42 @@ private fun compileMain(args: Array<String>) {
|
||||
exitProcess(1)
|
||||
}
|
||||
|
||||
if(watchMode && moduleFiles.size<=1) {
|
||||
if(watchMode) {
|
||||
val watchservice = FileSystems.getDefault().newWatchService()
|
||||
|
||||
while(true) {
|
||||
val filepath = pathFrom(moduleFiles.single()).normalize()
|
||||
println("Continuous watch mode active. Main module: $filepath")
|
||||
println("Continuous watch mode active. Modules: $moduleFiles")
|
||||
val results = mutableListOf<CompilationResult>()
|
||||
for(filepathRaw in moduleFiles) {
|
||||
val filepath = pathFrom(filepathRaw).normalize()
|
||||
val compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, slowCodegenWarnings, compilationTarget, outputPath)
|
||||
results.add(compilationResult)
|
||||
}
|
||||
|
||||
try {
|
||||
val compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, 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 allImportedFiles = results.flatMap { it.importedFiles }
|
||||
println("Imported files (now watching:)")
|
||||
for (importedFile in allImportedFiles) {
|
||||
print(" ")
|
||||
println(importedFile)
|
||||
val watchDir = importedFile.parent ?: Path.of(".")
|
||||
watchDir.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY)
|
||||
}
|
||||
println("[${LocalDateTime.now().withNano(0)}] Waiting for file changes.")
|
||||
|
||||
var recompile=false
|
||||
while(!recompile) {
|
||||
val event = watchservice.take()
|
||||
for(changed in event.pollEvents()) {
|
||||
for (changed in event.pollEvents()) {
|
||||
val changedPath = changed.context() as Path
|
||||
println(" change detected: $changedPath")
|
||||
if(allImportedFiles.any { it.fileName == changedPath.fileName }) {
|
||||
println(" change detected: $changedPath")
|
||||
recompile = true
|
||||
}
|
||||
}
|
||||
event.reset()
|
||||
println("\u001b[H\u001b[2J") // clear the screen
|
||||
} catch (x: Exception) {
|
||||
throw x
|
||||
}
|
||||
|
||||
println("\u001b[H\u001b[2J") // clear the screen
|
||||
}
|
||||
|
||||
} else {
|
||||
@ -87,7 +98,7 @@ private fun compileMain(args: Array<String>) {
|
||||
val filepath = pathFrom(filepathRaw).normalize()
|
||||
val compilationResult: CompilationResult
|
||||
try {
|
||||
compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, compilationTarget, outputPath)
|
||||
compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, slowCodegenWarnings, compilationTarget, outputPath)
|
||||
if(!compilationResult.success)
|
||||
exitProcess(1)
|
||||
} catch (x: ParsingFailedError) {
|
||||
|
@ -53,12 +53,14 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
}
|
||||
|
||||
override fun visit(expr: BinaryExpression) {
|
||||
output("(")
|
||||
expr.left.accept(this)
|
||||
if(expr.operator.any { it.isLetter() })
|
||||
output(" ${expr.operator} ")
|
||||
else
|
||||
output(expr.operator)
|
||||
expr.right.accept(this)
|
||||
output(")")
|
||||
}
|
||||
|
||||
override fun visit(directive: Directive) {
|
||||
@ -85,7 +87,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
DataType.ARRAY_W -> "word["
|
||||
DataType.ARRAY_F -> "float["
|
||||
DataType.STRUCT -> "" // the name of the struct is enough
|
||||
else -> "?????2"
|
||||
else -> "?????"
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,10 +115,14 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
VarDeclType.CONST -> output("const ")
|
||||
VarDeclType.MEMORY -> output("&")
|
||||
}
|
||||
output(decl.struct?.name ?: "")
|
||||
|
||||
if(decl.datatype==DataType.STRUCT && decl.struct!=null)
|
||||
output(decl.struct!!.name)
|
||||
|
||||
output(datatypeString(decl.datatype))
|
||||
if(decl.arraysize!=null) {
|
||||
decl.arraysize!!.index.accept(this)
|
||||
decl.arraysize!!.indexNum?.accept(this)
|
||||
decl.arraysize!!.indexVar?.accept(this)
|
||||
}
|
||||
if(decl.isArray)
|
||||
output("]")
|
||||
@ -137,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 -> "?????"
|
||||
@ -338,7 +343,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
output("do ")
|
||||
untilLoop.body.accept(this)
|
||||
output(" until ")
|
||||
untilLoop.untilCondition.accept(this)
|
||||
untilLoop.condition.accept(this)
|
||||
}
|
||||
|
||||
override fun visit(returnStmt: Return) {
|
||||
@ -347,9 +352,10 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
}
|
||||
|
||||
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
|
||||
arrayIndexedExpression.identifier.accept(this)
|
||||
arrayIndexedExpression.arrayvar.accept(this)
|
||||
output("[")
|
||||
arrayIndexedExpression.arrayspec.index.accept(this)
|
||||
arrayIndexedExpression.indexer.indexNum?.accept(this)
|
||||
arrayIndexedExpression.indexer.indexVar?.accept(this)
|
||||
output("]")
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,18 @@ interface Node {
|
||||
throw FatalAstException("scope missing from $this")
|
||||
}
|
||||
|
||||
fun definingBlock(): Block {
|
||||
if(this is Block)
|
||||
return this
|
||||
return findParentNode<Block>(this)!!
|
||||
}
|
||||
|
||||
fun containingStatement(): Statement {
|
||||
if(this is Statement)
|
||||
return this
|
||||
return findParentNode<Statement>(this)!!
|
||||
}
|
||||
|
||||
fun replaceChildNode(node: Node, replacement: Node)
|
||||
}
|
||||
|
||||
@ -44,6 +56,7 @@ interface IFunctionCall {
|
||||
var args: MutableList<Expression>
|
||||
}
|
||||
|
||||
|
||||
interface INameScope {
|
||||
val name: String
|
||||
val position: Position
|
||||
@ -137,7 +150,15 @@ interface INameScope {
|
||||
}
|
||||
return null
|
||||
} else {
|
||||
// unqualified name, find the scope the localContext is in, look in that first
|
||||
// unqualified name
|
||||
// special case: the do....until statement can also look INSIDE the anonymous scope
|
||||
if(localContext.parent.parent is UntilLoop) {
|
||||
val symbolFromInnerScope = (localContext.parent.parent as UntilLoop).body.getLabelOrVariable(scopedName[0])
|
||||
if(symbolFromInnerScope!=null)
|
||||
return symbolFromInnerScope
|
||||
}
|
||||
|
||||
// find the scope the localContext is in, look in that first
|
||||
var statementScope = localContext
|
||||
while(statementScope !is ParentSentinel) {
|
||||
val localScope = statementScope.definingScope()
|
||||
@ -155,7 +176,6 @@ interface INameScope {
|
||||
}
|
||||
|
||||
fun containsCodeOrVars() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm"}
|
||||
fun containsNoVars() = statements.all { it !is VarDecl }
|
||||
fun containsNoCodeNorVars() = !containsCodeOrVars()
|
||||
|
||||
fun remove(stmt: Statement) {
|
||||
@ -195,6 +215,22 @@ interface INameScope {
|
||||
else
|
||||
null
|
||||
}
|
||||
|
||||
fun previousSibling(stmt: Statement): Statement? {
|
||||
val previousIdx = statements.indexOfFirst { it===stmt } - 1
|
||||
return if(previousIdx>=0)
|
||||
statements[previousIdx]
|
||||
else
|
||||
null
|
||||
}
|
||||
|
||||
fun indexOfChild(stmt: Statement): Int {
|
||||
val idx = statements.indexOfFirst { it===stmt }
|
||||
if(idx>=0)
|
||||
return idx
|
||||
else
|
||||
throw FatalAstException("attempt to find a non-child")
|
||||
}
|
||||
}
|
||||
|
||||
interface IAssignable {
|
||||
@ -228,11 +264,11 @@ class Program(val name: String, val modules: MutableList<Module>): Node {
|
||||
override val position: Position = Position.DUMMY
|
||||
override var parent: Node
|
||||
get() = throw FatalAstException("program has no parent")
|
||||
set(value) = throw FatalAstException("can't set parent of program")
|
||||
set(_) = throw FatalAstException("can't set parent of program")
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
modules.forEach {
|
||||
it.linkParents(this)
|
||||
it.linkParents(namespace)
|
||||
}
|
||||
}
|
||||
|
||||
@ -255,7 +291,10 @@ class Module(override val name: String,
|
||||
val importedBy = mutableListOf<Module>()
|
||||
val imports = mutableSetOf<Module>()
|
||||
|
||||
var loadAddress: Int = 0 // can be set with the %address directive
|
||||
val loadAddress: Int by lazy {
|
||||
val address = (statements.singleOrNull { it is Directive && it.directive == "%address" } as? Directive)?.args?.single()?.int ?: 0
|
||||
address
|
||||
}
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -280,7 +319,7 @@ class Module(override val name: String,
|
||||
class GlobalNamespace(val modules: List<Module>): Node, INameScope {
|
||||
override val name = "<<<global>>>"
|
||||
override val position = Position("<<<global>>>", 0, 0, 0)
|
||||
override val statements = mutableListOf<Statement>()
|
||||
override val statements = mutableListOf<Statement>() // not used
|
||||
override var parent: Node = ParentSentinel
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
@ -312,6 +351,14 @@ class GlobalNamespace(val modules: List<Module>): Node, INameScope {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// special case: the do....until statement can also look INSIDE the anonymous scope
|
||||
if(localContext.parent.parent is UntilLoop) {
|
||||
val symbolFromInnerScope = (localContext.parent.parent as UntilLoop).body.lookup(scopedName, localContext)
|
||||
if(symbolFromInnerScope!=null)
|
||||
return symbolFromInnerScope
|
||||
}
|
||||
|
||||
// lookup something from the module.
|
||||
return when (val stmt = localContext.definingModule().lookup(scopedName, localContext)) {
|
||||
is Label, is VarDecl, is Block, is Subroutine, is StructDecl -> stmt
|
||||
|
@ -254,8 +254,8 @@ private fun prog8Parser.Asmsub_declContext.toAst(): AsmsubDecl {
|
||||
val clobbers = asmsub_clobbers()?.clobber()?.toAst() ?: emptySet()
|
||||
val normalParameters = params.map { SubroutineParameter(it.name, it.type, it.position) }
|
||||
val normalReturntypes = returns.map { it.type }
|
||||
val paramRegisters = params.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag, 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,13 +263,11 @@ 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>
|
||||
@ -288,7 +286,7 @@ 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>
|
||||
@ -342,14 +340,11 @@ private fun prog8Parser.LabeldefContext.toAst(): Statement =
|
||||
Label(children[0].text, toPosition())
|
||||
|
||||
private fun prog8Parser.SubroutineContext.toAst() : Subroutine {
|
||||
// non-asm subroutine
|
||||
val returntypes = sub_return_part()?.toAst() ?: emptyList()
|
||||
return Subroutine(identifier().text,
|
||||
sub_params()?.toAst() ?: emptyList(),
|
||||
sub_return_part()?.toAst() ?: emptyList(),
|
||||
emptyList(),
|
||||
emptyList(),
|
||||
emptySet(),
|
||||
null,
|
||||
false,
|
||||
returntypes,
|
||||
statement_block()?.toAst() ?: mutableListOf(),
|
||||
toPosition())
|
||||
}
|
||||
@ -470,7 +465,6 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
|
||||
litval.stringliteral()!=null -> litval.stringliteral().toAst()
|
||||
litval.charliteral()!=null -> {
|
||||
try {
|
||||
val cc=litval.charliteral()
|
||||
NumericLiteralValue(DataType.UBYTE, CompilationTarget.instance.encodeString(
|
||||
unescape(litval.charliteral().SINGLECHAR().text, litval.toPosition()),
|
||||
litval.charliteral().ALT_STRING_ENCODING()!=null)[0], litval.toPosition())
|
||||
@ -647,7 +641,20 @@ private fun prog8Parser.VardeclContext.toAst(): VarDecl {
|
||||
)
|
||||
}
|
||||
|
||||
internal fun escape(str: String) = str.replace("\t", "\\t").replace("\n", "\\n").replace("\r", "\\r")
|
||||
internal fun escape(str: String): String {
|
||||
val es = str.map {
|
||||
when(it) {
|
||||
'\t' -> "\\t"
|
||||
'\n' -> "\\n"
|
||||
'\r' -> "\\r"
|
||||
'"' -> "\\\""
|
||||
in '\u8000'..'\u80ff' -> "\\x" + (it.toInt() - 0x8000).toString(16).padStart(2, '0')
|
||||
in '\u0000'..'\u00ff' -> it.toString()
|
||||
else -> "\\u" + it.toInt().toString(16).padStart(4, '0')
|
||||
}
|
||||
}
|
||||
return es.joinToString("")
|
||||
}
|
||||
|
||||
internal fun unescape(str: String, position: Position): String {
|
||||
val result = mutableListOf<Char>()
|
||||
@ -661,9 +668,15 @@ internal fun unescape(str: String, position: Position): String {
|
||||
'n' -> '\n'
|
||||
'r' -> '\r'
|
||||
'"' -> '"'
|
||||
'\'' -> '\''
|
||||
'u' -> {
|
||||
"${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}".toInt(16).toChar()
|
||||
}
|
||||
'x' -> {
|
||||
// special hack 0x8000..0x80ff will be outputted verbatim without encoding
|
||||
val hex = ("" + iter.nextChar() + iter.nextChar()).toInt(16)
|
||||
(0x8000 + hex).toChar()
|
||||
}
|
||||
else -> throw SyntaxError("invalid escape char in string: \\$ec", position)
|
||||
})
|
||||
} else {
|
||||
|
@ -35,20 +35,25 @@ 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(this) {
|
||||
in ByteDatatypes -> false
|
||||
in WordDatatypes -> other in ByteDatatypes
|
||||
when {
|
||||
this == other -> false
|
||||
this in ByteDatatypes -> false
|
||||
this in WordDatatypes -> other in ByteDatatypes
|
||||
this==STR && other==UWORD || this==UWORD && other==STR -> false
|
||||
else -> true
|
||||
}
|
||||
|
||||
infix fun equalsSize(other: DataType) =
|
||||
when(this) {
|
||||
in ByteDatatypes -> other in ByteDatatypes
|
||||
in WordDatatypes -> other in WordDatatypes
|
||||
when {
|
||||
this == other -> true
|
||||
this in ByteDatatypes -> other in ByteDatatypes
|
||||
this in WordDatatypes -> other in WordDatatypes
|
||||
this==STR && other==UWORD || this==UWORD && other==STR -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
@ -75,7 +80,9 @@ enum class RegisterOrPair {
|
||||
Y,
|
||||
AX,
|
||||
AY,
|
||||
XY;
|
||||
XY,
|
||||
FAC1,
|
||||
FAC2;
|
||||
|
||||
companion object {
|
||||
val names by lazy { values().map { it.toString()} }
|
||||
|
@ -3,6 +3,7 @@ package prog8.ast.base
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.processing.*
|
||||
import prog8.ast.statements.Directive
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.BeforeAsmGenerationAstChanger
|
||||
|
||||
@ -18,8 +19,8 @@ internal fun Program.processAstBeforeAsmGeneration(errors: ErrorReporter) {
|
||||
fixer.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.reorderStatements() {
|
||||
val reorder = StatementReorderer(this)
|
||||
internal fun Program.reorderStatements(errors: ErrorReporter) {
|
||||
val reorder = StatementReorderer(this, errors)
|
||||
reorder.visit(this)
|
||||
reorder.applyModifications()
|
||||
}
|
||||
@ -41,12 +42,6 @@ internal fun Module.checkImportedValid() {
|
||||
imr.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.checkRecursion(errors: ErrorReporter) {
|
||||
val checker = AstRecursionChecker(namespace, errors)
|
||||
checker.visit(this)
|
||||
checker.processMessages(name)
|
||||
}
|
||||
|
||||
internal fun Program.checkIdentifiers(errors: ErrorReporter) {
|
||||
|
||||
val checker2 = AstIdentifiersChecker(this, errors)
|
||||
@ -56,6 +51,9 @@ internal fun Program.checkIdentifiers(errors: ErrorReporter) {
|
||||
val transforms = AstVariousTransforms(this)
|
||||
transforms.visit(this)
|
||||
transforms.applyModifications()
|
||||
val lit2decl = LiteralsToAutoVars(this)
|
||||
lit2decl.visit(this)
|
||||
lit2decl.applyModifications()
|
||||
}
|
||||
|
||||
if (modules.map { it.name }.toSet().size != modules.size) {
|
||||
@ -68,3 +66,37 @@ internal fun Program.variousCleanups() {
|
||||
process.visit(this)
|
||||
process.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.moveMainAndStartToFirst() {
|
||||
// the module containing the program entrypoint is moved to the first in the sequence.
|
||||
// the "main" block containing the entrypoint is moved to the top in there,
|
||||
// and finally the entrypoint subroutine "start" itself is moved to the top in that block.
|
||||
|
||||
val directives = modules[0].statements.filterIsInstance<Directive>()
|
||||
val start = this.entrypoint()
|
||||
if(start!=null) {
|
||||
val mod = start.definingModule()
|
||||
val block = start.definingBlock()
|
||||
if(!modules.remove(mod))
|
||||
throw FatalAstException("module wrong")
|
||||
modules.add(0, mod)
|
||||
mod.remove(block)
|
||||
var afterDirective = mod.statements.indexOfFirst { it !is Directive }
|
||||
if(afterDirective<0)
|
||||
mod.statements.add(block)
|
||||
else
|
||||
mod.statements.add(afterDirective, block)
|
||||
block.remove(start)
|
||||
afterDirective = block.statements.indexOfFirst { it !is Directive }
|
||||
if(afterDirective<0)
|
||||
block.statements.add(start)
|
||||
else
|
||||
block.statements.add(afterDirective, start)
|
||||
|
||||
// overwrite the directives in the module containing the entrypoint
|
||||
for(directive in directives) {
|
||||
modules[0].statements.removeAll { it is Directive && it.directive == directive.directive }
|
||||
modules[0].statements.add(0, directive)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,13 +17,13 @@ import kotlin.math.abs
|
||||
|
||||
val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "==", "!=")
|
||||
val comparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
|
||||
|
||||
val augmentAssignmentOperators = setOf("+", "-", "/", "*", "**", "&", "|", "^", "<<", ">>", "%", "and", "or", "xor")
|
||||
|
||||
sealed class Expression: Node {
|
||||
abstract fun constValue(program: Program): NumericLiteralValue?
|
||||
abstract fun accept(visitor: IAstVisitor)
|
||||
abstract fun accept(visitor: AstWalker, parent: Node)
|
||||
abstract fun referencesIdentifiers(vararg name: String): Boolean
|
||||
abstract fun referencesIdentifier(vararg scopedName: String): Boolean
|
||||
abstract fun inferType(program: Program): InferredTypes.InferredType
|
||||
|
||||
infix fun isSameAs(assigntarget: AssignTarget) = assigntarget.isSameAs(this)
|
||||
@ -41,8 +41,8 @@ sealed class Expression: Node {
|
||||
&& other.left isSameAs left
|
||||
&& other.right isSameAs right)
|
||||
is ArrayIndexedExpression -> {
|
||||
(other is ArrayIndexedExpression && other.identifier.nameInSource == identifier.nameInSource
|
||||
&& other.arrayspec.index isSameAs arrayspec.index)
|
||||
(other is ArrayIndexedExpression && other.arrayvar.nameInSource == arrayvar.nameInSource
|
||||
&& other.indexer isSameAs indexer)
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
(other is DirectMemoryRead && other.addressExpression isSameAs addressExpression)
|
||||
@ -85,7 +85,7 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name)
|
||||
override fun referencesIdentifier(vararg scopedName: String) = expression.referencesIdentifier(*scopedName)
|
||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||
val inferred = expression.inferType(program)
|
||||
return when(operator) {
|
||||
@ -142,7 +142,7 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String) = left.referencesIdentifiers(*name) || right.referencesIdentifiers(*name)
|
||||
override fun referencesIdentifier(vararg scopedName: String) = left.referencesIdentifier(*scopedName) || right.referencesIdentifier(*scopedName)
|
||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||
val leftDt = left.inferType(program)
|
||||
val rightDt = right.inferType(program)
|
||||
@ -232,20 +232,19 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
|
||||
}
|
||||
}
|
||||
|
||||
class ArrayIndexedExpression(var identifier: IdentifierReference,
|
||||
val arrayspec: ArrayIndex,
|
||||
class ArrayIndexedExpression(var arrayvar: IdentifierReference,
|
||||
val indexer: ArrayIndex,
|
||||
override val position: Position) : Expression(), IAssignable {
|
||||
override lateinit var parent: Node
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
identifier.linkParents(this)
|
||||
arrayspec.linkParents(this)
|
||||
arrayvar.linkParents(this)
|
||||
indexer.linkParents(this)
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
when {
|
||||
node===identifier -> identifier = replacement as IdentifierReference
|
||||
node===arrayspec.index -> arrayspec.index = replacement as Expression
|
||||
node===arrayvar -> arrayvar = replacement as IdentifierReference
|
||||
else -> throw FatalAstException("invalid replace")
|
||||
}
|
||||
replacement.parent = this
|
||||
@ -255,10 +254,10 @@ class ArrayIndexedExpression(var identifier: IdentifierReference,
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String) = identifier.referencesIdentifiers(*name)
|
||||
override fun referencesIdentifier(vararg scopedName: String) = arrayvar.referencesIdentifier(*scopedName)
|
||||
|
||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||
val target = identifier.targetStatement(program.namespace)
|
||||
val target = arrayvar.targetStatement(program.namespace)
|
||||
if (target is VarDecl) {
|
||||
return when (target.datatype) {
|
||||
DataType.STR -> InferredTypes.knownFor(DataType.UBYTE)
|
||||
@ -270,7 +269,7 @@ class ArrayIndexedExpression(var identifier: IdentifierReference,
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "ArrayIndexed(ident=$identifier, arraysize=$arrayspec; pos=$position)"
|
||||
return "ArrayIndexed(ident=$arrayvar, arraysize=$indexer; pos=$position)"
|
||||
}
|
||||
}
|
||||
|
||||
@ -291,7 +290,7 @@ class TypecastExpression(var expression: Expression, var type: DataType, val imp
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name)
|
||||
override fun referencesIdentifier(vararg scopedName: String) = expression.referencesIdentifier(*scopedName)
|
||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
|
||||
override fun constValue(program: Program): NumericLiteralValue? {
|
||||
val cv = expression.constValue(program) ?: return null
|
||||
@ -322,7 +321,7 @@ data class AddressOf(var identifier: IdentifierReference, override val position:
|
||||
}
|
||||
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun referencesIdentifiers(vararg name: String) = false
|
||||
override fun referencesIdentifier(vararg scopedName: String) = false
|
||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UWORD)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
@ -345,7 +344,7 @@ class DirectMemoryRead(var addressExpression: Expression, override val position:
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String) = false
|
||||
override fun referencesIdentifier(vararg scopedName: String) = false
|
||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UBYTE)
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
|
||||
@ -398,7 +397,7 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
|
||||
throw FatalAstException("can't replace here")
|
||||
}
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String) = false
|
||||
override fun referencesIdentifier(vararg scopedName: String) = false
|
||||
override fun constValue(program: Program) = this
|
||||
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
@ -498,7 +497,7 @@ class StringLiteralValue(val value: String,
|
||||
throw FatalAstException("can't replace here")
|
||||
}
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String) = false
|
||||
override fun referencesIdentifier(vararg scopedName: String) = false
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
@ -533,7 +532,7 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String) = value.any { it.referencesIdentifiers(*name) }
|
||||
override fun referencesIdentifier(vararg scopedName: String) = value.any { it.referencesIdentifier(*scopedName) }
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
@ -569,10 +568,17 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
|
||||
val dts = datatypesInArray.map { it.typeOrElse(DataType.STRUCT) }
|
||||
return when {
|
||||
DataType.FLOAT in dts -> InferredTypes.InferredType.known(DataType.ARRAY_F)
|
||||
DataType.STR in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UW)
|
||||
DataType.WORD in dts -> InferredTypes.InferredType.known(DataType.ARRAY_W)
|
||||
DataType.UWORD in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UW)
|
||||
DataType.BYTE in dts -> InferredTypes.InferredType.known(DataType.ARRAY_B)
|
||||
DataType.UBYTE in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UB)
|
||||
DataType.ARRAY_UW in dts ||
|
||||
DataType.ARRAY_W in dts ||
|
||||
DataType.ARRAY_UB in dts ||
|
||||
DataType.ARRAY_B in dts ||
|
||||
DataType.ARRAY_F in dts ||
|
||||
DataType.STRUCT in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UW)
|
||||
else -> InferredTypes.InferredType.unknown()
|
||||
}
|
||||
}
|
||||
@ -631,7 +637,7 @@ class RangeExpr(var from: Expression,
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String): Boolean = from.referencesIdentifiers(*name) || to.referencesIdentifiers(*name)
|
||||
override fun referencesIdentifier(vararg scopedName: String): Boolean = from.referencesIdentifier(*scopedName) || to.referencesIdentifier(*scopedName)
|
||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||
val fromDt=from.inferType(program)
|
||||
val toDt=to.inferType(program)
|
||||
@ -744,8 +750,8 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String): Boolean =
|
||||
nameInSource.size==name.size && nameInSource.toTypedArray().contentEquals(name)
|
||||
override fun referencesIdentifier(vararg scopedName: String): Boolean =
|
||||
nameInSource.size==scopedName.size && nameInSource.toTypedArray().contentEquals(scopedName)
|
||||
|
||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||
return when (val targetStmt = targetStatement(program.namespace)) {
|
||||
@ -767,6 +773,21 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
|
||||
else -> throw FatalAstException("requires a reference value")
|
||||
}
|
||||
}
|
||||
|
||||
fun firstStructVarName(namespace: INameScope): String? {
|
||||
// take the name of the first struct member of the structvariable instead
|
||||
// if it's just a regular variable, return null.
|
||||
val struct = memberOfStruct(namespace) ?: return null
|
||||
val decl = targetVarDecl(namespace)!!
|
||||
if(decl.datatype!=DataType.STRUCT)
|
||||
return null
|
||||
|
||||
val firstStructMember = struct.nameOfFirstMember()
|
||||
// find the flattened var that belongs to this first struct member
|
||||
val firstVarName = listOf(decl.name, firstStructMember)
|
||||
val firstVar = definingScope().lookup(firstVarName, this) as VarDecl
|
||||
return firstVar.name
|
||||
}
|
||||
}
|
||||
|
||||
class FunctionCall(override var target: IdentifierReference,
|
||||
@ -803,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)
|
||||
}
|
||||
|
||||
@ -833,7 +854,7 @@ class FunctionCall(override var target: IdentifierReference,
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String): Boolean = target.referencesIdentifiers(*name) || args.any{it.referencesIdentifiers(*name)}
|
||||
override fun referencesIdentifier(vararg scopedName: String): Boolean = target.referencesIdentifier(*scopedName) || args.any{it.referencesIdentifier(*scopedName)}
|
||||
|
||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||
val constVal = constValue(program ,false)
|
||||
@ -853,6 +874,12 @@ class FunctionCall(override var target: IdentifierReference,
|
||||
return InferredTypes.void() // no return value
|
||||
if(stmt.returntypes.size==1)
|
||||
return InferredTypes.knownFor(stmt.returntypes[0])
|
||||
|
||||
// multiple return values. Can occur for asmsub routines. If there is exactly one register return value, take that.
|
||||
val numRegisterReturns = stmt.asmReturnvaluesRegisters.count { it.registerOrPair!=null }
|
||||
if(numRegisterReturns==1)
|
||||
return InferredTypes.InferredType.known(DataType.UBYTE)
|
||||
|
||||
return InferredTypes.unknown() // has multiple return types... so not a single resulting datatype possible
|
||||
}
|
||||
else -> return InferredTypes.unknown()
|
||||
|
@ -35,6 +35,13 @@ object InferredTypes {
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = Objects.hash(isVoid, datatype)
|
||||
|
||||
infix fun isAssignableTo(targetDt: InferredType): Boolean =
|
||||
isKnown && targetDt.isKnown && (datatype!! isAssignableTo targetDt.datatype!!)
|
||||
infix fun isAssignableTo(targetDt: DataType): Boolean =
|
||||
isKnown && (datatype!! isAssignableTo targetDt)
|
||||
infix fun isNotAssignableTo(targetDt: InferredType): Boolean = !this.isAssignableTo(targetDt)
|
||||
infix fun isNotAssignableTo(targetDt: DataType): Boolean = !this.isAssignableTo(targetDt)
|
||||
}
|
||||
|
||||
private val unknownInstance = InferredType.unknown()
|
||||
|
@ -120,11 +120,6 @@ internal class AstChecker(private val program: Program,
|
||||
if(loopvar==null || loopvar.type== VarDeclType.CONST) {
|
||||
errors.err("for loop requires a variable to loop with", forLoop.position)
|
||||
} else {
|
||||
|
||||
fun checkLoopRangeValues() {
|
||||
|
||||
}
|
||||
|
||||
when (loopvar.datatype) {
|
||||
DataType.UBYTE -> {
|
||||
if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.ARRAY_UB && iterableDt != DataType.STR)
|
||||
@ -255,19 +250,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")
|
||||
}
|
||||
}
|
||||
|
||||
@ -293,6 +288,7 @@ 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 */ }
|
||||
null ->
|
||||
if(p.statusflag!=null)
|
||||
statusflagCounts[p.statusflag] = statusflagCounts.getValue(p.statusflag) + 1
|
||||
@ -334,8 +330,8 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(untilLoop: UntilLoop) {
|
||||
if(untilLoop.untilCondition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
|
||||
errors.err("condition value should be an integer type", untilLoop.untilCondition.position)
|
||||
if(untilLoop.condition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
|
||||
errors.err("condition value should be an integer type", untilLoop.condition.position)
|
||||
super.visit(untilLoop)
|
||||
}
|
||||
|
||||
@ -346,17 +342,15 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(assignment: Assignment) {
|
||||
// assigning from a functioncall COULD return multiple values (from an asm subroutine)
|
||||
if(assignment.value is FunctionCall) {
|
||||
val stmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace)
|
||||
if (stmt is Subroutine && stmt.isAsmSubroutine) {
|
||||
if(stmt.returntypes.size>1)
|
||||
errors.err("It's not possible to store the multiple results of this asmsub call; you should use a small block of custom inline assembly for this.", assignment.value.position)
|
||||
else {
|
||||
val idt = assignment.target.inferType(program, assignment)
|
||||
if(!idt.isKnown || stmt.returntypes.single()!=idt.typeOrElse(DataType.BYTE)) {
|
||||
errors.err("return type mismatch", assignment.value.position)
|
||||
}
|
||||
if (stmt is Subroutine) {
|
||||
val idt = assignment.target.inferType(program)
|
||||
if(!idt.isKnown) {
|
||||
errors.err("return type mismatch", assignment.value.position)
|
||||
}
|
||||
if(stmt.returntypes.size <= 1 && stmt.returntypes.single() isNotAssignableTo idt.typeOrElse(DataType.BYTE)) {
|
||||
errors.err("return type mismatch", assignment.value.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -376,17 +370,21 @@ internal class AstChecker(private val program: Program,
|
||||
if (sourceVar?.struct != null) {
|
||||
if (sourceVar.struct !== targetVar.struct)
|
||||
errors.err("assignment of different struct types", assignment.position)
|
||||
} else if(sourceVar?.isArray==true) {
|
||||
if((sourceVar.value as ArrayLiteralValue).value.size != targetVar.struct?.numberOfElements)
|
||||
errors.err("number of elements doesn't match struct definition", sourceVar.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val targetDt = assignment.target.inferType(program, assignment)
|
||||
if(assignment.value.inferType(program) != targetDt) {
|
||||
val targetDt = assignment.target.inferType(program)
|
||||
val valueDt = assignment.value.inferType(program)
|
||||
if(valueDt.isKnown && !(valueDt isAssignableTo targetDt)) {
|
||||
if(targetDt.typeOrElse(DataType.STRUCT) in IterableDatatypes)
|
||||
errors.err("cannot assign value to string or array", assignment.value.position)
|
||||
else
|
||||
else if(!(valueDt.istype(DataType.STR) && targetDt.istype(DataType.UWORD)))
|
||||
errors.err("value's type doesn't match target", assignment.value.position)
|
||||
}
|
||||
|
||||
@ -436,19 +434,15 @@ 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) {
|
||||
checkValueTypeAndRange(targetDatatype.typeOrElse(DataType.BYTE), constVal)
|
||||
} else {
|
||||
val sourceDatatype = assignment.value.inferType(program)
|
||||
if (!sourceDatatype.isKnown) {
|
||||
if (assignment.value is FunctionCall) {
|
||||
val targetStmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace)
|
||||
if (targetStmt != null)
|
||||
errors.err("function call doesn't return a suitable value to use in assignment", assignment.value.position)
|
||||
} else
|
||||
if (sourceDatatype.isUnknown) {
|
||||
if (assignment.value !is FunctionCall)
|
||||
errors.err("assignment value is invalid or has no proper datatype", assignment.value.position)
|
||||
} else {
|
||||
checkAssignmentCompatible(targetDatatype.typeOrElse(DataType.BYTE), assignTarget,
|
||||
@ -461,14 +455,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.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)
|
||||
}
|
||||
|
||||
@ -476,7 +468,7 @@ internal class AstChecker(private val program: Program,
|
||||
fun err(msg: String, position: Position?=null) = errors.err(msg, position ?: decl.position)
|
||||
|
||||
// the initializer value can't refer to the variable itself (recursive definition)
|
||||
if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true)
|
||||
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexVar?.referencesIdentifier(decl.name) == true)
|
||||
err("recursive var declaration")
|
||||
|
||||
// CONST can only occur on simple types (byte, word, float)
|
||||
@ -599,8 +591,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)
|
||||
@ -609,26 +601,39 @@ 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 -> {}
|
||||
}
|
||||
}
|
||||
|
||||
// string assignment is not supported in a vard
|
||||
if(decl.datatype==DataType.STR) {
|
||||
if(decl.value==null)
|
||||
err("string var must be initialized with a string literal")
|
||||
else if (decl.type==VarDeclType.VAR && decl.value !is StringLiteralValue)
|
||||
err("string var can only be initialized with a string literal")
|
||||
}
|
||||
|
||||
super.visit(decl)
|
||||
}
|
||||
|
||||
@ -709,7 +714,7 @@ internal class AstChecker(private val program: Program,
|
||||
err("this directive may only occur in a block or at module level")
|
||||
if(directive.args.isEmpty())
|
||||
err("missing option directive argument(s)")
|
||||
else if(directive.args.map{it.name in setOf("enable_floats", "force_output")}.any { !it })
|
||||
else if(directive.args.map{it.name in setOf("enable_floats", "force_output", "no_sysinit")}.any { !it })
|
||||
err("invalid option directive argument(s)")
|
||||
}
|
||||
"%target" -> {
|
||||
@ -742,9 +747,20 @@ internal class AstChecker(private val program: Program,
|
||||
checkValueTypeAndRangeArray(array.type.typeOrElse(DataType.STRUCT), null, arrayspec, array)
|
||||
}
|
||||
|
||||
if(!array.value.all { it is NumericLiteralValue || it is AddressOf || it is StringLiteralValue }) {
|
||||
// TODO for now, array literals have to consist of all compile time constant values...
|
||||
errors.err("array literal doesn't consist of only compile time constant values", array.position)
|
||||
fun isPassByReferenceElement(e: Expression): Boolean {
|
||||
if(e is IdentifierReference) {
|
||||
val decl = e.targetVarDecl(program.namespace)!!
|
||||
return decl.datatype in PassByReferenceDatatypes
|
||||
}
|
||||
return e is StringLiteralValue
|
||||
}
|
||||
|
||||
if(array.parent is VarDecl) {
|
||||
if (!array.value.all { it is NumericLiteralValue || it is AddressOf || isPassByReferenceElement(it) })
|
||||
errors.err("array literal for variable initialization contains invalid types", array.position)
|
||||
} else if(array.parent is ForLoop) {
|
||||
if (!array.value.all { it.constValue(program) != null })
|
||||
errors.err("array literal for iteration must contain constants. Try using a separate array variable instead?", array.position)
|
||||
}
|
||||
|
||||
super.visit(array)
|
||||
@ -756,7 +772,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)
|
||||
@ -774,6 +794,8 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(expr: BinaryExpression) {
|
||||
super.visit(expr)
|
||||
|
||||
val leftIDt = expr.left.inferType(program)
|
||||
val rightIDt = expr.right.inferType(program)
|
||||
if(!leftIDt.isKnown || !rightIDt.isKnown)
|
||||
@ -813,13 +835,12 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
if(leftDt !in NumericDatatypes)
|
||||
errors.err("left operand is not numeric", expr.left.position)
|
||||
if(rightDt!in NumericDatatypes)
|
||||
errors.err("right operand is not numeric", expr.right.position)
|
||||
if(leftDt !in NumericDatatypes && leftDt != DataType.STR)
|
||||
errors.err("left operand is not numeric or str", expr.left.position)
|
||||
if(rightDt!in NumericDatatypes && rightDt != DataType.STR)
|
||||
errors.err("right operand is not numeric or str", expr.right.position)
|
||||
if(leftDt!=rightDt)
|
||||
errors.err("left and right operands aren't the same type", expr.left.position)
|
||||
super.visit(expr)
|
||||
}
|
||||
|
||||
override fun visit(typecast: TypecastExpression) {
|
||||
@ -879,7 +900,29 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
val error = VerifyFunctionArgTypes.checkTypes(functionCall, functionCall.definingScope(), program)
|
||||
if(error!=null)
|
||||
errors.err(error, functionCall.args.first().position)
|
||||
errors.err(error, functionCall.position)
|
||||
|
||||
// check the functions that return multiple returnvalues.
|
||||
val stmt = functionCall.target.targetStatement(program.namespace)
|
||||
if (stmt is Subroutine) {
|
||||
if (stmt.returntypes.size > 1) {
|
||||
// Currently, it's only possible to handle ONE (or zero) return values from a subroutine.
|
||||
// asmsub routines can have multiple return values, for instance in 2 different registers.
|
||||
// It's not (yet) possible to handle these multiple return values because assignments
|
||||
// are only to a single unique target at the same time.
|
||||
// EXCEPTION:
|
||||
// if the asmsub returns multiple values and one of them is via a status register bit,
|
||||
// it *is* possible to handle them by just actually assigning the register value and
|
||||
// dealing with the status bit as just being that, the status bit after the call.
|
||||
val (returnRegisters, returnStatusflags) = stmt.asmReturnvaluesRegisters.partition { rr -> rr.registerOrPair != null }
|
||||
if (returnRegisters.isEmpty() || returnRegisters.size == 1) {
|
||||
if (returnStatusflags.any())
|
||||
errors.warn("this asmsub also has one or more return 'values' in one of the status flags", functionCall.position)
|
||||
} else {
|
||||
errors.err("It's not possible to store the multiple result values of this asmsub call; you should use a small block of custom inline assembly for this.", functionCall.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.visit(functionCall)
|
||||
}
|
||||
@ -948,8 +991,8 @@ internal class AstChecker(private val program: Program,
|
||||
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
|
||||
}
|
||||
@ -972,7 +1015,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
} else if(postIncrDecr.target.arrayindexed != null) {
|
||||
val target = postIncrDecr.target.arrayindexed?.identifier?.targetStatement(program.namespace)
|
||||
val target = postIncrDecr.target.arrayindexed?.arrayvar?.targetStatement(program.namespace)
|
||||
if(target==null) {
|
||||
errors.err("undefined symbol", postIncrDecr.position)
|
||||
}
|
||||
@ -987,32 +1030,38 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
|
||||
val target = arrayIndexedExpression.identifier.targetStatement(program.namespace)
|
||||
val target = arrayIndexedExpression.arrayvar.targetStatement(program.namespace)
|
||||
if(target is VarDecl) {
|
||||
if(target.datatype !in IterableDatatypes)
|
||||
errors.err("indexing requires an iterable variable", arrayIndexedExpression.position)
|
||||
val arraysize = target.arraysize?.constIndex()
|
||||
if(arraysize!=null) {
|
||||
// check out of bounds
|
||||
val index = (arrayIndexedExpression.arrayspec.index as? NumericLiteralValue)?.number?.toInt()
|
||||
val index = arrayIndexedExpression.indexer.constIndex()
|
||||
if(index!=null && (index<0 || index>=arraysize))
|
||||
errors.err("array index out of bounds", arrayIndexedExpression.arrayspec.position)
|
||||
errors.err("array index out of bounds", arrayIndexedExpression.indexer.position)
|
||||
} else if(target.datatype == DataType.STR) {
|
||||
if(target.value is StringLiteralValue) {
|
||||
// check string lengths for non-memory mapped strings
|
||||
val stringLen = (target.value as StringLiteralValue).value.length
|
||||
val index = (arrayIndexedExpression.arrayspec.index as? NumericLiteralValue)?.number?.toInt()
|
||||
val index = arrayIndexedExpression.indexer.constIndex()
|
||||
if (index != null && (index < 0 || index >= stringLen))
|
||||
errors.err("index out of bounds", arrayIndexedExpression.arrayspec.position)
|
||||
errors.err("index out of bounds", arrayIndexedExpression.indexer.position)
|
||||
}
|
||||
}
|
||||
} else
|
||||
errors.err("indexing requires a variable to act upon", arrayIndexedExpression.position)
|
||||
|
||||
// check index value 0..255
|
||||
val dtx = arrayIndexedExpression.arrayspec.index.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
if(dtx!= DataType.UBYTE && dtx!= DataType.BYTE)
|
||||
val dtxNum = arrayIndexedExpression.indexer.indexNum?.inferType(program)?.typeOrElse(DataType.STRUCT)
|
||||
if(dtxNum!=null && dtxNum != DataType.UBYTE && dtxNum != DataType.BYTE)
|
||||
errors.err("array indexing is limited to byte size 0..255", arrayIndexedExpression.position)
|
||||
val dtxVar = arrayIndexedExpression.indexer.indexVar?.inferType(program)?.typeOrElse(DataType.STRUCT)
|
||||
if(dtxVar!=null && dtxVar != DataType.UBYTE && dtxVar != DataType.BYTE)
|
||||
errors.err("array indexing is limited to byte size 0..255", arrayIndexedExpression.position)
|
||||
|
||||
if(arrayIndexedExpression.indexer.origExpression!=null)
|
||||
throw FatalAstException("array indexer should have been replaced with a temp var @ ${arrayIndexedExpression.indexer.position}")
|
||||
|
||||
super.visit(arrayIndexedExpression)
|
||||
}
|
||||
@ -1117,10 +1166,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(arraySpecSize!=null && arraySpecSize>0) {
|
||||
if(arraySpecSize<1 || arraySpecSize>256)
|
||||
return err("byte array length must be 1-256")
|
||||
val constX = arrayspec.index.constValue(program)
|
||||
if(constX?.type !in IntegerDatatypes)
|
||||
return err("array size specifier must be constant integer value")
|
||||
val expectedSize = constX!!.number.toInt()
|
||||
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
|
||||
if (arraySize != expectedSize)
|
||||
return err("initializer array size mismatch (expecting $expectedSize, got $arraySize)")
|
||||
return true
|
||||
@ -1139,10 +1185,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(arraySpecSize!=null && arraySpecSize>0) {
|
||||
if(arraySpecSize<1 || arraySpecSize>128)
|
||||
return err("word array length must be 1-128")
|
||||
val constX = arrayspec.index.constValue(program)
|
||||
if(constX?.type !in IntegerDatatypes)
|
||||
return err("array size specifier must be constant integer value")
|
||||
val expectedSize = constX!!.number.toInt()
|
||||
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
|
||||
if (arraySize != expectedSize)
|
||||
return err("initializer array size mismatch (expecting $expectedSize, got $arraySize)")
|
||||
return true
|
||||
@ -1161,10 +1204,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(arraySpecSize!=null && arraySpecSize>0) {
|
||||
if(arraySpecSize < 1 || arraySpecSize>51)
|
||||
return err("float array length must be 1-51")
|
||||
val constX = arrayspec.index.constValue(program)
|
||||
if(constX?.type !in IntegerDatatypes)
|
||||
return err("array size specifier must be constant integer value")
|
||||
val expectedSize = constX!!.number.toInt()
|
||||
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
|
||||
if (arraySize != expectedSize)
|
||||
return err("initializer array size mismatch (expecting $expectedSize, got $arraySize)")
|
||||
} else
|
||||
@ -1185,7 +1225,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
|
||||
}
|
||||
@ -1317,9 +1357,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)
|
||||
}
|
||||
|
||||
|
@ -80,6 +80,11 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
|
||||
if (existing != null && existing !== decl)
|
||||
nameError(decl.name, decl.position, existing)
|
||||
|
||||
if(decl.definingBlock().name==decl.name)
|
||||
nameError(decl.name, decl.position, decl.definingBlock())
|
||||
if(decl.definingSubroutine()?.name==decl.name)
|
||||
nameError(decl.name, decl.position, decl.definingSubroutine()!!)
|
||||
|
||||
super.visit(decl)
|
||||
}
|
||||
|
||||
@ -143,8 +148,8 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
|
||||
}
|
||||
|
||||
override fun visit(string: StringLiteralValue) {
|
||||
if (string.value.length !in 1..255)
|
||||
errors.err("string literal length must be between 1 and 255", string.position)
|
||||
if (string.value.length > 255)
|
||||
errors.err("string literal length max is 255", string.position)
|
||||
|
||||
super.visit(string)
|
||||
}
|
||||
|
@ -1,118 +0,0 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.FunctionCall
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
import prog8.ast.statements.Subroutine
|
||||
|
||||
|
||||
internal class AstRecursionChecker(private val namespace: INameScope,
|
||||
private val errors: ErrorReporter) : IAstVisitor {
|
||||
private val callGraph = DirectedGraph<INameScope>()
|
||||
|
||||
fun processMessages(modulename: String) {
|
||||
val cycle = callGraph.checkForCycle()
|
||||
if(cycle.isEmpty())
|
||||
return
|
||||
val chain = cycle.joinToString(" <-- ") { "${it.name} at ${it.position}" }
|
||||
errors.err("Program contains recursive subroutine calls, this is not supported. Recursive chain:\n (a subroutine call in) $chain", Position.DUMMY)
|
||||
}
|
||||
|
||||
override fun visit(functionCallStatement: FunctionCallStatement) {
|
||||
val scope = functionCallStatement.definingScope()
|
||||
val targetStatement = functionCallStatement.target.targetStatement(namespace)
|
||||
if(targetStatement!=null) {
|
||||
val targetScope = when (targetStatement) {
|
||||
is Subroutine -> targetStatement
|
||||
else -> targetStatement.definingScope()
|
||||
}
|
||||
callGraph.add(scope, targetScope)
|
||||
}
|
||||
super.visit(functionCallStatement)
|
||||
}
|
||||
|
||||
override fun visit(functionCall: FunctionCall) {
|
||||
val scope = functionCall.definingScope()
|
||||
val targetStatement = functionCall.target.targetStatement(namespace)
|
||||
if(targetStatement!=null) {
|
||||
val targetScope = when (targetStatement) {
|
||||
is Subroutine -> targetStatement
|
||||
else -> targetStatement.definingScope()
|
||||
}
|
||||
callGraph.add(scope, targetScope)
|
||||
}
|
||||
super.visit(functionCall)
|
||||
}
|
||||
|
||||
private class DirectedGraph<VT> {
|
||||
private val graph = mutableMapOf<VT, MutableSet<VT>>()
|
||||
private var uniqueVertices = mutableSetOf<VT>()
|
||||
val numVertices : Int
|
||||
get() = uniqueVertices.size
|
||||
|
||||
fun add(from: VT, to: VT) {
|
||||
var targets = graph[from]
|
||||
if(targets==null) {
|
||||
targets = mutableSetOf()
|
||||
graph[from] = targets
|
||||
}
|
||||
targets.add(to)
|
||||
uniqueVertices.add(from)
|
||||
uniqueVertices.add(to)
|
||||
}
|
||||
|
||||
fun print() {
|
||||
println("#vertices: $numVertices")
|
||||
graph.forEach { (from, to) ->
|
||||
println("$from CALLS:")
|
||||
to.forEach { println(" $it") }
|
||||
}
|
||||
val cycle = checkForCycle()
|
||||
if(cycle.isNotEmpty()) {
|
||||
println("CYCLIC! $cycle")
|
||||
}
|
||||
}
|
||||
|
||||
fun checkForCycle(): MutableList<VT> {
|
||||
val visited = uniqueVertices.associateWith { false }.toMutableMap()
|
||||
val recStack = uniqueVertices.associateWith { false }.toMutableMap()
|
||||
val cycle = mutableListOf<VT>()
|
||||
for(node in uniqueVertices) {
|
||||
if(isCyclicUntil(node, visited, recStack, cycle))
|
||||
return cycle
|
||||
}
|
||||
return mutableListOf()
|
||||
}
|
||||
|
||||
private fun isCyclicUntil(node: VT,
|
||||
visited: MutableMap<VT, Boolean>,
|
||||
recStack: MutableMap<VT, Boolean>,
|
||||
cycleNodes: MutableList<VT>): Boolean {
|
||||
|
||||
if(recStack[node]==true) return true
|
||||
if(visited[node]==true) return false
|
||||
|
||||
// mark current node as visited and add to recursion stack
|
||||
visited[node] = true
|
||||
recStack[node] = true
|
||||
|
||||
// recurse for all neighbours
|
||||
val neighbors = graph[node]
|
||||
if(neighbors!=null) {
|
||||
for (neighbour in neighbors) {
|
||||
if (isCyclicUntil(neighbour, visited, recStack, cycleNodes)) {
|
||||
cycleNodes.add(node)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pop node from recursion stack
|
||||
recStack[node] = false
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -47,78 +47,59 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
when {
|
||||
expr.left is StringLiteralValue ->
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr,
|
||||
processBinaryExprWithString(expr.left as StringLiteralValue, expr.right, expr),
|
||||
parent
|
||||
))
|
||||
expr.right is StringLiteralValue ->
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr,
|
||||
processBinaryExprWithString(expr.right as StringLiteralValue, expr.left, expr),
|
||||
parent
|
||||
))
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
val leftStr = expr.left as? StringLiteralValue
|
||||
val rightStr = expr.right as? StringLiteralValue
|
||||
if(expr.operator == "+") {
|
||||
val concatenatedString = concatString(expr)
|
||||
if(concatenatedString!=null)
|
||||
return listOf(IAstModification.ReplaceNode(expr, concatenatedString, parent))
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||
if(string.parent !is VarDecl) {
|
||||
// replace the literal string by a identifier reference to a new local vardecl
|
||||
val vardecl = VarDecl.createAuto(string)
|
||||
val identifier = IdentifierReference(listOf(vardecl.name), vardecl.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(string, identifier, parent),
|
||||
IAstModification.InsertFirst(vardecl, string.definingScope() as Node)
|
||||
)
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||
val vardecl = array.parent as? VarDecl
|
||||
if(vardecl!=null) {
|
||||
// adjust the datatype of the array (to an educated guess)
|
||||
val arrayDt = array.type
|
||||
if(!arrayDt.istype(vardecl.datatype)) {
|
||||
val cast = array.cast(vardecl.datatype)
|
||||
if (cast != null && cast!=array)
|
||||
return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl))
|
||||
else if(expr.operator == "*") {
|
||||
if (leftStr!=null) {
|
||||
val amount = expr.right.constValue(program)
|
||||
if(amount!=null) {
|
||||
val string = leftStr.value.repeat(amount.number.toInt())
|
||||
val strval = StringLiteralValue(string, leftStr.altEncoding, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, strval, parent))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val arrayDt = array.guessDatatype(program)
|
||||
if(arrayDt.isKnown) {
|
||||
// this array literal is part of an expression, turn it into an identifier reference
|
||||
val litval2 = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
|
||||
if(litval2!=null && litval2!=array) {
|
||||
val vardecl2 = VarDecl.createAuto(litval2)
|
||||
val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(array, identifier, parent),
|
||||
IAstModification.InsertFirst(vardecl2, array.definingScope() as Node)
|
||||
)
|
||||
else if (rightStr!=null) {
|
||||
val amount = expr.right.constValue(program)
|
||||
if(amount!=null) {
|
||||
val string = rightStr.value.repeat(amount.number.toInt())
|
||||
val strval = StringLiteralValue(string, rightStr.altEncoding, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, strval, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun processBinaryExprWithString(string: StringLiteralValue, operand: Expression, expr: BinaryExpression): Expression {
|
||||
val constvalue = operand.constValue(program)
|
||||
if(constvalue!=null) {
|
||||
if (expr.operator == "*") {
|
||||
// repeat a string a number of times
|
||||
return StringLiteralValue(string.value.repeat(constvalue.number.toInt()), string.altEncoding, expr.position)
|
||||
private fun concatString(expr: BinaryExpression): StringLiteralValue? {
|
||||
val rightStrval = expr.right as? StringLiteralValue
|
||||
val leftStrval = expr.left as? StringLiteralValue
|
||||
return when {
|
||||
expr.operator!="+" -> null
|
||||
expr.left is BinaryExpression && rightStrval!=null -> {
|
||||
val subStrVal = concatString(expr.left as BinaryExpression)
|
||||
if(subStrVal==null)
|
||||
null
|
||||
else
|
||||
StringLiteralValue("${subStrVal.value}${rightStrval.value}", subStrVal.altEncoding, rightStrval.position)
|
||||
}
|
||||
expr.right is BinaryExpression && leftStrval!=null -> {
|
||||
val subStrVal = concatString(expr.right as BinaryExpression)
|
||||
if(subStrVal==null)
|
||||
null
|
||||
else
|
||||
StringLiteralValue("${leftStrval.value}${subStrVal.value}", subStrVal.altEncoding, leftStrval.position)
|
||||
}
|
||||
leftStrval!=null && rightStrval!=null -> {
|
||||
StringLiteralValue("${leftStrval.value}${rightStrval.value}", leftStrval.altEncoding, leftStrval.position)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
if(expr.operator == "+" && operand is StringLiteralValue) {
|
||||
// concatenate two strings
|
||||
return StringLiteralValue("${string.value}${operand.value}", string.altEncoding, expr.position)
|
||||
}
|
||||
return expr
|
||||
}
|
||||
}
|
||||
|
@ -9,79 +9,60 @@ import prog8.ast.statements.*
|
||||
interface IAstModification {
|
||||
fun perform()
|
||||
|
||||
class Remove(val node: Node, val parent: Node) : IAstModification {
|
||||
class Remove(val node: Node, val parent: INameScope) : IAstModification {
|
||||
override fun perform() {
|
||||
if(parent is INameScope) {
|
||||
if (!parent.statements.remove(node) && parent !is GlobalNamespace)
|
||||
throw FatalAstException("attempt to remove non-existing node $node")
|
||||
} else {
|
||||
throw FatalAstException("parent of a remove modification is not an INameScope")
|
||||
}
|
||||
if (!parent.statements.remove(node) && parent !is GlobalNamespace)
|
||||
throw FatalAstException("attempt to remove non-existing node $node")
|
||||
}
|
||||
}
|
||||
|
||||
class SetExpression(val setter: (newExpr: Expression) -> Unit, val newExpr: Expression, val parent: Node) : IAstModification {
|
||||
class SetExpression(private val setter: (newExpr: Expression) -> Unit, private val newExpr: Expression, private val parent: Node) : IAstModification {
|
||||
override fun perform() {
|
||||
setter(newExpr)
|
||||
newExpr.linkParents(parent)
|
||||
}
|
||||
}
|
||||
|
||||
class InsertFirst(val stmt: Statement, val parent: Node) : IAstModification {
|
||||
class InsertFirst(private val stmt: Statement, private val parent: INameScope) : IAstModification {
|
||||
override fun perform() {
|
||||
if(parent is INameScope) {
|
||||
parent.statements.add(0, stmt)
|
||||
stmt.linkParents(parent)
|
||||
} else {
|
||||
throw FatalAstException("parent of an insert modification is not an INameScope")
|
||||
}
|
||||
parent.statements.add(0, stmt)
|
||||
stmt.linkParents(parent as Node)
|
||||
}
|
||||
}
|
||||
|
||||
class InsertLast(val stmt: Statement, val parent: Node) : IAstModification {
|
||||
class InsertLast(private val stmt: Statement, private val parent: INameScope) : IAstModification {
|
||||
override fun perform() {
|
||||
if(parent is INameScope) {
|
||||
parent.statements.add(stmt)
|
||||
stmt.linkParents(parent)
|
||||
} else {
|
||||
throw FatalAstException("parent of an insert modification is not an INameScope")
|
||||
}
|
||||
parent.statements.add(stmt)
|
||||
stmt.linkParents(parent as Node)
|
||||
}
|
||||
}
|
||||
|
||||
class InsertAfter(val after: Statement, val stmt: Statement, val parent: Node) : IAstModification {
|
||||
class InsertAfter(private val after: Statement, private val stmt: Statement, private val parent: INameScope) : IAstModification {
|
||||
override fun perform() {
|
||||
if(parent is INameScope) {
|
||||
val idx = parent.statements.indexOfFirst { it===after } + 1
|
||||
parent.statements.add(idx, stmt)
|
||||
stmt.linkParents(parent)
|
||||
} else {
|
||||
throw FatalAstException("parent of an insert modification is not an INameScope")
|
||||
}
|
||||
val idx = parent.statements.indexOfFirst { it===after } + 1
|
||||
parent.statements.add(idx, stmt)
|
||||
stmt.linkParents(parent as Node)
|
||||
}
|
||||
}
|
||||
|
||||
class InsertBefore(val before: Statement, val stmt: Statement, val parent: Node) : IAstModification {
|
||||
class InsertBefore(private val before: Statement, private val stmt: Statement, private val parent: INameScope) : IAstModification {
|
||||
override fun perform() {
|
||||
if(parent is INameScope) {
|
||||
val idx = parent.statements.indexOfFirst { it===before }
|
||||
parent.statements.add(idx, stmt)
|
||||
stmt.linkParents(parent)
|
||||
} else {
|
||||
throw FatalAstException("parent of an insert modification is not an INameScope")
|
||||
}
|
||||
val idx = parent.statements.indexOfFirst { it===before }
|
||||
parent.statements.add(idx, stmt)
|
||||
stmt.linkParents(parent as Node)
|
||||
}
|
||||
}
|
||||
|
||||
class ReplaceNode(val node: Node, val replacement: Node, val parent: Node) : IAstModification {
|
||||
class ReplaceNode(private val node: Node, private val replacement: Node, private val parent: Node) : IAstModification {
|
||||
override fun perform() {
|
||||
parent.replaceChildNode(node, replacement)
|
||||
replacement.linkParents(parent)
|
||||
}
|
||||
}
|
||||
|
||||
class SwapOperands(val expr: BinaryExpression): IAstModification {
|
||||
class SwapOperands(private val expr: BinaryExpression): IAstModification {
|
||||
override fun perform() {
|
||||
require(expr.operator in associativeOperators)
|
||||
val tmp = expr.left
|
||||
expr.left = expr.right
|
||||
expr.right = tmp
|
||||
@ -228,6 +209,7 @@ abstract class AstWalker {
|
||||
track(before(decl, parent), decl, parent)
|
||||
decl.value?.accept(this, decl)
|
||||
decl.arraysize?.accept(this, decl)
|
||||
decl.struct?.accept(this, decl)
|
||||
track(after(decl, parent), decl, parent)
|
||||
}
|
||||
|
||||
@ -348,7 +330,7 @@ abstract class AstWalker {
|
||||
|
||||
fun visit(untilLoop: UntilLoop, parent: Node) {
|
||||
track(before(untilLoop, parent), untilLoop, parent)
|
||||
untilLoop.untilCondition.accept(this, untilLoop)
|
||||
untilLoop.condition.accept(this, untilLoop)
|
||||
untilLoop.body.accept(this, untilLoop)
|
||||
track(after(untilLoop, parent), untilLoop, parent)
|
||||
}
|
||||
@ -361,8 +343,8 @@ abstract class AstWalker {
|
||||
|
||||
fun visit(arrayIndexedExpression: ArrayIndexedExpression, parent: Node) {
|
||||
track(before(arrayIndexedExpression, parent), arrayIndexedExpression, parent)
|
||||
arrayIndexedExpression.identifier.accept(this, arrayIndexedExpression)
|
||||
arrayIndexedExpression.arrayspec.accept(this, arrayIndexedExpression)
|
||||
arrayIndexedExpression.arrayvar.accept(this, arrayIndexedExpression)
|
||||
arrayIndexedExpression.indexer.accept(this, arrayIndexedExpression)
|
||||
track(after(arrayIndexedExpression, parent), arrayIndexedExpression, parent)
|
||||
}
|
||||
|
||||
|
@ -33,6 +33,7 @@ interface IAstVisitor {
|
||||
fun visit(decl: VarDecl) {
|
||||
decl.value?.accept(this)
|
||||
decl.arraysize?.accept(this)
|
||||
decl.struct?.accept(this)
|
||||
}
|
||||
|
||||
fun visit(subroutine: Subroutine) {
|
||||
@ -115,7 +116,7 @@ interface IAstVisitor {
|
||||
}
|
||||
|
||||
fun visit(untilLoop: UntilLoop) {
|
||||
untilLoop.untilCondition.accept(this)
|
||||
untilLoop.condition.accept(this)
|
||||
untilLoop.body.accept(this)
|
||||
}
|
||||
|
||||
@ -124,8 +125,8 @@ interface IAstVisitor {
|
||||
}
|
||||
|
||||
fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
|
||||
arrayIndexedExpression.identifier.accept(this)
|
||||
arrayIndexedExpression.arrayspec.accept(this)
|
||||
arrayIndexedExpression.arrayvar.accept(this)
|
||||
arrayIndexedExpression.indexer.accept(this)
|
||||
}
|
||||
|
||||
fun visit(assignTarget: AssignTarget) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.statements.Directive
|
||||
|
||||
@ -14,7 +15,7 @@ internal class ImportedModuleDirectiveRemover: AstWalker() {
|
||||
|
||||
override fun before(directive: Directive, parent: Node): Iterable<IAstModification> {
|
||||
if(directive.directive in moduleLevelDirectives) {
|
||||
return listOf(IAstModification.Remove(directive, parent))
|
||||
return listOf(IAstModification.Remove(directive, parent as INameScope))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
53
compiler/src/prog8/ast/processing/LiteralsToAutoVars.kt
Normal file
53
compiler/src/prog8/ast/processing/LiteralsToAutoVars.kt
Normal file
@ -0,0 +1,53 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
|
||||
|
||||
internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||
if(string.parent !is VarDecl && string.parent !is WhenChoice) {
|
||||
// replace the literal string by a identifier reference to a new local vardecl
|
||||
val vardecl = VarDecl.createAuto(string)
|
||||
val identifier = IdentifierReference(listOf(vardecl.name), vardecl.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(string, identifier, parent),
|
||||
IAstModification.InsertFirst(vardecl, string.definingScope())
|
||||
)
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||
val vardecl = array.parent as? VarDecl
|
||||
if(vardecl!=null) {
|
||||
// adjust the datatype of the array (to an educated guess)
|
||||
val arrayDt = array.type
|
||||
if(!arrayDt.istype(vardecl.datatype)) {
|
||||
val cast = array.cast(vardecl.datatype)
|
||||
if (cast != null && cast !== array)
|
||||
return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl))
|
||||
}
|
||||
} else {
|
||||
val arrayDt = array.guessDatatype(program)
|
||||
if(arrayDt.isKnown) {
|
||||
// this array literal is part of an expression, turn it into an identifier reference
|
||||
val litval2 = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
|
||||
if(litval2!=null) {
|
||||
val vardecl2 = VarDecl.createAuto(litval2)
|
||||
val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(array, identifier, parent),
|
||||
IAstModification.InsertFirst(vardecl2, array.definingScope())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
}
|
@ -4,14 +4,15 @@ 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) : AstWalker() {
|
||||
internal class StatementReorderer(val program: Program, val errors: ErrorReporter) : AstWalker() {
|
||||
// Reorders the statements in a way the compiler needs.
|
||||
// - 'main' block must be the very first statement UNLESS it has an address set.
|
||||
// - library blocks are put last.
|
||||
// - blocks are ordered by address, where blocks without address are placed last.
|
||||
// - in every scope, most directives and vardecls are moved to the top.
|
||||
// - in every block and module, most directives and vardecls are moved to the top. (not in subroutines!)
|
||||
// - the 'start' subroutine is moved to the top.
|
||||
// - (syntax desugaring) a vardecl with a non-const initializer value is split into a regular vardecl and an assignment statement.
|
||||
// - (syntax desugaring) struct value assignment is expanded into several struct member assignments.
|
||||
@ -71,6 +72,112 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||
when (val expr2 = arrayIndexedExpression.indexer.origExpression) {
|
||||
is NumericLiteralValue -> {
|
||||
arrayIndexedExpression.indexer.indexNum = expr2
|
||||
arrayIndexedExpression.indexer.origExpression = null
|
||||
return noModifications
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
arrayIndexedExpression.indexer.indexVar = expr2
|
||||
arrayIndexedExpression.indexer.origExpression = null
|
||||
return noModifications
|
||||
}
|
||||
is Expression -> {
|
||||
// replace complex indexing with a temp variable
|
||||
return getAutoIndexerVarFor(arrayIndexedExpression)
|
||||
}
|
||||
else -> return noModifications
|
||||
}
|
||||
}
|
||||
|
||||
override fun 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 repo = subroutine.asmGenInfo.usedAutoArrayIndexerForStatements
|
||||
|
||||
// TODO make this even smarter so that an indexerVar can be reused for a different following statement... requires updating the partOfStatement?
|
||||
var indexerVar = repo.firstOrNull { it.replaces isSameAs expr.indexer }
|
||||
if(indexerVar==null) {
|
||||
// add another loop index var to be used for this expression
|
||||
val indexerVarName = "$indexerVarPrefix${expr.indexer.hashCode()}"
|
||||
indexerVar = AsmGenInfo.ArrayIndexerInfo(indexerVarName, expr.indexer, statement)
|
||||
repo.add(indexerVar)
|
||||
// create the indexer var at block level scope
|
||||
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.PREFER_ZEROPAGE,
|
||||
null, indexerVarName, null, null, isArray = false, autogeneratedDontRemove = true, position = expr.position)
|
||||
modifications.add(IAstModification.InsertFirst(vardecl, subroutine))
|
||||
}
|
||||
indexerVar.used++ // keep track of how many times it it used, to avoid assigning it multiple times
|
||||
|
||||
// replace the indexer with just the variable
|
||||
// assign the indexing expression to the helper variable, but only if that hasn't been done already
|
||||
val indexerExpression = expr.indexer.origExpression!!
|
||||
val target = AssignTarget(IdentifierReference(listOf(indexerVar.name), indexerExpression.position), null, null, indexerExpression.position)
|
||||
if(indexerVar.used==1) {
|
||||
val assign = Assignment(target, indexerExpression, indexerExpression.position)
|
||||
modifications.add(IAstModification.InsertBefore(statement, assign, statement.definingScope()))
|
||||
}
|
||||
modifications.add(IAstModification.SetExpression( {
|
||||
expr.indexer.indexVar = it as IdentifierReference
|
||||
expr.indexer.indexNum = null
|
||||
expr.indexer.origExpression = null
|
||||
}, target.identifier!!.copy(), expr.indexer))
|
||||
|
||||
return modifications
|
||||
}
|
||||
|
||||
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
|
||||
val choices = whenStatement.choiceValues(program).sortedBy {
|
||||
@ -81,23 +188,57 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
val declValue = decl.value
|
||||
if(declValue!=null && decl.type== VarDeclType.VAR && decl.datatype in NumericDatatypes) {
|
||||
val declConstValue = declValue.constValue(program)
|
||||
if(declConstValue==null) {
|
||||
// move the vardecl (without value) to the scope and replace this with a regular assignment
|
||||
// Unless we're dealing with a floating point variable because that will actually make things less efficient at the moment (because floats are mostly calcualated via the stack)
|
||||
if(decl.datatype!=DataType.FLOAT) {
|
||||
decl.value = null
|
||||
decl.allowInitializeWithZero = false
|
||||
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
|
||||
val assign = Assignment(target, declValue, decl.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(decl, assign, parent),
|
||||
IAstModification.InsertFirst(decl, decl.definingScope())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
val valueType = assignment.value.inferType(program)
|
||||
val targetType = assignment.target.inferType(program, assignment)
|
||||
if(valueType.istype(DataType.STRUCT) && targetType.istype(DataType.STRUCT)) {
|
||||
val assignments = if (assignment.value is ArrayLiteralValue) {
|
||||
flattenStructAssignmentFromStructLiteral(assignment, program) // 'structvar = [ ..... ] '
|
||||
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 )) {
|
||||
assignments = if (assignment.value is ArrayLiteralValue) {
|
||||
flattenStructAssignmentFromStructLiteral(assignment) // 'structvar = [ ..... ] '
|
||||
} else {
|
||||
flattenStructAssignmentFromIdentifier(assignment, program) // 'structvar1 = structvar2'
|
||||
flattenStructAssignmentFromIdentifier(assignment) // 'structvar1 = structvar2'
|
||||
}
|
||||
if(assignments.isNotEmpty()) {
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
assignments.reversed().mapTo(modifications) { IAstModification.InsertAfter(assignment, it, parent) }
|
||||
modifications.add(IAstModification.Remove(assignment, parent))
|
||||
return modifications
|
||||
}
|
||||
|
||||
if(targetType.typeOrElse(DataType.STRUCT) in ArrayDatatypes && valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes ) {
|
||||
assignments = if (assignment.value is ArrayLiteralValue) {
|
||||
flattenArrayAssignmentFromArrayLiteral(assignment) // 'arrayvar = [ ..... ] '
|
||||
} else {
|
||||
flattenArrayAssignmentFromIdentifier(assignment) // 'arrayvar1 = arrayvar2'
|
||||
}
|
||||
}
|
||||
|
||||
if(assignments.isNotEmpty()) {
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
val scope = assignment.definingScope()
|
||||
assignments.reversed().mapTo(modifications) { IAstModification.InsertAfter(assignment, it, scope) }
|
||||
modifications.add(IAstModification.Remove(assignment, scope))
|
||||
return modifications
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@ -150,15 +291,55 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment, program: Program): List<Assignment> {
|
||||
private fun flattenArrayAssignmentFromArrayLiteral(assign: Assignment): List<Assignment> {
|
||||
val identifier = assign.target.identifier!!
|
||||
val targetVar = identifier.targetVarDecl(program.namespace)!!
|
||||
val alv = assign.value as? ArrayLiteralValue
|
||||
return flattenArrayAssign(targetVar, alv, identifier, assign.position)
|
||||
}
|
||||
|
||||
private fun flattenArrayAssignmentFromIdentifier(assign: Assignment): List<Assignment> {
|
||||
val identifier = assign.target.identifier!!
|
||||
val targetVar = identifier.targetVarDecl(program.namespace)!!
|
||||
val sourceIdent = assign.value as IdentifierReference
|
||||
val sourceVar = sourceIdent.targetVarDecl(program.namespace)!!
|
||||
if(!sourceVar.isArray) {
|
||||
errors.err("value must be an array", sourceIdent.position)
|
||||
return emptyList()
|
||||
}
|
||||
val alv = sourceVar.value as? ArrayLiteralValue
|
||||
return flattenArrayAssign(targetVar, alv, identifier, assign.position)
|
||||
}
|
||||
|
||||
private fun flattenArrayAssign(targetVar: VarDecl, alv: ArrayLiteralValue?, identifier: IdentifierReference, position: Position): List<Assignment> {
|
||||
if(targetVar.arraysize==null) {
|
||||
errors.err("array has no defined size", identifier.position)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
if(alv==null || alv.value.size != targetVar.arraysize!!.constIndex()) {
|
||||
errors.err("element count mismatch", position)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
// TODO use a pointer loop instead of individual assignments
|
||||
return alv.value.mapIndexed { index, value ->
|
||||
val idx = ArrayIndexedExpression(identifier, ArrayIndex(NumericLiteralValue(DataType.UBYTE, index, position), position), position)
|
||||
Assignment(AssignTarget(null, idx, null, position), value, value.position)
|
||||
}
|
||||
}
|
||||
|
||||
private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment): List<Assignment> {
|
||||
val identifier = structAssignment.target.identifier!!
|
||||
val identifierName = identifier.nameInSource.single()
|
||||
val targetVar = identifier.targetVarDecl(program.namespace)!!
|
||||
val struct = targetVar.struct!!
|
||||
|
||||
val slv = structAssignment.value as? ArrayLiteralValue
|
||||
if(slv==null || slv.value.size != struct.numberOfElements)
|
||||
throw FatalAstException("element count mismatch")
|
||||
if(slv==null || slv.value.size != struct.numberOfElements) {
|
||||
errors.err("element count mismatch", structAssignment.position)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
return struct.statements.zip(slv.value).map { (targetDecl, sourceValue) ->
|
||||
targetDecl as VarDecl
|
||||
@ -171,7 +352,8 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun flattenStructAssignmentFromIdentifier(structAssignment: Assignment, program: Program): List<Assignment> {
|
||||
private fun flattenStructAssignmentFromIdentifier(structAssignment: Assignment): List<Assignment> {
|
||||
// TODO use memcopy beyond a certain number of elements
|
||||
val identifier = structAssignment.target.identifier!!
|
||||
val identifierName = identifier.nameInSource.single()
|
||||
val targetVar = identifier.targetVarDecl(program.namespace)!!
|
||||
@ -179,26 +361,47 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
|
||||
when (structAssignment.value) {
|
||||
is IdentifierReference -> {
|
||||
val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program.namespace)!!
|
||||
if (sourceVar.struct == null)
|
||||
throw FatalAstException("can only assign arrays or structs to structs")
|
||||
// struct memberwise copy
|
||||
val sourceStruct = sourceVar.struct!!
|
||||
if(sourceStruct!==targetVar.struct) {
|
||||
// structs are not the same in assignment
|
||||
return listOf() // error will be printed elsewhere
|
||||
}
|
||||
return struct.statements.zip(sourceStruct.statements).map { member ->
|
||||
val targetDecl = member.first as VarDecl
|
||||
val sourceDecl = member.second as VarDecl
|
||||
if(targetDecl.name != sourceDecl.name)
|
||||
throw FatalAstException("struct member mismatch")
|
||||
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
|
||||
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
|
||||
val sourcemangled = mangledStructMemberName(sourceVar.name, sourceDecl.name)
|
||||
val sourceIdref = IdentifierReference(listOf(sourcemangled), structAssignment.position)
|
||||
val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position), sourceIdref, member.second.position)
|
||||
assign.linkParents(structAssignment)
|
||||
assign
|
||||
when {
|
||||
sourceVar.struct!=null -> {
|
||||
// struct memberwise copy
|
||||
val sourceStruct = sourceVar.struct!!
|
||||
if(sourceStruct!==targetVar.struct) {
|
||||
// structs are not the same in assignment
|
||||
return listOf() // error will be printed elsewhere
|
||||
}
|
||||
if(struct.statements.size!=sourceStruct.statements.size)
|
||||
return listOf() // error will be printed elsewhere
|
||||
return struct.statements.zip(sourceStruct.statements).map { member ->
|
||||
val targetDecl = member.first as VarDecl
|
||||
val sourceDecl = member.second as VarDecl
|
||||
if(targetDecl.name != sourceDecl.name)
|
||||
throw FatalAstException("struct member mismatch")
|
||||
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
|
||||
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
|
||||
val sourcemangled = mangledStructMemberName(sourceVar.name, sourceDecl.name)
|
||||
val sourceIdref = IdentifierReference(listOf(sourcemangled), structAssignment.position)
|
||||
val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position), sourceIdref, member.second.position)
|
||||
assign.linkParents(structAssignment)
|
||||
assign
|
||||
}
|
||||
}
|
||||
sourceVar.isArray -> {
|
||||
val array = (sourceVar.value as ArrayLiteralValue).value
|
||||
if(struct.statements.size!=array.size)
|
||||
return listOf() // error will be printed elsewhere
|
||||
return struct.statements.zip(array).map {
|
||||
val decl = it.first as VarDecl
|
||||
val mangled = mangledStructMemberName(identifierName, decl.name)
|
||||
val targetName = IdentifierReference(listOf(mangled), structAssignment.position)
|
||||
val target = AssignTarget(targetName, null, null, structAssignment.position)
|
||||
val assign = Assignment(target, it.second, structAssignment.position)
|
||||
assign.linkParents(structAssignment)
|
||||
assign
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
throw FatalAstException("can only assign arrays or structs to structs")
|
||||
}
|
||||
}
|
||||
}
|
||||
is ArrayLiteralValue -> {
|
||||
|
@ -23,6 +23,11 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
if(decl.type==VarDeclType.VAR && declValue!=null && decl.struct==null) {
|
||||
val valueDt = declValue.inferType(program)
|
||||
if(!valueDt.istype(decl.datatype)) {
|
||||
|
||||
// don't add a typecast on an array initializer value
|
||||
if(valueDt.typeOrElse(DataType.STRUCT) in IntegerDatatypes && decl.datatype in ArrayDatatypes)
|
||||
return noModifications
|
||||
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
declValue,
|
||||
TypecastExpression(declValue, decl.datatype, true, declValue.position),
|
||||
@ -55,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),
|
||||
@ -111,28 +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.
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[arg.second.index],
|
||||
AddressOf(arg.second.value as IdentifierReference, arg.second.value.position),
|
||||
call as Node)
|
||||
} else if(arg.second.value is NumericLiteralValue) {
|
||||
val cast = (arg.second.value as NumericLiteralValue).cast(requiredType)
|
||||
if(pair.second is IdentifierReference) {
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[index],
|
||||
AddressOf(pair.second as IdentifierReference, pair.second.position),
|
||||
call as Node)
|
||||
}
|
||||
} else if(pair.second is NumericLiteralValue) {
|
||||
val cast = (pair.second as NumericLiteralValue).cast(requiredType)
|
||||
if(cast.isValid)
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[arg.second.index],
|
||||
call.args[index],
|
||||
cast.valueOrZero(),
|
||||
call as Node)
|
||||
}
|
||||
@ -142,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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ internal class VariousCleanups: AstWalker() {
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
|
||||
return listOf(IAstModification.Remove(nopStatement, parent))
|
||||
return listOf(IAstModification.Remove(nopStatement, parent as INameScope))
|
||||
}
|
||||
|
||||
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
|
@ -4,10 +4,10 @@ import prog8.ast.IFunctionCall
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.FunctionCall
|
||||
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.CompilerException
|
||||
import prog8.functions.BuiltinFunctions
|
||||
|
||||
@ -26,31 +26,58 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private fun argTypeCompatible(argDt: DataType, paramDt: DataType): Boolean {
|
||||
if(argDt==paramDt)
|
||||
return true
|
||||
|
||||
// there are some exceptions that are considered compatible, such as STR <> UWORD
|
||||
if(argDt==DataType.STR && paramDt==DataType.UWORD ||
|
||||
argDt==DataType.UWORD && paramDt==DataType.STR)
|
||||
return true
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
fun checkTypes(call: IFunctionCall, scope: INameScope, program: Program): String? {
|
||||
val 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) {
|
||||
// asmsub types are not checked specifically at this time
|
||||
if(call.args.size != target.parameters.size)
|
||||
return "invalid number of arguments"
|
||||
val paramtypes = target.parameters.map { it.type }
|
||||
val mismatch = argtypes.zip(paramtypes).indexOfFirst { it.first != it.second}
|
||||
val mismatch = argtypes.zip(paramtypes).indexOfFirst { !argTypeCompatible(it.first, it.second) }
|
||||
if(mismatch>=0) {
|
||||
val actual = argtypes[mismatch].toString()
|
||||
val expected = paramtypes[mismatch].toString()
|
||||
return "argument ${mismatch + 1} type mismatch, was: $actual expected: $expected"
|
||||
}
|
||||
if(target.isAsmSubroutine) {
|
||||
if(target.asmReturnvaluesRegisters.size>1) {
|
||||
// multiple return values will NOT work inside an expression.
|
||||
// they MIGHT work in a regular assignment or just a function call statement.
|
||||
val parent = if(call is Statement) call.parent else if(call is Expression) call.parent else null
|
||||
if(call !is FunctionCallStatement && parent !is Assignment && parent !is VarDecl) {
|
||||
return "can't use subroutine call that returns multiple return values here (try moving it into a separate assignment)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (target is BuiltinFunctionStatementPlaceholder) {
|
||||
val func = BuiltinFunctions.getValue(target.name)
|
||||
if(call.args.size != func.parameters.size)
|
||||
return "invalid number of arguments"
|
||||
val paramtypes = func.parameters.map { it.possibleDatatypes }
|
||||
for (x in argtypes.zip(paramtypes).withIndex()) {
|
||||
if (x.value.first !in x.value.second) {
|
||||
val actual = x.value.first.toString()
|
||||
val expected = x.value.second.toString()
|
||||
return "argument ${x.index + 1} type mismatch, was: $actual expected: $expected"
|
||||
argtypes.zip(paramtypes).forEachIndexed { index, pair ->
|
||||
val anyCompatible = pair.second.any { argTypeCompatible(pair.first, it) }
|
||||
if (!anyCompatible) {
|
||||
val actual = pair.first.toString()
|
||||
val expected = pair.second.toString()
|
||||
return "argument ${index + 1} type mismatch, was: $actual expected: $expected"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstVisitor
|
||||
import prog8.compiler.CompilerException
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
|
||||
|
||||
sealed class Statement : Node {
|
||||
@ -28,12 +30,6 @@ sealed class Statement : Node {
|
||||
scope.add(name)
|
||||
return scope.joinToString(".")
|
||||
}
|
||||
|
||||
fun definingBlock(): Block {
|
||||
if(this is Block)
|
||||
return this
|
||||
return findParentNode<Block>(this)!!
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -48,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?,
|
||||
@ -175,6 +171,7 @@ open class VarDecl(val type: VarDeclType,
|
||||
private set
|
||||
var structHasBeenFlattened = false // set later
|
||||
private set
|
||||
var allowInitializeWithZero = true
|
||||
|
||||
// prefix for literal values that are turned into a variable on the heap
|
||||
|
||||
@ -189,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)
|
||||
@ -198,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")
|
||||
@ -214,8 +216,9 @@ open class VarDecl(val type: VarDeclType,
|
||||
DataType.UWORD -> DataType.ARRAY_UW
|
||||
DataType.WORD -> DataType.ARRAY_W
|
||||
DataType.FLOAT -> DataType.ARRAY_F
|
||||
DataType.STR -> DataType.ARRAY_UW // use memory address of the string instead
|
||||
else -> {
|
||||
datatypeErrors.add(SyntaxError("array can only contain bytes/words/floats", position))
|
||||
datatypeErrors.add(SyntaxError("array can only contain bytes/words/floats/strings(ptrs)", position))
|
||||
DataType.ARRAY_UB
|
||||
}
|
||||
}
|
||||
@ -232,7 +235,8 @@ open class VarDecl(val type: VarDeclType,
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Expression && node===value)
|
||||
// TODO the check that node===value is too strict sometimes, but leaving it out allows for bugs to creep through ... :( Perhaps check when adding the replace if there is already a replace on the same node?
|
||||
require(replacement is Expression)
|
||||
value = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
@ -244,12 +248,17 @@ open class VarDecl(val type: VarDeclType,
|
||||
return "VarDecl(name=$name, vartype=$type, datatype=$datatype, struct=$structName, value=$value, pos=$position)"
|
||||
}
|
||||
|
||||
fun zeroElementValue() = defaultZero(declaredDatatype, position)
|
||||
fun zeroElementValue(): NumericLiteralValue {
|
||||
if(allowInitializeWithZero)
|
||||
return defaultZero(declaredDatatype, position)
|
||||
else
|
||||
throw CompilerException("attempt to get zero value for vardecl that shouldn't get it")
|
||||
}
|
||||
|
||||
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,
|
||||
@ -273,34 +282,78 @@ class ParameterVarDecl(name: String, declaredDatatype: DataType, position: Posit
|
||||
: VarDecl(VarDeclType.VAR, declaredDatatype, ZeropageWish.DONTCARE, null, name, null, null, false, true, position)
|
||||
|
||||
|
||||
class ArrayIndex(var index: Expression, override val position: Position) : Node {
|
||||
class ArrayIndex(var origExpression: Expression?, // will be replaced later by either the number or the identifier
|
||||
override val position: Position) : Node {
|
||||
// for code simplicity, either indexed via a constant number or via a variable (no arbitrary expressions)
|
||||
override lateinit var parent: Node
|
||||
var indexNum: NumericLiteralValue? = origExpression as? NumericLiteralValue
|
||||
var indexVar: IdentifierReference? = origExpression as? IdentifierReference
|
||||
|
||||
init {
|
||||
if(indexNum!=null || indexVar!=null)
|
||||
origExpression = null
|
||||
}
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
index.linkParents(this)
|
||||
origExpression?.linkParents(this)
|
||||
indexNum?.linkParents(this)
|
||||
indexVar?.linkParents(this)
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Expression && node===index)
|
||||
index = replacement
|
||||
replacement.parent = this
|
||||
require(replacement is Expression)
|
||||
when {
|
||||
node===origExpression -> origExpression = replacement
|
||||
node===indexVar -> {
|
||||
when (replacement) {
|
||||
is NumericLiteralValue -> {
|
||||
indexVar = null
|
||||
indexNum = replacement
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
indexVar = replacement
|
||||
indexNum = null
|
||||
}
|
||||
else -> {
|
||||
throw FatalAstException("invalid replace")
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw FatalAstException("invalid replace")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun forArray(v: ArrayLiteralValue): ArrayIndex {
|
||||
return ArrayIndex(NumericLiteralValue.optimalNumeric(v.value.size, v.position), v.position)
|
||||
val indexnum = NumericLiteralValue.optimalNumeric(v.value.size, v.position)
|
||||
return ArrayIndex(indexnum, v.position)
|
||||
}
|
||||
}
|
||||
|
||||
fun accept(visitor: IAstVisitor) = index.accept(visitor)
|
||||
fun accept(visitor: AstWalker, parent: Node) = index.accept(visitor, this)
|
||||
|
||||
override fun toString(): String {
|
||||
return("ArrayIndex($index, pos=$position)")
|
||||
fun accept(visitor: IAstVisitor) {
|
||||
origExpression?.accept(visitor)
|
||||
indexNum?.accept(visitor)
|
||||
indexVar?.accept(visitor)
|
||||
}
|
||||
fun accept(visitor: AstWalker, parent: Node) {
|
||||
origExpression?.accept(visitor, this)
|
||||
indexNum?.accept(visitor, this)
|
||||
indexVar?.accept(visitor, this)
|
||||
}
|
||||
|
||||
fun constIndex() = (index as? NumericLiteralValue)?.number?.toInt()
|
||||
override fun toString(): String {
|
||||
return("ArrayIndex($indexNum, $indexVar, pos=$position)")
|
||||
}
|
||||
|
||||
fun constIndex() = indexNum?.number?.toInt()
|
||||
|
||||
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() {
|
||||
@ -391,8 +444,8 @@ data class AssignTarget(var identifier: IdentifierReference?,
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
when {
|
||||
node===identifier -> identifier = replacement as IdentifierReference
|
||||
node===arrayindexed -> arrayindexed = replacement as ArrayIndexedExpression
|
||||
node === identifier -> identifier = replacement as IdentifierReference
|
||||
node === arrayindexed -> arrayindexed = replacement as ArrayIndexedExpression
|
||||
else -> throw FatalAstException("invalid replace")
|
||||
}
|
||||
replacement.parent = this
|
||||
@ -401,28 +454,17 @@ data class AssignTarget(var identifier: IdentifierReference?,
|
||||
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
companion object {
|
||||
fun fromExpr(expr: Expression): AssignTarget {
|
||||
return when (expr) {
|
||||
is IdentifierReference -> AssignTarget(expr, null, null, expr.position)
|
||||
is ArrayIndexedExpression -> AssignTarget(null, expr, null, expr.position)
|
||||
is DirectMemoryRead -> AssignTarget(null, null, DirectMemoryWrite(expr.addressExpression, expr.position), expr.position)
|
||||
else -> throw FatalAstException("invalid expression object $expr")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun inferType(program: Program, stmt: Statement): InferredTypes.InferredType {
|
||||
if(identifier!=null) {
|
||||
val symbol = program.namespace.lookup(identifier!!.nameInSource, stmt) ?: return InferredTypes.unknown()
|
||||
fun inferType(program: Program): InferredTypes.InferredType {
|
||||
if (identifier != null) {
|
||||
val symbol = program.namespace.lookup(identifier!!.nameInSource, this) ?: return InferredTypes.unknown()
|
||||
if (symbol is VarDecl) return InferredTypes.knownFor(symbol.datatype)
|
||||
}
|
||||
|
||||
if(arrayindexed!=null) {
|
||||
if (arrayindexed != null) {
|
||||
return arrayindexed!!.inferType(program)
|
||||
}
|
||||
|
||||
if(memoryAddress!=null)
|
||||
if (memoryAddress != null)
|
||||
return InferredTypes.knownFor(DataType.UBYTE)
|
||||
|
||||
return InferredTypes.unknown()
|
||||
@ -430,69 +472,93 @@ data class AssignTarget(var identifier: IdentifierReference?,
|
||||
|
||||
fun toExpression(): Expression {
|
||||
return when {
|
||||
identifier!=null -> identifier!!
|
||||
arrayindexed!=null -> arrayindexed!!
|
||||
memoryAddress!=null -> DirectMemoryRead(memoryAddress.addressExpression, memoryAddress.position)
|
||||
identifier != null -> identifier!!
|
||||
arrayindexed != null -> arrayindexed!!
|
||||
memoryAddress != null -> DirectMemoryRead(memoryAddress.addressExpression, memoryAddress.position)
|
||||
else -> throw FatalAstException("invalid assignmenttarget $this")
|
||||
}
|
||||
}
|
||||
|
||||
infix fun isSameAs(value: Expression): Boolean {
|
||||
return when {
|
||||
this.memoryAddress!=null -> {
|
||||
memoryAddress != null -> {
|
||||
// if the target is a memory write, and the value is a memory read, they're the same if the address matches
|
||||
if(value is DirectMemoryRead)
|
||||
if (value is DirectMemoryRead)
|
||||
this.memoryAddress.addressExpression isSameAs value.addressExpression
|
||||
else
|
||||
false
|
||||
}
|
||||
this.identifier!=null -> value is IdentifierReference && value.nameInSource==identifier!!.nameInSource
|
||||
this.arrayindexed!=null -> value is ArrayIndexedExpression &&
|
||||
value.identifier.nameInSource==arrayindexed!!.identifier.nameInSource &&
|
||||
value.arrayspec.constIndex()!=null &&
|
||||
arrayindexed!!.arrayspec.constIndex()!=null &&
|
||||
value.arrayspec.constIndex()==arrayindexed!!.arrayspec.constIndex()
|
||||
identifier != null -> value is IdentifierReference && value.nameInSource == identifier!!.nameInSource
|
||||
arrayindexed != null -> {
|
||||
if(value is ArrayIndexedExpression && value.arrayvar.nameInSource == arrayindexed!!.arrayvar.nameInSource)
|
||||
arrayindexed!!.indexer isSameAs value.indexer
|
||||
else
|
||||
false
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
fun isSameAs(other: AssignTarget, program: Program): Boolean {
|
||||
if(this===other)
|
||||
if (this === other)
|
||||
return true
|
||||
if(this.identifier!=null && other.identifier!=null)
|
||||
return this.identifier!!.nameInSource==other.identifier!!.nameInSource
|
||||
if(this.memoryAddress!=null && other.memoryAddress!=null) {
|
||||
if (this.identifier != null && other.identifier != null)
|
||||
return this.identifier!!.nameInSource == other.identifier!!.nameInSource
|
||||
if (this.memoryAddress != null && other.memoryAddress != null) {
|
||||
val addr1 = this.memoryAddress.addressExpression.constValue(program)
|
||||
val addr2 = other.memoryAddress.addressExpression.constValue(program)
|
||||
return addr1!=null && addr2!=null && addr1==addr2
|
||||
return addr1 != null && addr2 != null && addr1 == addr2
|
||||
}
|
||||
if(this.arrayindexed!=null && other.arrayindexed!=null) {
|
||||
if(this.arrayindexed!!.identifier.nameInSource == other.arrayindexed!!.identifier.nameInSource) {
|
||||
val x1 = this.arrayindexed!!.arrayspec.index.constValue(program)
|
||||
val x2 = other.arrayindexed!!.arrayspec.index.constValue(program)
|
||||
return x1!=null && x2!=null && x1==x2
|
||||
if (this.arrayindexed != null && other.arrayindexed != null) {
|
||||
if (this.arrayindexed!!.arrayvar.nameInSource == other.arrayindexed!!.arrayvar.nameInSource) {
|
||||
val x1 = this.arrayindexed!!.indexer.constIndex()
|
||||
val x2 = other.arrayindexed!!.indexer.constIndex()
|
||||
return x1 != null && x2 != null && x1 == x2
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun isNotMemory(namespace: INameScope): Boolean {
|
||||
if(this.memoryAddress!=null)
|
||||
return false
|
||||
if(this.arrayindexed!=null) {
|
||||
val targetStmt = this.arrayindexed!!.identifier.targetVarDecl(namespace)
|
||||
if(targetStmt!=null)
|
||||
return targetStmt.type!= VarDeclType.MEMORY
|
||||
fun isInRegularRAM(namespace: INameScope): Boolean {
|
||||
when {
|
||||
this.memoryAddress != null -> {
|
||||
return when (this.memoryAddress.addressExpression) {
|
||||
is NumericLiteralValue -> {
|
||||
CompilationTarget.instance.machine.isRegularRAMaddress((this.memoryAddress.addressExpression as NumericLiteralValue).number.toInt())
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val decl = (this.memoryAddress.addressExpression as IdentifierReference).targetVarDecl(namespace)
|
||||
if ((decl?.type == VarDeclType.VAR || decl?.type == VarDeclType.CONST) && decl.value is NumericLiteralValue)
|
||||
CompilationTarget.instance.machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
|
||||
else
|
||||
false
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
this.arrayindexed != null -> {
|
||||
val targetStmt = this.arrayindexed!!.arrayvar.targetVarDecl(namespace)
|
||||
return if (targetStmt?.type == VarDeclType.MEMORY) {
|
||||
val addr = targetStmt.value as? NumericLiteralValue
|
||||
if (addr != null)
|
||||
CompilationTarget.instance.machine.isRegularRAMaddress(addr.number.toInt())
|
||||
else
|
||||
false
|
||||
} else true
|
||||
}
|
||||
this.identifier != null -> {
|
||||
val decl = this.identifier!!.targetVarDecl(namespace)!!
|
||||
return if (decl.type == VarDeclType.MEMORY && decl.value is NumericLiteralValue)
|
||||
CompilationTarget.instance.machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
|
||||
else
|
||||
true
|
||||
}
|
||||
else -> return true
|
||||
}
|
||||
if(this.identifier!=null) {
|
||||
val targetStmt = this.identifier!!.targetVarDecl(namespace)
|
||||
if(targetStmt!=null)
|
||||
return targetStmt.type!= VarDeclType.MEMORY
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PostIncrDecr(var target: AssignTarget, val operator: String, override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
@ -619,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, val partOfStatement: Statement, var used: Int=0)
|
||||
}
|
||||
|
||||
// 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)
|
||||
@ -633,7 +715,24 @@ class Subroutine(override val name: String,
|
||||
override var statements: MutableList<Statement>,
|
||||
override val position: Position) : Statement(), INameScope {
|
||||
|
||||
constructor(name: String, parameters: List<SubroutineParameter>, returntypes: List<DataType>, statements: MutableList<Statement>, position: Position)
|
||||
: this(name, parameters, returntypes, emptyList(), determineReturnRegisters(returntypes), emptySet(), null, false, 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.AY, null))
|
||||
null -> emptyList()
|
||||
else -> listOf(RegisterOrStatusflag(RegisterOrPair.AY, null))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override lateinit var parent: Node
|
||||
val asmGenInfo = AsmGenInfo()
|
||||
val scopedname: String by lazy { makeScopedName(name) }
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
@ -658,6 +757,8 @@ 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 shouldPreserveStatusRegisterAfterCall() = asmReturnvaluesRegisters.any { it.statusflag != null } || shouldSaveX()
|
||||
fun shouldSaveX() = CpuRegister.X in asmClobbers || regXasResult() || regXasParam()
|
||||
|
||||
fun amountOfRtsInAsm(): Int = statements
|
||||
.asSequence()
|
||||
@ -815,19 +916,19 @@ class RepeatLoop(var iterations: Expression?, var body: AnonymousScope, override
|
||||
}
|
||||
|
||||
class UntilLoop(var body: AnonymousScope,
|
||||
var untilCondition: Expression,
|
||||
var condition: Expression,
|
||||
override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
untilCondition.linkParents(this)
|
||||
condition.linkParents(this)
|
||||
body.linkParents(this)
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
when {
|
||||
node===untilCondition -> untilCondition = replacement as Expression
|
||||
node===condition -> condition = replacement as Expression
|
||||
node===body -> body = replacement as AnonymousScope
|
||||
else -> throw FatalAstException("invalid replace")
|
||||
}
|
||||
@ -866,11 +967,11 @@ class WhenStatement(var condition: Expression,
|
||||
if(choice.values==null)
|
||||
result.add(null to choice)
|
||||
else {
|
||||
val values = choice.values!!.map { it.constValue(program)?.number?.toInt() }
|
||||
if(values.contains(null))
|
||||
result.add(null to choice)
|
||||
else
|
||||
result.add(values.filterNotNull() to choice)
|
||||
val values = choice.values!!.map {
|
||||
val cv = it.constValue(program)
|
||||
cv?.number?.toInt() ?: it.hashCode() // the hashcode is a nonsensical number but it avoids weird AST validation errors later
|
||||
}
|
||||
result.add(values to choice)
|
||||
}
|
||||
}
|
||||
return result
|
||||
@ -892,9 +993,15 @@ class WhenChoice(var values: List<Expression>?, // if null, this is t
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is AnonymousScope && node===statements)
|
||||
statements = replacement
|
||||
replacement.parent = this
|
||||
val choiceValues = values
|
||||
if(replacement is AnonymousScope && node===statements) {
|
||||
statements = replacement
|
||||
replacement.parent = this
|
||||
} else if(choiceValues!=null && node in choiceValues) {
|
||||
throw FatalAstException("cannot replace choice values")
|
||||
} else {
|
||||
throw FatalAstException("invalid replacement")
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
|
@ -15,10 +15,18 @@ 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.
|
||||
decl.value = decl.zeroElementValue()
|
||||
// a numeric vardecl without an initial value is initialized with zero,
|
||||
// unless there's already an assignment below, that initializes the value
|
||||
if(decl.allowInitializeWithZero)
|
||||
{
|
||||
val nextAssign = decl.definingScope().nextSibling(decl) as? Assignment
|
||||
if (nextAssign != null && nextAssign.target.isSameAs(IdentifierReference(listOf(decl.name), Position.DUMMY)))
|
||||
decl.value = null
|
||||
else
|
||||
decl.value = decl.zeroElementValue()
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
@ -26,16 +34,31 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
// Try to replace A = B <operator> Something by A= B, A = A <operator> Something
|
||||
// this triggers the more efficent augmented assignment code generation more often.
|
||||
// But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF.
|
||||
if(!assignment.isAugmentable
|
||||
&& assignment.target.identifier != null
|
||||
&& assignment.target.isNotMemory(program.namespace)) {
|
||||
&& assignment.target.isInRegularRAM(program.namespace)) {
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
if(binExpr!=null && binExpr.operator !in comparisonOperators) {
|
||||
if(binExpr.left !is BinaryExpression) {
|
||||
val assignLeft = Assignment(assignment.target, binExpr.left, assignment.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, assignLeft, parent),
|
||||
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
|
||||
if (binExpr != null && binExpr.operator !in comparisonOperators) {
|
||||
if (binExpr.left !is BinaryExpression) {
|
||||
if (binExpr.right.referencesIdentifier(*assignment.target.identifier!!.nameInSource.toTypedArray())) {
|
||||
// the right part of the expression contains the target variable itself.
|
||||
// we can't 'split' it trivially because the variable will be changed halfway through.
|
||||
if(binExpr.operator in associativeOperators) {
|
||||
// A = <something-without-A> <associativeoperator> <otherthing-with-A>
|
||||
// use the other part of the expression to split.
|
||||
val assignRight = Assignment(assignment.target, binExpr.right, assignment.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, assignRight, assignment.definingScope()),
|
||||
IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr),
|
||||
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
|
||||
}
|
||||
} else {
|
||||
val assignLeft = Assignment(assignment.target, binExpr.left, assignment.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, assignLeft, assignment.definingScope()),
|
||||
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -51,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) {
|
||||
@ -104,9 +127,8 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
||||
&& outerStatements[subroutineStmtIdx - 1] !is Subroutine
|
||||
&& outerStatements[subroutineStmtIdx - 1] !is Return
|
||||
&& outerScope !is Block) {
|
||||
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope as Node)
|
||||
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope)
|
||||
}
|
||||
|
||||
return mods
|
||||
}
|
||||
|
||||
@ -142,11 +164,19 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
||||
|
||||
if(sourceDt in PassByReferenceDatatypes) {
|
||||
if(typecast.type==DataType.UWORD) {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
typecast,
|
||||
AddressOf(typecast.expression as IdentifierReference, typecast.position),
|
||||
parent
|
||||
))
|
||||
if(typecast.expression is IdentifierReference) {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
typecast,
|
||||
AddressOf(typecast.expression as IdentifierReference, typecast.position),
|
||||
parent
|
||||
))
|
||||
} else if(typecast.expression is IFunctionCall) {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
typecast,
|
||||
typecast.expression,
|
||||
parent
|
||||
))
|
||||
}
|
||||
} else {
|
||||
errors.err("cannot cast pass-by-reference value to type ${typecast.type} (only to UWORD)", typecast.position)
|
||||
}
|
||||
@ -154,4 +184,34 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
|
||||
val binExpr = ifStatement.condition as? BinaryExpression
|
||||
if(binExpr==null || binExpr.operator !in comparisonOperators) {
|
||||
// if x -> if x!=0, if x+5 -> if x+5 != 0
|
||||
val booleanExpr = BinaryExpression(ifStatement.condition, "!=", NumericLiteralValue.optimalInteger(0, ifStatement.condition.position), ifStatement.condition.position)
|
||||
return listOf(IAstModification.ReplaceNode(ifStatement.condition, booleanExpr, ifStatement))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
|
||||
val binExpr = untilLoop.condition as? BinaryExpression
|
||||
if(binExpr==null || binExpr.operator !in comparisonOperators) {
|
||||
// until x -> until x!=0, until x+5 -> until x+5 != 0
|
||||
val booleanExpr = BinaryExpression(untilLoop.condition, "!=", NumericLiteralValue.optimalInteger(0, untilLoop.condition.position), untilLoop.condition.position)
|
||||
return listOf(IAstModification.ReplaceNode(untilLoop.condition, booleanExpr, untilLoop))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
|
||||
val binExpr = whileLoop.condition as? BinaryExpression
|
||||
if(binExpr==null || binExpr.operator !in comparisonOperators) {
|
||||
// while x -> while x!=0, while x+5 -> while x+5 != 0
|
||||
val booleanExpr = BinaryExpression(whileLoop.condition, "!=", NumericLiteralValue.optimalInteger(0, whileLoop.condition.position), whileLoop.condition.position)
|
||||
return listOf(IAstModification.ReplaceNode(whileLoop.condition, booleanExpr, whileLoop))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,10 @@ data class CompilationOptions(val output: OutputType,
|
||||
val launcher: LauncherType,
|
||||
val zeropage: ZeropageType,
|
||||
val zpReserved: List<IntRange>,
|
||||
val floats: Boolean)
|
||||
val floats: Boolean,
|
||||
val noSysInit: Boolean) {
|
||||
var slowCodegenWarnings = false
|
||||
}
|
||||
|
||||
|
||||
class CompilerException(message: String?) : Exception(message)
|
||||
|
@ -7,6 +7,7 @@ import prog8.ast.statements.Directive
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8.optimizer.*
|
||||
import prog8.optimizer.UnusedCodeRemover
|
||||
import prog8.optimizer.constantFold
|
||||
import prog8.optimizer.optimizeStatements
|
||||
@ -28,6 +29,7 @@ class CompilationResult(val success: Boolean,
|
||||
fun compileProgram(filepath: Path,
|
||||
optimize: Boolean,
|
||||
writeAssembly: Boolean,
|
||||
slowCodegenWarnings: Boolean,
|
||||
compilationTarget: String,
|
||||
outputDir: Path): CompilationResult {
|
||||
var programName = ""
|
||||
@ -48,6 +50,7 @@ fun compileProgram(filepath: Path,
|
||||
val totalTime = measureTimeMillis {
|
||||
// import main module and everything it needs
|
||||
val (ast, compilationOptions, imported) = parseImports(filepath, errors)
|
||||
compilationOptions.slowCodegenWarnings = slowCodegenWarnings
|
||||
programAst = ast
|
||||
importedFiles = imported
|
||||
processAst(programAst, errors, compilationOptions)
|
||||
@ -106,9 +109,9 @@ private fun parseImports(filepath: Path, errors: ErrorReporter): Triple<Program,
|
||||
// depending on the machine and compiler options we may have to include some libraries
|
||||
CompilationTarget.instance.machine.importLibs(compilerOptions, importer, programAst)
|
||||
|
||||
// always import prog8lib and math
|
||||
// always import prog8_lib and math
|
||||
importer.importLibraryModule(programAst, "math")
|
||||
importer.importLibraryModule(programAst, "prog8lib")
|
||||
importer.importLibraryModule(programAst, "prog8_lib")
|
||||
errors.handle()
|
||||
return Triple(programAst, compilerOptions, importedFiles)
|
||||
}
|
||||
@ -119,12 +122,11 @@ private fun determineCompilationOptions(program: Program): CompilationOptions {
|
||||
as? Directive)?.args?.single()?.name?.toUpperCase()
|
||||
val launcherType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" }
|
||||
as? Directive)?.args?.single()?.name?.toUpperCase()
|
||||
mainModule.loadAddress = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%address" }
|
||||
as? Directive)?.args?.single()?.int ?: 0
|
||||
val zpoption: String? = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" }
|
||||
as? Directive)?.args?.single()?.name?.toUpperCase()
|
||||
val allOptions = program.modules.flatMap { it.statements }.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.toSet()
|
||||
val floatsEnabled = allOptions.any { it.name == "enable_floats" }
|
||||
val noSysInit = allOptions.any { it.name == "no_sysinit" }
|
||||
var zpType: ZeropageType =
|
||||
if (zpoption == null)
|
||||
if(floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE
|
||||
@ -160,7 +162,7 @@ private fun determineCompilationOptions(program: Program): CompilationOptions {
|
||||
return CompilationOptions(
|
||||
if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType),
|
||||
if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType),
|
||||
zpType, zpReserved, floatsEnabled
|
||||
zpType, zpReserved, floatsEnabled, noSysInit
|
||||
)
|
||||
}
|
||||
|
||||
@ -171,7 +173,8 @@ private fun processAst(programAst: Program, errors: ErrorReporter, compilerOptio
|
||||
errors.handle()
|
||||
programAst.constantFold(errors)
|
||||
errors.handle()
|
||||
programAst.reorderStatements()
|
||||
programAst.reorderStatements(errors)
|
||||
errors.handle()
|
||||
programAst.addTypecasts(errors)
|
||||
errors.handle()
|
||||
programAst.variousCleanups()
|
||||
@ -187,14 +190,15 @@ private fun optimizeAst(programAst: Program, errors: ErrorReporter) {
|
||||
while (true) {
|
||||
// keep optimizing expressions and statements until no more steps remain
|
||||
val optsDone1 = programAst.simplifyExpressions()
|
||||
val optsDone2 = programAst.optimizeStatements(errors)
|
||||
programAst.constantFold(errors) // because simplified statements and expressions could give rise to more constants that can be folded away
|
||||
val optsDone2 = programAst.splitBinaryExpressions()
|
||||
val optsDone3 = programAst.optimizeStatements(errors)
|
||||
programAst.constantFold(errors) // because simplified statements and expressions can result in more constants that can be folded away
|
||||
errors.handle()
|
||||
if (optsDone1 + optsDone2 == 0)
|
||||
if (optsDone1 + optsDone2 + optsDone3 == 0)
|
||||
break
|
||||
}
|
||||
|
||||
val remover = UnusedCodeRemover(errors)
|
||||
val remover = UnusedCodeRemover(programAst, errors)
|
||||
remover.visit(programAst)
|
||||
remover.applyModifications()
|
||||
errors.handle()
|
||||
@ -206,9 +210,11 @@ private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerO
|
||||
programAst.variousCleanups()
|
||||
programAst.checkValid(compilerOptions, errors) // check if final tree is still valid
|
||||
errors.handle()
|
||||
programAst.checkRecursion(errors) // check if there are recursive subroutine calls
|
||||
val callGraph = CallGraph(programAst)
|
||||
callGraph.checkRecursiveCalls(errors)
|
||||
errors.handle()
|
||||
programAst.verifyFunctionArgTypes()
|
||||
programAst.moveMainAndStartToFirst()
|
||||
}
|
||||
|
||||
private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir: Path,
|
||||
|
@ -64,18 +64,10 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
||||
|
||||
private fun makeAllocation(address: Int, size: Int, datatype: DataType, name: String?): Int {
|
||||
free.removeAll(address until address+size)
|
||||
allocations[address] = Pair(name ?: "<unnamed>", datatype)
|
||||
allocations[address] = (name ?: "<unnamed>") to datatype
|
||||
return address
|
||||
}
|
||||
|
||||
private fun loneByte(address: Int) = address in free && address-1 !in free && address+1 !in free
|
||||
private fun sequentialFree(address: Int, size: Int) = free.containsAll((address until address+size).toList())
|
||||
|
||||
enum class ExitProgramStrategy {
|
||||
CLEAN_EXIT,
|
||||
SYSTEM_RESET
|
||||
}
|
||||
|
||||
abstract val exitProgramStrategy: ExitProgramStrategy
|
||||
|
||||
}
|
||||
|
@ -17,8 +17,6 @@ internal interface CompilationTarget {
|
||||
fun encodeString(str: String, altEncoding: Boolean): List<Short>
|
||||
fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
|
||||
fun asmGenerator(program: Program, errors: ErrorReporter, zp: Zeropage, options: CompilationOptions, path: Path): IAssemblyGenerator
|
||||
val initProcName: String?
|
||||
val resetProcName: String?
|
||||
|
||||
companion object {
|
||||
lateinit var instance: CompilationTarget
|
||||
@ -35,8 +33,6 @@ internal object C64Target: CompilationTarget {
|
||||
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||
override fun asmGenerator(program: Program, errors: ErrorReporter, zp: Zeropage, options: CompilationOptions, path: Path) =
|
||||
AsmGen(program, errors, zp, options, path)
|
||||
override val initProcName = "c64.init_system"
|
||||
override val resetProcName = "c64.reset_system"
|
||||
}
|
||||
|
||||
internal object Cx16Target: CompilationTarget {
|
||||
@ -48,6 +44,4 @@ internal object Cx16Target: CompilationTarget {
|
||||
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||
override fun asmGenerator(program: Program, errors: ErrorReporter, zp: Zeropage, options: CompilationOptions, path: Path) =
|
||||
AsmGen(program, errors, zp, options, path)
|
||||
override val initProcName = "cx16.init_system"
|
||||
override val resetProcName = "cx16.reset_system"
|
||||
}
|
||||
|
@ -7,6 +7,8 @@ internal interface IAssemblyGenerator {
|
||||
}
|
||||
|
||||
internal const val generatedLabelPrefix = "_prog8_label_"
|
||||
internal const val subroutineFloatEvalResultVar1 = "_prog8_float_eval_result1"
|
||||
internal const val subroutineFloatEvalResultVar2 = "_prog8_float_eval_result2"
|
||||
|
||||
internal interface IAssemblyProgram {
|
||||
val name: String
|
||||
|
@ -35,4 +35,5 @@ internal interface IMachineDefinition {
|
||||
fun getFloatRomConst(number: Double): String?
|
||||
fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program)
|
||||
fun launchEmulator(programName: String)
|
||||
fun isRegularRAMaddress(address: Int): Boolean
|
||||
}
|
||||
|
@ -37,7 +37,8 @@ internal object C64MachineDefinition: IMachineDefinition {
|
||||
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.ZERO" // not a ROM const
|
||||
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"
|
||||
@ -88,6 +89,8 @@ internal object C64MachineDefinition: IMachineDefinition {
|
||||
}
|
||||
}
|
||||
|
||||
override fun isRegularRAMaddress(address: Int): Boolean = address<0xa000 || address in 0xc000..0xcfff
|
||||
|
||||
override fun initializeZeropage(compilerOptions: CompilationOptions) {
|
||||
zeropage = C64Zeropage(compilerOptions)
|
||||
}
|
||||
@ -107,17 +110,11 @@ 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
|
||||
|
||||
|
||||
override val exitProgramStrategy: ExitProgramStrategy = when (options.zeropage) {
|
||||
ZeropageType.BASICSAFE, ZeropageType.DONTUSE -> ExitProgramStrategy.CLEAN_EXIT
|
||||
ZeropageType.FLOATSAFE, ZeropageType.KERNALSAFE, ZeropageType.FULL -> ExitProgramStrategy.SYSTEM_RESET
|
||||
}
|
||||
|
||||
|
||||
init {
|
||||
if (options.floats && options.zeropage !in setOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
|
||||
throw CompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
|
||||
@ -129,8 +126,10 @@ internal object C64MachineDefinition: IMachineDefinition {
|
||||
free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ
|
||||
} else {
|
||||
if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
|
||||
free.addAll(listOf(0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
|
||||
0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
|
||||
free.addAll(listOf(
|
||||
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
|
||||
0x16, 0x17, 0x18, 0x19, 0x1a,
|
||||
0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
|
||||
0x22, 0x23, 0x24, 0x25,
|
||||
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
|
||||
0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53,
|
||||
@ -151,16 +150,16 @@ internal object C64MachineDefinition: IMachineDefinition {
|
||||
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
|
||||
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
||||
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
|
||||
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xf
|
||||
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
|
||||
))
|
||||
}
|
||||
|
||||
if(options.zeropage!=ZeropageType.DONTUSE) {
|
||||
// add the other free Zp addresses,
|
||||
// these are valid for the C-64 (when no RS232 I/O is performed) but to keep BASIC running fully:
|
||||
// add the free Zp addresses
|
||||
// these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O*
|
||||
free.addAll(listOf(0x04, 0x05, 0x06, 0x0a, 0x0e,
|
||||
0x94, 0x95, 0xa7, 0xa8, 0xa9, 0xaa,
|
||||
0xb5, 0xb6, 0xf7, 0xf8, 0xf9))
|
||||
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa5, 0xa6,
|
||||
0xb0, 0xb1, 0xbe, 0xbf, 0xf9))
|
||||
} else {
|
||||
// don't use the zeropage at all
|
||||
free.clear()
|
||||
|
@ -1054,11 +1054,16 @@ object Petscii {
|
||||
val lookup = if(lowercase) encodingPetsciiLowercase else encodingPetsciiUppercase
|
||||
return text.map {
|
||||
val petscii = lookup[it]
|
||||
petscii?.toShort() ?: if(it=='\u0000')
|
||||
0.toShort()
|
||||
else {
|
||||
val case = if (lowercase) "lower" else "upper"
|
||||
throw CharConversionException("no ${case}case Petscii character for '$it' (${it.toShort()})")
|
||||
petscii?.toShort() ?: when (it) {
|
||||
'\u0000' -> 0.toShort()
|
||||
in '\u8000'..'\u80ff' -> {
|
||||
// special case: take the lower 8 bit hex value directly
|
||||
(it.toInt() - 0x8000).toShort()
|
||||
}
|
||||
else -> {
|
||||
val case = if (lowercase) "lower" else "upper"
|
||||
throw CharConversionException("no ${case}case Petscii character for '$it' (${it.toShort()})")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1072,11 +1077,16 @@ object Petscii {
|
||||
val lookup = if(lowercase) encodingScreencodeLowercase else encodingScreencodeUppercase
|
||||
return text.map{
|
||||
val screencode = lookup[it]
|
||||
screencode?.toShort() ?: if(it=='\u0000')
|
||||
0.toShort()
|
||||
else {
|
||||
val case = if (lowercase) "lower" else "upper"
|
||||
throw CharConversionException("no ${case}Screencode character for '$it' (${it.toShort()})")
|
||||
screencode?.toShort() ?: when (it) {
|
||||
'\u0000' -> 0.toShort()
|
||||
in '\u8000'..'\u80ff' -> {
|
||||
// special case: take the lower 8 bit hex value directly
|
||||
(it.toInt() - 0x8000).toShort()
|
||||
}
|
||||
else -> {
|
||||
val case = if (lowercase) "lower" else "upper"
|
||||
throw CharConversionException("no ${case}Screencode character for '$it' (${it.toShort()})")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,20 +8,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 +36,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)
|
||||
@ -47,10 +46,10 @@ internal class AsmGen(private val program: Program,
|
||||
private val forloopsAsmGen = ForLoopsAsmGen(program, this)
|
||||
private val postincrdecrAsmGen = PostIncrDecrAsmGen(program, this)
|
||||
private val functioncallAsmGen = FunctionCallAsmGen(program, this)
|
||||
private val assignmentAsmGen = AssignmentAsmGen(program, this)
|
||||
private val expressionsAsmGen = ExpressionsAsmGen(program, this)
|
||||
private val assignmentAsmGen = AssignmentAsmGen(program, this, expressionsAsmGen)
|
||||
internal val loopEndLabels = ArrayDeque<String>()
|
||||
internal val blockLevelVarInits = mutableMapOf<Block, MutableSet<VarDecl>>()
|
||||
private val blockLevelVarInits = mutableMapOf<Block, MutableSet<VarDecl>>()
|
||||
|
||||
override fun compileToAssembly(optimize: Boolean): IAssemblyProgram {
|
||||
assemblyLines.clear()
|
||||
@ -66,21 +65,27 @@ internal class AsmGen(private val program: Program,
|
||||
block2asm(b)
|
||||
footer()
|
||||
|
||||
if(optimize) {
|
||||
var optimizationsDone = 1
|
||||
while (optimizationsDone > 0) {
|
||||
optimizationsDone = optimizeAssembly(assemblyLines)
|
||||
}
|
||||
}
|
||||
|
||||
val outputFile = outputDir.resolve("${program.name}.asm").toFile()
|
||||
outputFile.printWriter().use {
|
||||
for (line in assemblyLines) { it.println(line) }
|
||||
}
|
||||
|
||||
if(optimize) {
|
||||
assemblyLines.clear()
|
||||
assemblyLines.addAll(outputFile.readLines())
|
||||
var optimizationsDone = 1
|
||||
while (optimizationsDone > 0) {
|
||||
optimizationsDone = optimizeAssembly(assemblyLines)
|
||||
}
|
||||
outputFile.printWriter().use {
|
||||
for (line in assemblyLines) { it.println(line) }
|
||||
}
|
||||
}
|
||||
|
||||
return AssemblyProgram(program.name, outputDir)
|
||||
}
|
||||
|
||||
|
||||
private fun header() {
|
||||
val ourName = this.javaClass.name
|
||||
val cpu = when(CompilationTarget.instance.machine.cpu) {
|
||||
@ -120,18 +125,14 @@ internal class AsmGen(private val program: Program,
|
||||
out(" .null $9e, format(' %d ', _prog8_entrypoint), $3a, $8f, ' prog8 by idj'")
|
||||
out("+\t.word 0")
|
||||
out("_prog8_entrypoint\t; assembly code starts here\n")
|
||||
out(" tsx")
|
||||
out(" stx prog8_lib.orig_stackpointer")
|
||||
if(!CompilationTarget.instance.initProcName.isNullOrEmpty())
|
||||
out(" jsr ${CompilationTarget.instance.initProcName}")
|
||||
if(!options.noSysInit)
|
||||
out(" jsr ${CompilationTarget.instance.name}.init_system")
|
||||
}
|
||||
options.output == OutputType.PRG -> {
|
||||
out("; ---- program without basic sys call ----")
|
||||
out("* = ${program.actualLoadAddress.toHex()}\n")
|
||||
out(" tsx")
|
||||
out(" stx prog8_lib.orig_stackpointer")
|
||||
if(!CompilationTarget.instance.initProcName.isNullOrEmpty())
|
||||
out(" jsr ${CompilationTarget.instance.initProcName}")
|
||||
if(!options.noSysInit)
|
||||
out(" jsr ${CompilationTarget.instance.name}.init_system")
|
||||
}
|
||||
options.output == OutputType.RAW -> {
|
||||
out("; ---- raw assembler program ----")
|
||||
@ -139,33 +140,16 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
if (zeropage.exitProgramStrategy != Zeropage.ExitProgramStrategy.CLEAN_EXIT) {
|
||||
// disable shift-commodore charset switching and run/stop key
|
||||
out(" lda #$80")
|
||||
out(" lda #$80")
|
||||
out(" sta 657\t; disable charset switching")
|
||||
out(" lda #239")
|
||||
out(" sta 808\t; disable run/stop key")
|
||||
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
|
||||
pha
|
||||
lda #<${CompilationTarget.instance.name}.reset_system
|
||||
pha""")
|
||||
}
|
||||
|
||||
out(" ldx #\$ff\t; init estack pointer")
|
||||
|
||||
out(" ; initialize the variables in each block that has globals")
|
||||
program.allBlocks().forEach {
|
||||
if(it.statements.filterIsInstance<VarDecl>().any { vd->vd.value!=null && vd.type==VarDeclType.VAR && vd.datatype in NumericDatatypes})
|
||||
out(" jsr ${it.name}.prog8_init_vars")
|
||||
}
|
||||
|
||||
out(" clc")
|
||||
when (zeropage.exitProgramStrategy) {
|
||||
Zeropage.ExitProgramStrategy.CLEAN_EXIT -> {
|
||||
out(" jmp main.start\t; jump to program entrypoint")
|
||||
}
|
||||
Zeropage.ExitProgramStrategy.SYSTEM_RESET -> {
|
||||
out(" jsr main.start\t; call program entrypoint")
|
||||
out(" jmp ${CompilationTarget.instance.resetProcName}")
|
||||
}
|
||||
}
|
||||
out(" jmp main.start ; start program / force start proc to be included")
|
||||
}
|
||||
|
||||
private fun footer() {
|
||||
@ -176,6 +160,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) {
|
||||
@ -214,13 +199,8 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
|
||||
private fun assignInitialValueToVar(decl: VarDecl, variableName: List<String>) {
|
||||
val variable = IdentifierReference(variableName, decl.position)
|
||||
variable.linkParents(decl.parent)
|
||||
val asgn = AsmAssignment(
|
||||
AsmAssignSource.fromAstSource(decl.value!!, program),
|
||||
AsmAssignTarget(TargetStorageKind.VARIABLE, program, this, decl.datatype, variable = variable),
|
||||
false, decl.position)
|
||||
assignmentAsmGen.translateNormalAssignment(asgn)
|
||||
val asmName = asmVariableName(variableName)
|
||||
assignmentAsmGen.assignExpressionToVariable(decl.value!!, asmName, decl.datatype, decl.definingSubroutine())
|
||||
}
|
||||
|
||||
private var generatedLabelSequenceNumber: Int = 0
|
||||
@ -247,8 +227,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>) {
|
||||
@ -269,7 +253,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.
|
||||
}
|
||||
@ -426,10 +410,17 @@ internal class AsmGen(private val program: Program,
|
||||
"$"+number.toString(16).padStart(2, '0')
|
||||
}
|
||||
DataType.ARRAY_UW -> array.map {
|
||||
if(it is NumericLiteralValue) {
|
||||
"$" + it.number.toInt().toString(16).padStart(4, '0')
|
||||
} else {
|
||||
(it as AddressOf).identifier.nameInSource.joinToString(".")
|
||||
when (it) {
|
||||
is NumericLiteralValue -> {
|
||||
"$" + it.number.toInt().toString(16).padStart(4, '0')
|
||||
}
|
||||
is AddressOf -> {
|
||||
it.identifier.firstStructVarName(program.namespace) ?: asmSymbolName(it.identifier)
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
it.firstStructVarName(program.namespace) ?: asmSymbolName(it)
|
||||
}
|
||||
else -> throw AssemblyError("weird array elt dt")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invalid arraysize type")
|
||||
@ -509,8 +500,14 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
internal fun asmSymbolName(name: String) = fixNameSymbols(name)
|
||||
internal fun asmVariableName(name: String) = fixNameSymbols(name)
|
||||
internal fun asmSymbolName(name: Iterable<String>) = fixNameSymbols(name.joinToString("."))
|
||||
internal fun asmVariableName(name: Iterable<String>) = fixNameSymbols(name.joinToString("."))
|
||||
|
||||
|
||||
internal fun loadByteFromPointerIntoA(pointervar: IdentifierReference): Pair<Boolean, String> {
|
||||
// returns if the pointer is already on the ZP itself or not (in which case SCRATCH_W1 is used as intermediary)
|
||||
// returns if the pointer is already on the ZP itself or not (in the latter case SCRATCH_W1 is used as intermediary)
|
||||
val sourceName = asmVariableName(pointervar)
|
||||
val vardecl = pointervar.targetVarDecl(program.namespace)!!
|
||||
val scopedName = vardecl.makeScopedName(vardecl.name)
|
||||
@ -551,55 +548,64 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
internal fun fixNameSymbols(name: String) = name.replace("<", "prog8_").replace(">", "") // take care of the autogenerated invalid (anon) label names
|
||||
private fun fixNameSymbols(name: String) = name.replace("<", "prog8_").replace(">", "") // take care of the autogenerated invalid (anon) label names
|
||||
|
||||
|
||||
private val saveRegisterLabels = Stack<String>();
|
||||
|
||||
internal fun saveRegister(register: CpuRegister) {
|
||||
when(register) {
|
||||
CpuRegister.A -> out(" pha")
|
||||
CpuRegister.X -> {
|
||||
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phx")
|
||||
else {
|
||||
val save = makeLabel("saveX")
|
||||
saveRegisterLabels.push(save)
|
||||
out("""
|
||||
stx $save
|
||||
jmp +
|
||||
$save .byte 0
|
||||
+""")
|
||||
internal fun saveRegister(register: CpuRegister, dontUseStack: Boolean, scope: Subroutine) {
|
||||
if(dontUseStack) {
|
||||
when (register) {
|
||||
CpuRegister.A -> {
|
||||
out(" sta _prog8_regsaveA")
|
||||
scope.asmGenInfo.usedRegsaveA = true
|
||||
}
|
||||
CpuRegister.X -> {
|
||||
out(" stx _prog8_regsaveX")
|
||||
scope.asmGenInfo.usedRegsaveX = true
|
||||
}
|
||||
CpuRegister.Y -> {
|
||||
out(" sty _prog8_regsaveY")
|
||||
scope.asmGenInfo.usedRegsaveY = true
|
||||
}
|
||||
}
|
||||
CpuRegister.Y -> {
|
||||
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phy")
|
||||
else {
|
||||
val save = makeLabel("saveY")
|
||||
out("""
|
||||
sty $save
|
||||
jmp +
|
||||
$save .byte 0
|
||||
+""")
|
||||
|
||||
} else {
|
||||
when (register) {
|
||||
CpuRegister.A -> out(" pha")
|
||||
CpuRegister.X -> {
|
||||
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phx")
|
||||
else {
|
||||
out(" stx _prog8_regsaveX")
|
||||
scope.asmGenInfo.usedRegsaveX = true
|
||||
}
|
||||
}
|
||||
CpuRegister.Y -> {
|
||||
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phy")
|
||||
else {
|
||||
out(" sty _prog8_regsaveY")
|
||||
scope.asmGenInfo.usedRegsaveY = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun restoreRegister(register: CpuRegister) {
|
||||
when(register) {
|
||||
CpuRegister.A -> out(" pla")
|
||||
CpuRegister.X -> {
|
||||
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" plx")
|
||||
else {
|
||||
val save = saveRegisterLabels.pop()
|
||||
out(" ldx $save")
|
||||
}
|
||||
internal fun restoreRegister(register: CpuRegister, dontUseStack: Boolean) {
|
||||
if(dontUseStack) {
|
||||
when (register) {
|
||||
CpuRegister.A -> out(" sta _prog8_regsaveA")
|
||||
CpuRegister.X -> out(" ldx _prog8_regsaveX")
|
||||
CpuRegister.Y -> out(" ldy _prog8_regsaveY")
|
||||
}
|
||||
CpuRegister.Y -> {
|
||||
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" ply")
|
||||
else {
|
||||
val save = saveRegisterLabels.pop()
|
||||
out(" ldy $save")
|
||||
|
||||
} else {
|
||||
when (register) {
|
||||
CpuRegister.A -> out(" pla")
|
||||
CpuRegister.X -> {
|
||||
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" plx")
|
||||
else out(" ldx _prog8_regsaveX")
|
||||
}
|
||||
CpuRegister.Y -> {
|
||||
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" ply")
|
||||
else out(" ldy _prog8_regsaveY")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -619,19 +625,10 @@ $save .byte 0
|
||||
is FunctionCallStatement -> {
|
||||
val functionName = stmt.target.nameInSource.last()
|
||||
val builtinFunc = BuiltinFunctions[functionName]
|
||||
if(builtinFunc!=null) {
|
||||
if (builtinFunc != null) {
|
||||
builtinFunctionsAsmGen.translateFunctioncallStatement(stmt, builtinFunc)
|
||||
} else {
|
||||
functioncallAsmGen.translateFunctionCall(stmt)
|
||||
// discard any results from the stack:
|
||||
val sub = stmt.target.targetSubroutine(program.namespace)!!
|
||||
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")
|
||||
}
|
||||
}
|
||||
functioncallAsmGen.translateFunctionCallStatement(stmt)
|
||||
}
|
||||
}
|
||||
is Assignment -> assignmentAsmGen.translate(stmt)
|
||||
@ -662,101 +659,75 @@ $save .byte 0
|
||||
register: CpuRegister,
|
||||
addOneExtra: Boolean=false) {
|
||||
val reg = register.toString().toLowerCase()
|
||||
val index = expr.arrayspec.index
|
||||
if(index is NumericLiteralValue) {
|
||||
val indexValue = index.number.toInt() * elementDt.memorySize() + if(addOneExtra) 1 else 0
|
||||
val indexnum = expr.indexer.constIndex()
|
||||
if(indexnum!=null) {
|
||||
val indexValue = indexnum * elementDt.memorySize() + if(addOneExtra) 1 else 0
|
||||
out(" ld$reg #$indexValue")
|
||||
return
|
||||
}
|
||||
|
||||
val indexName = asmVariableName(expr.indexer.indexVar!!)
|
||||
if(addOneExtra) {
|
||||
// add 1 to the result
|
||||
if (index is IdentifierReference) {
|
||||
val indexName = asmVariableName(index)
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> {
|
||||
out(" ldy $indexName | iny")
|
||||
when(register) {
|
||||
CpuRegister.A -> out(" tya")
|
||||
CpuRegister.X -> out(" tyx")
|
||||
CpuRegister.Y -> {}
|
||||
}
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> {
|
||||
out(" ldy $indexName | iny")
|
||||
when(register) {
|
||||
CpuRegister.A -> out(" tya")
|
||||
CpuRegister.X -> out(" tyx")
|
||||
CpuRegister.Y -> {}
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
out(" lda $indexName | sec | rol a")
|
||||
when(register) {
|
||||
CpuRegister.A -> {}
|
||||
CpuRegister.X -> out(" tax")
|
||||
CpuRegister.Y -> out(" tay")
|
||||
}
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
require(DataType.FLOAT.memorySize()==5)
|
||||
out("""
|
||||
lda $indexName
|
||||
asl a
|
||||
asl a
|
||||
sec
|
||||
adc $indexName""")
|
||||
when(register) {
|
||||
CpuRegister.A -> {}
|
||||
CpuRegister.X -> out(" tax")
|
||||
CpuRegister.Y -> out(" tay")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
}
|
||||
else {
|
||||
expressionsAsmGen.translateExpression(index)
|
||||
out("""
|
||||
inc P8ESTACK_LO,x
|
||||
bne +
|
||||
inc P8ESTACK_HI,x
|
||||
+""")
|
||||
when(register) {
|
||||
CpuRegister.A -> out(" inx | lda P8ESTACK_LO,x")
|
||||
CpuRegister.X -> out(" inx | lda P8ESTACK_LO,x | tax")
|
||||
CpuRegister.Y -> out(" inx | ldy P8ESTACK_LO,x")
|
||||
in WordDatatypes -> {
|
||||
out(" lda $indexName | sec | rol a")
|
||||
when(register) {
|
||||
CpuRegister.A -> {}
|
||||
CpuRegister.X -> out(" tax")
|
||||
CpuRegister.Y -> out(" tay")
|
||||
}
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
require(DataType.FLOAT.memorySize()==5)
|
||||
out("""
|
||||
lda $indexName
|
||||
asl a
|
||||
asl a
|
||||
sec
|
||||
adc $indexName""")
|
||||
when(register) {
|
||||
CpuRegister.A -> {}
|
||||
CpuRegister.X -> out(" tax")
|
||||
CpuRegister.Y -> out(" tay")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
} else {
|
||||
if (index is IdentifierReference) {
|
||||
val indexName = asmVariableName(index)
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> out(" ld$reg $indexName")
|
||||
in WordDatatypes -> {
|
||||
out(" lda $indexName | asl a")
|
||||
when(register) {
|
||||
CpuRegister.A -> {}
|
||||
CpuRegister.X -> out(" tax")
|
||||
CpuRegister.Y -> out(" tay")
|
||||
}
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> out(" ld$reg $indexName")
|
||||
in WordDatatypes -> {
|
||||
out(" lda $indexName | asl a")
|
||||
when(register) {
|
||||
CpuRegister.A -> {}
|
||||
CpuRegister.X -> out(" tax")
|
||||
CpuRegister.Y -> out(" tay")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
require(DataType.FLOAT.memorySize()==5)
|
||||
out("""
|
||||
lda $indexName
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc $indexName""")
|
||||
when(register) {
|
||||
CpuRegister.A -> {}
|
||||
CpuRegister.X -> out(" tax")
|
||||
CpuRegister.Y -> out(" tay")
|
||||
}
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
require(DataType.FLOAT.memorySize()==5)
|
||||
out("""
|
||||
lda $indexName
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc $indexName""")
|
||||
when(register) {
|
||||
CpuRegister.A -> {}
|
||||
CpuRegister.X -> out(" tax")
|
||||
CpuRegister.Y -> out(" tay")
|
||||
}
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
}
|
||||
else {
|
||||
expressionsAsmGen.translateExpression(index)
|
||||
when(register) {
|
||||
CpuRegister.A -> out(" inx | lda P8ESTACK_LO,x")
|
||||
CpuRegister.X -> out(" inx | lda P8ESTACK_LO,x | tax")
|
||||
CpuRegister.Y -> out(" inx | ldy P8ESTACK_LO,x")
|
||||
}
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -764,15 +735,28 @@ $save .byte 0
|
||||
internal fun translateExpression(expression: Expression) =
|
||||
expressionsAsmGen.translateExpression(expression)
|
||||
|
||||
internal fun translateFunctioncallExpression(functionCall: FunctionCall, signature: FSignature) =
|
||||
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature)
|
||||
internal fun translateExpression(indexer: ArrayIndex) =
|
||||
expressionsAsmGen.translateExpression(indexer)
|
||||
|
||||
internal fun translateFunctionCall(functionCall: FunctionCall) =
|
||||
functioncallAsmGen.translateFunctionCall(functionCall)
|
||||
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 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) {
|
||||
out("")
|
||||
outputSourceLine(sub)
|
||||
@ -790,9 +774,37 @@ $save .byte 0
|
||||
out("${sub.name}\t.proc")
|
||||
zeropagevars2asm(sub.statements)
|
||||
memdefs2asm(sub.statements)
|
||||
|
||||
// the main.start subroutine is the program's entrypoint and should perform some initialization logic
|
||||
if(sub.name=="start" && sub.definingBlock().name=="main") {
|
||||
out("; program startup initialization")
|
||||
out(" cld")
|
||||
program.allBlocks().forEach {
|
||||
if(it.statements.filterIsInstance<VarDecl>().any { vd->vd.value!=null && vd.type==VarDeclType.VAR && vd.datatype in NumericDatatypes})
|
||||
out(" jsr ${it.name}.prog8_init_vars")
|
||||
}
|
||||
out("""
|
||||
tsx
|
||||
stx prog8_lib.orig_stackpointer ; required for func_exit
|
||||
ldx #255 ; init estack ptr
|
||||
clv
|
||||
clc""")
|
||||
}
|
||||
|
||||
out("; statements")
|
||||
sub.statements.forEach{ translate(it) }
|
||||
out("; variables")
|
||||
out("; register saves")
|
||||
if(sub.asmGenInfo.usedRegsaveA)
|
||||
out("_prog8_regsaveA .byte 0")
|
||||
if(sub.asmGenInfo.usedRegsaveX)
|
||||
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")
|
||||
}
|
||||
@ -803,12 +815,12 @@ $save .byte 0
|
||||
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) {
|
||||
@ -824,47 +836,32 @@ $save .byte 0
|
||||
}
|
||||
|
||||
private fun translate(stmt: IfStatement) {
|
||||
when {
|
||||
stmt.elsepart.containsNoCodeNorVars() -> {
|
||||
// empty else
|
||||
expressionsAsmGen.translateExpression(stmt.condition)
|
||||
translateTestStack(stmt.condition.inferType(program).typeOrElse(DataType.STRUCT))
|
||||
val endLabel = makeLabel("if_end")
|
||||
out(" beq $endLabel")
|
||||
translate(stmt.truepart)
|
||||
out(endLabel)
|
||||
}
|
||||
stmt.truepart.containsNoCodeNorVars() -> {
|
||||
// empty true part
|
||||
expressionsAsmGen.translateExpression(stmt.condition)
|
||||
translateTestStack(stmt.condition.inferType(program).typeOrElse(DataType.STRUCT))
|
||||
val endLabel = makeLabel("if_end")
|
||||
out(" bne $endLabel")
|
||||
translate(stmt.elsepart)
|
||||
out(endLabel)
|
||||
}
|
||||
else -> {
|
||||
expressionsAsmGen.translateExpression(stmt.condition)
|
||||
translateTestStack(stmt.condition.inferType(program).typeOrElse(DataType.STRUCT))
|
||||
val elseLabel = makeLabel("if_else")
|
||||
val endLabel = makeLabel("if_end")
|
||||
out(" beq $elseLabel")
|
||||
translate(stmt.truepart)
|
||||
out(" jmp $endLabel")
|
||||
out(elseLabel)
|
||||
translate(stmt.elsepart)
|
||||
out(endLabel)
|
||||
}
|
||||
checkBooleanExpression(stmt.condition) // we require the condition to be of the form 'x <comparison> <value>'
|
||||
val booleanCondition = stmt.condition as BinaryExpression
|
||||
|
||||
if (stmt.elsepart.containsNoCodeNorVars()) {
|
||||
// empty else
|
||||
val endLabel = makeLabel("if_end")
|
||||
expressionsAsmGen.translateComparisonExpressionWithJumpIfFalse(booleanCondition, endLabel)
|
||||
translate(stmt.truepart)
|
||||
out(endLabel)
|
||||
}
|
||||
else {
|
||||
// both true and else parts
|
||||
val elseLabel = makeLabel("if_else")
|
||||
val endLabel = makeLabel("if_end")
|
||||
expressionsAsmGen.translateComparisonExpressionWithJumpIfFalse(booleanCondition, elseLabel)
|
||||
translate(stmt.truepart)
|
||||
out(" jmp $endLabel")
|
||||
out(elseLabel)
|
||||
translate(stmt.elsepart)
|
||||
out(endLabel)
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateTestStack(dataType: DataType) {
|
||||
when(dataType) {
|
||||
in ByteDatatypes -> out(" inx | lda P8ESTACK_LO,x")
|
||||
in WordDatatypes -> out(" inx | lda P8ESTACK_LO,x | ora P8ESTACK_HI,x")
|
||||
DataType.FLOAT -> throw AssemblyError("conditional value should be an integer (boolean)")
|
||||
else -> throw AssemblyError("non-numerical dt")
|
||||
}
|
||||
private fun checkBooleanExpression(condition: Expression) {
|
||||
if(condition !is BinaryExpression || condition.operator !in comparisonOperators)
|
||||
throw AssemblyError("expected boolean expression $condition")
|
||||
}
|
||||
|
||||
private fun translate(stmt: RepeatLoop) {
|
||||
@ -912,15 +909,16 @@ $save .byte 0
|
||||
}
|
||||
}
|
||||
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")
|
||||
@ -932,6 +930,8 @@ $save .byte 0
|
||||
}
|
||||
|
||||
private fun repeatWordCountInAY(constIterations: Int?, repeatLabel: String, endLabel: String, body: AnonymousScope) {
|
||||
if(constIterations==0)
|
||||
return
|
||||
// note: A/Y must have been loaded with the number of iterations already!
|
||||
val counterVar = makeLabel("repeatcounter")
|
||||
out("""
|
||||
@ -961,8 +961,12 @@ $counterVar .word 0""")
|
||||
}
|
||||
|
||||
private fun repeatByteCountInA(constIterations: Int?, repeatLabel: String, endLabel: String, body: AnonymousScope) {
|
||||
if(constIterations==0)
|
||||
return
|
||||
// note: A must have been loaded with the number of iterations already!
|
||||
val counterVar = makeLabel("repeatcounter")
|
||||
if(constIterations==null)
|
||||
out(" beq $endLabel")
|
||||
out("""
|
||||
sta $counterVar
|
||||
$repeatLabel""")
|
||||
@ -983,25 +987,13 @@ $counterVar .byte 0""")
|
||||
}
|
||||
|
||||
private fun translate(stmt: WhileLoop) {
|
||||
checkBooleanExpression(stmt.condition) // we require the condition to be of the form 'x <comparison> <value>'
|
||||
val booleanCondition = stmt.condition as BinaryExpression
|
||||
val whileLabel = makeLabel("while")
|
||||
val endLabel = makeLabel("whileend")
|
||||
loopEndLabels.push(endLabel)
|
||||
out(whileLabel)
|
||||
expressionsAsmGen.translateExpression(stmt.condition)
|
||||
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 | beq $endLabel")
|
||||
} else {
|
||||
out("""
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
bne +
|
||||
lda P8ESTACK_HI,x
|
||||
beq $endLabel
|
||||
+ """)
|
||||
}
|
||||
expressionsAsmGen.translateComparisonExpressionWithJumpIfFalse(booleanCondition, endLabel)
|
||||
translate(stmt.body)
|
||||
out(" jmp $whileLabel")
|
||||
out(endLabel)
|
||||
@ -1009,41 +1001,29 @@ $counterVar .byte 0""")
|
||||
}
|
||||
|
||||
private fun translate(stmt: UntilLoop) {
|
||||
checkBooleanExpression(stmt.condition) // we require the condition to be of the form 'x <comparison> <value>'
|
||||
val booleanCondition = stmt.condition as BinaryExpression
|
||||
val repeatLabel = makeLabel("repeat")
|
||||
val endLabel = makeLabel("repeatend")
|
||||
loopEndLabels.push(endLabel)
|
||||
out(repeatLabel)
|
||||
translate(stmt.body)
|
||||
expressionsAsmGen.translateExpression(stmt.untilCondition)
|
||||
val conditionDt = stmt.untilCondition.inferType(program)
|
||||
if(!conditionDt.isKnown)
|
||||
throw AssemblyError("unknown condition dt")
|
||||
if(conditionDt.typeOrElse(DataType.BYTE) in ByteDatatypes) {
|
||||
out(" inx | lda P8ESTACK_LO,x | beq $repeatLabel")
|
||||
} else {
|
||||
out("""
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
bne +
|
||||
lda P8ESTACK_HI,x
|
||||
beq $repeatLabel
|
||||
+ """)
|
||||
}
|
||||
expressionsAsmGen.translateComparisonExpressionWithJumpIfFalse(booleanCondition, repeatLabel)
|
||||
out(endLabel)
|
||||
loopEndLabels.pop()
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -1051,7 +1031,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) {
|
||||
@ -1068,6 +1048,7 @@ $counterVar .byte 0""")
|
||||
}
|
||||
}
|
||||
}
|
||||
out(" jmp $endLabel")
|
||||
for(choiceBlock in choiceBlocks) {
|
||||
out(choiceBlock.first)
|
||||
translate(choiceBlock.second)
|
||||
@ -1091,26 +1072,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)
|
||||
}
|
||||
}
|
||||
@ -1156,7 +1159,9 @@ $counterVar .byte 0""")
|
||||
"%breakpoint" -> {
|
||||
val label = "_prog8_breakpoint_${breakpointLabels.size+1}"
|
||||
breakpointLabels.add(label)
|
||||
out("$label\tnop")
|
||||
out("""
|
||||
nop
|
||||
$label nop""")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1182,7 +1187,28 @@ $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 IntegerDatatypes -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(returnvalue, returnReg.registerOrPair)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
// return the float value via FAC1
|
||||
assignExpressionToRegister(returnvalue, RegisterOrPair.FAC1)
|
||||
}
|
||||
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")
|
||||
}
|
||||
|
||||
@ -1190,4 +1216,48 @@ $counterVar .byte 0""")
|
||||
val assembly = asm.assembly.trimEnd().trimStart('\n')
|
||||
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 on stack
|
||||
when(valueDt) {
|
||||
DataType.UBYTE -> {
|
||||
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 var to a full word in that variable
|
||||
when(valueDt) {
|
||||
DataType.UBYTE -> {
|
||||
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
|
||||
out(" stz $asmvar+1")
|
||||
else
|
||||
out(" lda #0 | sta $asmvar+1")
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
out("""
|
||||
lda $asmvar
|
||||
ora #$7f
|
||||
bmi +
|
||||
lda #0
|
||||
+ sta $asmvar+1""")
|
||||
}
|
||||
else -> throw AssemblyError("need byte type")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>()
|
||||
@ -180,6 +180,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
||||
|
||||
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can be eliminated
|
||||
// TODO this is not true if X is not a regular RAM memory address (but instead mapped I/O or ROM)
|
||||
val mods = mutableListOf<Modification>()
|
||||
for (pair in linesByFour) {
|
||||
val first = pair[0].value.trimStart()
|
||||
@ -195,8 +196,8 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>)
|
||||
(first.startsWith("sty ") && second.startsWith("ldy ")) ||
|
||||
(first.startsWith("stx ") && second.startsWith("ldx "))
|
||||
) {
|
||||
val firstLoc = first.substring(4)
|
||||
val secondLoc = second.substring(4)
|
||||
val firstLoc = first.substring(4).trimStart()
|
||||
val secondLoc = second.substring(4).trimStart()
|
||||
if (firstLoc == secondLoc) {
|
||||
mods.add(Modification(pair[1].index, true, null))
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
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), variable=stmt.loopVar)
|
||||
val src = AsmAssignSource.fromAstSource(range.from, program).adjustDataTypeToTarget(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())
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
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.*
|
||||
@ -13,13 +14,23 @@ import prog8.compiler.target.c64.codegen.assignment.*
|
||||
|
||||
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||
|
||||
internal fun translateFunctionCall(stmt: IFunctionCall) {
|
||||
internal fun translateFunctionCallStatement(stmt: IFunctionCall) {
|
||||
val sub = stmt.target.targetSubroutine(program.namespace)!!
|
||||
val preserveStatusRegisterAfterCall = sub.shouldPreserveStatusRegisterAfterCall()
|
||||
translateFunctionCall(stmt, preserveStatusRegisterAfterCall)
|
||||
// functioncalls no longer return results on the stack, so simply ignore the results in the registers
|
||||
if(preserveStatusRegisterAfterCall)
|
||||
asmgen.out(" plp") // restore status flags from call
|
||||
}
|
||||
|
||||
|
||||
internal fun translateFunctionCall(stmt: IFunctionCall, preserveStatusRegisterAfterCall: Boolean) {
|
||||
// output the code to setup the parameters and perform the actual call
|
||||
// does NOT output the code to deal with the result values!
|
||||
val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
||||
val saveX = CpuRegister.X in sub.asmClobbers || sub.regXasResult() || sub.regXasParam()
|
||||
val saveX = sub.shouldSaveX()
|
||||
if(saveX)
|
||||
asmgen.saveRegister(CpuRegister.X)
|
||||
asmgen.saveRegister(CpuRegister.X, preserveStatusRegisterAfterCall, (stmt as Node).definingSubroutine()!!)
|
||||
|
||||
val subName = asmgen.asmSymbolName(stmt.target)
|
||||
if(stmt.args.isNotEmpty()) {
|
||||
@ -32,7 +43,7 @@ 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.
|
||||
@ -57,8 +68,14 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
asmgen.out(" jsr $subName")
|
||||
|
||||
if(preserveStatusRegisterAfterCall) {
|
||||
asmgen.out(" php") // save status flags from call
|
||||
// note: the containing statement (such as the FunctionCallStatement or the Assignment or the Expression)
|
||||
// must take care of popping this value again at the end!
|
||||
}
|
||||
|
||||
if(saveX)
|
||||
asmgen.restoreRegister(CpuRegister.X)
|
||||
asmgen.restoreRegister(CpuRegister.X, preserveStatusRegisterAfterCall)
|
||||
}
|
||||
|
||||
private fun registerArgsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) {
|
||||
@ -80,7 +97,6 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
|
||||
for(argi in stmt.args.zip(sub.asmParameterRegisters).withIndex()) {
|
||||
when {
|
||||
argi.value.second.stack -> TODO("asmsub @stack parameter")
|
||||
argi.value.second.statusflag == Statusflag.Pc -> {
|
||||
require(argForCarry == null)
|
||||
argForCarry = argi
|
||||
@ -143,25 +159,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 scopedParamVar = (sub.scopedname+"."+parameter.value.name).split(".")
|
||||
val identifier = IdentifierReference(scopedParamVar, sub.position)
|
||||
identifier.linkParents(value.parent)
|
||||
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, parameter.value.type, variable = identifier)
|
||||
val source = AsmAssignSource.fromAstSource(value, program).adjustDataTypeToTarget(tgt)
|
||||
val asgn = AsmAssignment(source, tgt, false, Position.DUMMY)
|
||||
asmgen.translateNormalAssignment(asgn)
|
||||
val varName = asmgen.asmVariableName(sub.scopedname+"."+parameter.value.name)
|
||||
asmgen.assignExpressionToVariable(value, varName, parameter.value.type, sub)
|
||||
}
|
||||
|
||||
private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
|
||||
// pass argument via a register parameter
|
||||
val valueIDt = value.inferType(program)
|
||||
if(!valueIDt.isKnown)
|
||||
throw AssemblyError("arg type unknown")
|
||||
throw AssemblyError("unknown dt")
|
||||
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
|
||||
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
|
||||
throw AssemblyError("argument type incompatible")
|
||||
@ -169,14 +180,15 @@ 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)
|
||||
}
|
||||
statusflag!=null -> {
|
||||
if(requiredDt!=valueDt)
|
||||
throw AssemblyError("for statusflag, byte value is required")
|
||||
if (statusflag == Statusflag.Pc) {
|
||||
// this param needs to be set last, right before the jsr
|
||||
// for now, this is already enforced on the subroutine definition by the Ast Checker
|
||||
@ -198,17 +210,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
|
||||
+""")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -216,15 +224,28 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
else -> {
|
||||
// via register or register pair
|
||||
val target = AsmAssignTarget.fromRegisters(register!!, program, asmgen)
|
||||
val src = if(valueDt in PassByReferenceDatatypes) {
|
||||
val addr = AddressOf(value as IdentifierReference, Position.DUMMY)
|
||||
AsmAssignSource.fromAstSource(addr, program).adjustDataTypeToTarget(target)
|
||||
} else {
|
||||
AsmAssignSource.fromAstSource(value, program).adjustDataTypeToTarget(target)
|
||||
register!!
|
||||
if(requiredDt largerThan valueDt) {
|
||||
// we need to sign extend the source, do this via temporary word variable
|
||||
val scratchVar = asmgen.asmVariableName("P8ZP_SCRATCH_W1")
|
||||
asmgen.assignExpressionToVariable(value, scratchVar, DataType.UBYTE, sub)
|
||||
asmgen.signExtendVariableLsb(scratchVar, valueDt)
|
||||
asmgen.assignVariableToRegister(scratchVar, register)
|
||||
}
|
||||
else {
|
||||
val target = AsmAssignTarget.fromRegisters(register, sub, program, asmgen)
|
||||
val src = if(valueDt in PassByReferenceDatatypes) {
|
||||
if(value is IdentifierReference) {
|
||||
val addr = AddressOf(value, Position.DUMMY)
|
||||
AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target)
|
||||
} else {
|
||||
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
|
||||
}
|
||||
} else {
|
||||
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
|
||||
}
|
||||
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, Position.DUMMY))
|
||||
}
|
||||
|
||||
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, Position.DUMMY))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,10 +15,11 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
val targetIdent = stmt.target.identifier
|
||||
val targetMemory = stmt.target.memoryAddress
|
||||
val targetArrayIdx = stmt.target.arrayindexed
|
||||
val scope = stmt.definingSubroutine()
|
||||
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)
|
||||
@ -53,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
|
||||
@ -69,67 +64,64 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
}
|
||||
targetArrayIdx!=null -> {
|
||||
val index = targetArrayIdx.arrayspec.index
|
||||
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.identifier)
|
||||
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.arrayvar)
|
||||
val elementDt = targetArrayIdx.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
when(index) {
|
||||
is NumericLiteralValue -> {
|
||||
val indexValue = index.number.toInt() * elementDt.memorySize()
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue")
|
||||
in WordDatatypes -> {
|
||||
if(incr)
|
||||
asmgen.out(" inc $asmArrayvarname+$indexValue | bne + | inc $asmArrayvarname+$indexValue+1 |+")
|
||||
else
|
||||
asmgen.out("""
|
||||
lda $asmArrayvarname+$indexValue
|
||||
bne +
|
||||
dec $asmArrayvarname+$indexValue+1
|
||||
if(targetArrayIdx.indexer.indexNum!=null) {
|
||||
val indexValue = targetArrayIdx.indexer.constIndex()!! * elementDt.memorySize()
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue")
|
||||
in WordDatatypes -> {
|
||||
if(incr)
|
||||
asmgen.out(" inc $asmArrayvarname+$indexValue | bne + | inc $asmArrayvarname+$indexValue+1 |+")
|
||||
else
|
||||
asmgen.out("""
|
||||
lda $asmArrayvarname+$indexValue
|
||||
bne +
|
||||
dec $asmArrayvarname+$indexValue+1
|
||||
+ dec $asmArrayvarname+$indexValue
|
||||
""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out(" lda #<$asmArrayvarname+$indexValue | ldy #>$asmArrayvarname+$indexValue")
|
||||
asmgen.out(if(incr) " jsr floats.inc_var_f" else " jsr floats.dec_var_f")
|
||||
}
|
||||
else -> throw AssemblyError("need numeric type")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.A)
|
||||
asmgen.saveRegister(CpuRegister.X)
|
||||
asmgen.out(" tax")
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.out(if(incr) " inc $asmArrayvarname,x" else " dec $asmArrayvarname,x")
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
if(incr)
|
||||
asmgen.out(" inc $asmArrayvarname,x | bne + | inc $asmArrayvarname+1,x |+")
|
||||
else
|
||||
asmgen.out("""
|
||||
lda $asmArrayvarname,x
|
||||
bne +
|
||||
dec $asmArrayvarname+1,x
|
||||
+ dec $asmArrayvarname
|
||||
""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out("""
|
||||
ldy #>$asmArrayvarname
|
||||
clc
|
||||
adc #<$asmArrayvarname
|
||||
bcc +
|
||||
iny
|
||||
+ jsr floats.inc_var_f""")
|
||||
}
|
||||
else -> throw AssemblyError("weird array elt dt")
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out(" lda #<$asmArrayvarname+$indexValue | ldy #>$asmArrayvarname+$indexValue")
|
||||
asmgen.out(if(incr) " jsr floats.inc_var_f" else " jsr floats.dec_var_f")
|
||||
}
|
||||
asmgen.restoreRegister(CpuRegister.X)
|
||||
else -> throw AssemblyError("need numeric type")
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.A)
|
||||
asmgen.saveRegister(CpuRegister.X, false, scope!!)
|
||||
asmgen.out(" tax")
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.out(if(incr) " inc $asmArrayvarname,x" else " dec $asmArrayvarname,x")
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
if(incr)
|
||||
asmgen.out(" inc $asmArrayvarname,x | bne + | inc $asmArrayvarname+1,x |+")
|
||||
else
|
||||
asmgen.out("""
|
||||
lda $asmArrayvarname,x
|
||||
bne +
|
||||
dec $asmArrayvarname+1,x
|
||||
+ dec $asmArrayvarname
|
||||
""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out("""
|
||||
ldy #>$asmArrayvarname
|
||||
clc
|
||||
adc #<$asmArrayvarname
|
||||
bcc +
|
||||
iny
|
||||
+ jsr floats.inc_var_f""")
|
||||
}
|
||||
else -> throw AssemblyError("weird array elt dt")
|
||||
}
|
||||
asmgen.restoreRegister(CpuRegister.X, false)
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird target type ${stmt.target}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +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.*
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.c64.codegen.AsmGen
|
||||
|
||||
@ -29,10 +27,11 @@ internal enum class SourceStorageKind {
|
||||
}
|
||||
|
||||
internal class AsmAssignTarget(val kind: TargetStorageKind,
|
||||
program: Program,
|
||||
asmgen: AsmGen,
|
||||
private val program: Program,
|
||||
private val asmgen: AsmGen,
|
||||
val datatype: DataType,
|
||||
val variable: IdentifierReference? = null,
|
||||
val scope: Subroutine?,
|
||||
private val variableAsmName: String? = null,
|
||||
val array: ArrayIndexedExpression? = null,
|
||||
val memory: DirectMemoryWrite? = null,
|
||||
val register: RegisterOrPair? = null,
|
||||
@ -40,51 +39,53 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
|
||||
)
|
||||
{
|
||||
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0}
|
||||
val constArrayIndexValue by lazy { array?.arrayspec?.constIndex() }
|
||||
val vardecl by lazy { variable?.targetVarDecl(program.namespace)!! }
|
||||
val asmVarname by lazy {
|
||||
if(variable!=null)
|
||||
asmgen.asmVariableName(variable)
|
||||
val constArrayIndexValue by lazy { array?.indexer?.constIndex() }
|
||||
val asmVarname: String
|
||||
get() = if(array==null)
|
||||
variableAsmName!!
|
||||
else
|
||||
asmgen.asmVariableName(array!!.identifier)
|
||||
}
|
||||
asmgen.asmVariableName(array.arrayvar)
|
||||
|
||||
lateinit var origAssign: AsmAssignment
|
||||
|
||||
init {
|
||||
if(variable!=null && vardecl.type == VarDeclType.CONST)
|
||||
throw AssemblyError("can't assign to a constant")
|
||||
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, variable=identifier, origAstTarget = this)
|
||||
arrayindexed != null -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, array = arrayindexed, origAstTarget = this)
|
||||
memoryAddress != null -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, dt, memory = memoryAddress, origAstTarget = this)
|
||||
identifier != null -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, assign.definingSubroutine(), variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
|
||||
arrayindexed != null -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, assign.definingSubroutine(), array = arrayindexed, origAstTarget = this)
|
||||
memoryAddress != null -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, dt, assign.definingSubroutine(), memory = memoryAddress, origAstTarget = this)
|
||||
else -> throw AssemblyError("weird target")
|
||||
}
|
||||
}
|
||||
|
||||
fun fromRegisters(registers: RegisterOrPair, program: Program, asmgen: AsmGen): AsmAssignTarget =
|
||||
fun fromRegisters(registers: RegisterOrPair, scope: Subroutine?, program: Program, asmgen: AsmGen): AsmAssignTarget =
|
||||
when(registers) {
|
||||
RegisterOrPair.A,
|
||||
RegisterOrPair.X,
|
||||
RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UBYTE, register = registers)
|
||||
RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UBYTE, scope, register = registers)
|
||||
RegisterOrPair.AX,
|
||||
RegisterOrPair.AY,
|
||||
RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UWORD, register = registers)
|
||||
RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UWORD, scope, register = registers)
|
||||
RegisterOrPair.FAC1,
|
||||
RegisterOrPair.FAC2 -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.FLOAT, scope, register = registers)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
private val program: Program,
|
||||
private val asmgen: AsmGen,
|
||||
val datatype: DataType,
|
||||
val variable: IdentifierReference? = null,
|
||||
private val variableAsmName: String? = null,
|
||||
val array: ArrayIndexedExpression? = null,
|
||||
val memory: DirectMemoryRead? = null,
|
||||
val register: CpuRegister? = null,
|
||||
@ -93,53 +94,78 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
)
|
||||
{
|
||||
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0}
|
||||
val constArrayIndexValue by lazy { array?.arrayspec?.constIndex() }
|
||||
val vardecl by lazy { variable?.targetVarDecl(program.namespace)!! }
|
||||
val constArrayIndexValue by lazy { array?.indexer?.constIndex() }
|
||||
|
||||
val asmVarname: String
|
||||
get() = if(array==null)
|
||||
variableAsmName!!
|
||||
else
|
||||
asmgen.asmVariableName(array.arrayvar)
|
||||
|
||||
companion object {
|
||||
fun fromAstSource(value: Expression, program: Program): AsmAssignSource {
|
||||
fun fromAstSource(indexer: ArrayIndex, program: Program, asmgen: AsmGen): AsmAssignSource {
|
||||
return when {
|
||||
indexer.indexNum!=null -> fromAstSource(indexer.indexNum!!, program, asmgen)
|
||||
indexer.indexVar!=null -> fromAstSource(indexer.indexVar!!, program, asmgen)
|
||||
else -> throw AssemblyError("weird indexer")
|
||||
}
|
||||
}
|
||||
|
||||
fun fromAstSource(value: Expression, program: Program, asmgen: AsmGen): AsmAssignSource {
|
||||
val cv = value.constValue(program)
|
||||
if(cv!=null)
|
||||
return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, cv.type, number = cv)
|
||||
return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, cv.type, number = cv)
|
||||
|
||||
return when(value) {
|
||||
is NumericLiteralValue -> AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, value.type, number = cv)
|
||||
is NumericLiteralValue -> AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, value.type, number = cv)
|
||||
is StringLiteralValue -> throw AssemblyError("string literal value should not occur anymore for asm generation")
|
||||
is ArrayLiteralValue -> throw AssemblyError("array literal value should not occur anymore for asm generation")
|
||||
is IdentifierReference -> {
|
||||
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
AsmAssignSource(SourceStorageKind.VARIABLE, program, dt, variable = value)
|
||||
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, dt, variableAsmName = asmgen.asmVariableName(value))
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
AsmAssignSource(SourceStorageKind.MEMORY, program, DataType.UBYTE, memory = value)
|
||||
AsmAssignSource(SourceStorageKind.MEMORY, program, asmgen, DataType.UBYTE, memory = value)
|
||||
}
|
||||
is ArrayIndexedExpression -> {
|
||||
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
AsmAssignSource(SourceStorageKind.ARRAY, program, dt, array = value)
|
||||
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value)
|
||||
}
|
||||
is FunctionCall -> {
|
||||
when (val sub = value.target.targetStatement(program.namespace)) {
|
||||
is Subroutine -> {
|
||||
val returnType = sub.returntypes.zip(sub.asmReturnvaluesRegisters).firstOrNull { rr -> rr.second.registerOrPair != null }?.first
|
||||
?: throw AssemblyError("can't translate zero return values in assignment")
|
||||
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)
|
||||
}
|
||||
is BuiltinFunctionStatementPlaceholder -> {
|
||||
val returnType = value.inferType(program)
|
||||
if(!returnType.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.typeOrElse(DataType.STRUCT), expression = value)
|
||||
}
|
||||
else -> {
|
||||
throw AssemblyError("weird call")
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, dt, expression = value)
|
||||
val dt = value.inferType(program)
|
||||
if(!dt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt.typeOrElse(DataType.STRUCT), expression = value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getAstValue(): Expression = when(kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> number!!
|
||||
SourceStorageKind.VARIABLE -> variable!!
|
||||
SourceStorageKind.ARRAY -> array!!
|
||||
SourceStorageKind.MEMORY -> memory!!
|
||||
SourceStorageKind.EXPRESSION -> expression!!
|
||||
SourceStorageKind.REGISTER -> throw AssemblyError("cannot get a register source as Ast node")
|
||||
SourceStorageKind.STACK -> throw AssemblyError("cannot get a stack source as Ast node")
|
||||
}
|
||||
|
||||
fun withAdjustedDt(newType: DataType) =
|
||||
AsmAssignSource(kind, program, newType, variable, array, memory, register, number, expression)
|
||||
|
||||
fun adjustDataTypeToTarget(target: AsmAssignTarget): AsmAssignSource {
|
||||
fun adjustSignedUnsigned(target: AsmAssignTarget): AsmAssignSource {
|
||||
// allow some signed/unsigned relaxations
|
||||
|
||||
fun withAdjustedDt(newType: DataType) =
|
||||
AsmAssignSource(kind, program, asmgen, newType, variableAsmName, array, memory, register, number, expression)
|
||||
|
||||
if(target.datatype!=datatype) {
|
||||
if(target.datatype in ByteDatatypes && datatype in ByteDatatypes) {
|
||||
return withAdjustedDt(target.datatype)
|
||||
@ -160,6 +186,9 @@ internal class AsmAssignment(val source: AsmAssignSource,
|
||||
|
||||
init {
|
||||
if(target.register !in setOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY))
|
||||
require(source.datatype.memorySize() == target.datatype.memorySize()) { "source and target datatype must be same storage class" }
|
||||
require(source.datatype != DataType.STRUCT) { "must not be placeholder datatype" }
|
||||
require(source.datatype.memorySize() <= target.datatype.memorySize()) {
|
||||
"source storage size must be less or equal to target datatype storage size"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -38,8 +38,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 {
|
||||
@ -52,6 +51,8 @@ internal object CX16MachineDefinition: IMachineDefinition {
|
||||
}
|
||||
}
|
||||
|
||||
override fun isRegularRAMaddress(address: Int): Boolean = address < 0x9f00 || address in 0xa000..0xbfff
|
||||
|
||||
override fun initializeZeropage(compilerOptions: CompilationOptions) {
|
||||
zeropage = CX16Zeropage(compilerOptions)
|
||||
}
|
||||
@ -73,18 +74,11 @@ 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_REG = 0x7a // temp storage for a register, must be B1+1
|
||||
override val SCRATCH_W1 = 0x7c // temp storage 1 for a word $7c+$7d
|
||||
override val SCRATCH_W2 = 0x7e // temp storage 2 for a word $7e+$7f
|
||||
|
||||
|
||||
override val exitProgramStrategy: ExitProgramStrategy = when (options.zeropage) {
|
||||
ZeropageType.BASICSAFE, ZeropageType.DONTUSE -> ExitProgramStrategy.CLEAN_EXIT
|
||||
ZeropageType.KERNALSAFE, ZeropageType.FULL -> ExitProgramStrategy.SYSTEM_RESET
|
||||
else -> ExitProgramStrategy.SYSTEM_RESET
|
||||
}
|
||||
|
||||
|
||||
init {
|
||||
if (options.floats && options.zeropage !in setOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
|
||||
throw CompilerException("when floats are enabled, zero page type should be 'basicsafe' or 'dontuse'")
|
||||
|
@ -15,95 +15,167 @@ 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),
|
||||
// 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(
|
||||
FSignature("sgn" , true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
|
||||
FSignature("sin" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) },
|
||||
FSignature("sin8" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
|
||||
FSignature("sin8u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
|
||||
FSignature("sin16" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
|
||||
FSignature("sin16u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
|
||||
FSignature("cos" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::cos) },
|
||||
FSignature("cos8" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
|
||||
FSignature("cos8u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
|
||||
FSignature("cos16" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
|
||||
FSignature("cos16u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
|
||||
FSignature("tan" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::tan) },
|
||||
FSignature("atan" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::atan) },
|
||||
FSignature("ln" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::log) },
|
||||
FSignature("log2" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, ::log2) },
|
||||
FSignature("sqrt16" , true, listOf(FParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
|
||||
FSignature("sqrt" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
|
||||
FSignature("rad" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) },
|
||||
FSignature("deg" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
|
||||
FSignature("round" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
|
||||
FSignature("floor" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
|
||||
FSignature("ceil" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
|
||||
FSignature("any" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) },
|
||||
FSignature("all" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
|
||||
FSignature("lsb" , true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 }},
|
||||
FSignature("msb" , true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255}},
|
||||
FSignature("mkword" , true, listOf(FParam("msb", setOf(DataType.UBYTE)), FParam("lsb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
|
||||
FSignature("rnd" , false, emptyList(), DataType.UBYTE),
|
||||
FSignature("rndw" , false, emptyList(), DataType.UWORD),
|
||||
FSignature("rndf" , false, emptyList(), DataType.FLOAT),
|
||||
FSignature("exit" , false, listOf(FParam("returnvalue", setOf(DataType.UBYTE))), null),
|
||||
FSignature("rsave" , false, emptyList(), null),
|
||||
FSignature("rrestore" , false, emptyList(), null),
|
||||
FSignature("set_carry" , false, emptyList(), null),
|
||||
FSignature("clear_carry" , false, emptyList(), null),
|
||||
FSignature("set_irqd" , false, emptyList(), null),
|
||||
FSignature("clear_irqd" , false, emptyList(), null),
|
||||
FSignature("read_flags" , false, emptyList(), DataType.UBYTE),
|
||||
FSignature("progend" , true, emptyList(), DataType.UWORD),
|
||||
FSignature("swap" , false, listOf(FParam("first", NumericDatatypes), FParam("second", NumericDatatypes)), null),
|
||||
FSignature("memcopy" , false, listOf(
|
||||
FParam("from", IterableDatatypes + DataType.UWORD),
|
||||
FParam("to", IterableDatatypes + DataType.UWORD),
|
||||
FParam("numbytes", setOf(DataType.UBYTE))), null),
|
||||
"memset" to FSignature(false, listOf(
|
||||
FParam("numbytes", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
FSignature("memset" , false, listOf(
|
||||
FParam("address", IterableDatatypes + DataType.UWORD),
|
||||
FParam("numbytes", setOf(DataType.UWORD)),
|
||||
FParam("bytevalue", ByteDatatypes)), null),
|
||||
"memsetw" to FSignature(false, listOf(
|
||||
FSignature("memsetw" , false, listOf(
|
||||
FParam("address", IterableDatatypes + DataType.UWORD),
|
||||
FParam("numwords", setOf(DataType.UWORD)),
|
||||
FParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null),
|
||||
"strlen" to FSignature(true, listOf(FParam("string", setOf(DataType.STR))), DataType.UBYTE, ::builtinStrlen),
|
||||
"substr" to FSignature(false, listOf(
|
||||
FSignature("strlen" , true, listOf(FParam("string", setOf(DataType.STR))), DataType.UBYTE, ::builtinStrlen),
|
||||
FSignature("strcopy" , false, listOf(FParam("from", IterableDatatypes + DataType.UWORD), FParam("to", IterableDatatypes + DataType.UWORD)), DataType.UBYTE),
|
||||
FSignature("substr" , false, listOf(
|
||||
FParam("source", IterableDatatypes + DataType.UWORD),
|
||||
FParam("target", IterableDatatypes + DataType.UWORD),
|
||||
FParam("start", setOf(DataType.UBYTE)),
|
||||
FParam("length", setOf(DataType.UBYTE))), null),
|
||||
"leftstr" to FSignature(false, listOf(
|
||||
FSignature("leftstr" , false, listOf(
|
||||
FParam("source", IterableDatatypes + DataType.UWORD),
|
||||
FParam("target", IterableDatatypes + DataType.UWORD),
|
||||
FParam("length", setOf(DataType.UBYTE))), null),
|
||||
"rightstr" to FSignature(false, listOf(
|
||||
FSignature("rightstr" , false, listOf(
|
||||
FParam("source", IterableDatatypes + DataType.UWORD),
|
||||
FParam("target", IterableDatatypes + DataType.UWORD),
|
||||
FParam("length", setOf(DataType.UBYTE))), null)
|
||||
FParam("length", setOf(DataType.UBYTE))), null),
|
||||
FSignature("strcmp" , false, listOf(FParam("s1", IterableDatatypes + DataType.UWORD), FParam("s2", IterableDatatypes + DataType.UWORD)), DataType.BYTE, 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() }!!
|
||||
@ -143,17 +215,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())) {
|
||||
@ -283,11 +355,11 @@ private fun builtinStrlen(args: List<Expression>, position: Position, program: P
|
||||
val argument=args[0]
|
||||
if(argument is StringLiteralValue)
|
||||
return NumericLiteralValue.optimalInteger(argument.value.length, argument.position)
|
||||
val vardecl = (argument as IdentifierReference).targetVarDecl(program.namespace)
|
||||
val vardecl = (argument as? IdentifierReference)?.targetVarDecl(program.namespace)
|
||||
if(vardecl!=null) {
|
||||
if(vardecl.datatype!=DataType.STR)
|
||||
if(vardecl.datatype!=DataType.STR && vardecl.datatype!=DataType.UWORD)
|
||||
throw SyntaxError("strlen must have string argument", position)
|
||||
if(vardecl.autogeneratedDontRemove) {
|
||||
if(vardecl.autogeneratedDontRemove && vardecl.value!=null) {
|
||||
return NumericLiteralValue.optimalInteger((vardecl.value as StringLiteralValue).value.length, argument.position)
|
||||
}
|
||||
}
|
||||
|
87
compiler/src/prog8/optimizer/BinExprSplitter.kt
Normal file
87
compiler/src/prog8/optimizer/BinExprSplitter.kt
Normal file
@ -0,0 +1,87 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModification
|
||||
import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.Assignment
|
||||
|
||||
|
||||
internal class BinExprSplitter(private val program: Program) : AstWalker() {
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
// override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
// TODO somehow if we do this, the resulting code for some programs (cube3d.p8) gets hundreds of bytes larger...:
|
||||
// if(decl.type==VarDeclType.VAR ) {
|
||||
// val binExpr = decl.value as? BinaryExpression
|
||||
// if (binExpr != null && binExpr.operator in augmentAssignmentOperators) {
|
||||
// // split into a vardecl with just the left expression, and an aug. assignment with the right expression.
|
||||
// val augExpr = BinaryExpression(IdentifierReference(listOf(decl.name), decl.position), binExpr.operator, binExpr.right, binExpr.position)
|
||||
// val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
|
||||
// val assign = Assignment(target, augExpr, binExpr.position)
|
||||
// println("SPLIT VARDECL $decl")
|
||||
// return listOf(
|
||||
// IAstModification.SetExpression({ decl.value = it }, binExpr.left, decl),
|
||||
// IAstModification.InsertAfter(decl, assign, parent)
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// return noModifications
|
||||
// }
|
||||
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
if (binExpr != null) {
|
||||
/*
|
||||
|
||||
Reduce the complexity of a (binary) expression that has to be evaluated on the eval stack,
|
||||
by attempting to splitting it up into individual simple steps.
|
||||
We only consider a binary expression *one* level deep (so the operands must not be a combined expression)
|
||||
|
||||
|
||||
X = BinExpr X = LeftExpr
|
||||
<operator> followed by
|
||||
/ \ IF 'X' not used X = BinExpr
|
||||
/ \ IN expression ==> <operator>
|
||||
/ \ / \
|
||||
LeftExpr. RightExpr. / \
|
||||
X RightExpr.
|
||||
|
||||
|
||||
*/
|
||||
if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target, program.namespace)) {
|
||||
if(assignment.target isSameAs binExpr.left || assignment.target isSameAs binExpr.right)
|
||||
return noModifications
|
||||
|
||||
if(isSimpleExpression(binExpr.right) && !assignment.isAugmentable) {
|
||||
val firstAssign = Assignment(assignment.target, binExpr.left, binExpr.left.position)
|
||||
val targetExpr = assignment.target.toExpression()
|
||||
val augExpr = BinaryExpression(targetExpr, binExpr.operator, binExpr.right, binExpr.right.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, firstAssign, assignment.definingScope()),
|
||||
IAstModification.ReplaceNode(assignment.value, augExpr, assignment))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO further unraveling of binary expression trees into flat statements.
|
||||
// however this should probably be done in a more generic way to also service
|
||||
// the expressiontrees that are not used in an assignment statement...
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun isSimpleExpression(expr: Expression) =
|
||||
expr is IdentifierReference || expr is NumericLiteralValue || expr is AddressOf || expr is DirectMemoryRead || expr is StringLiteralValue || expr is ArrayLiteralValue || expr is RangeExpr
|
||||
|
||||
private fun isSimpleTarget(target: AssignTarget, namespace: INameScope) =
|
||||
if (target.identifier!=null || target.memoryAddress!=null)
|
||||
target.isInRegularRAM(namespace)
|
||||
else
|
||||
false
|
||||
|
||||
}
|
@ -1,11 +1,10 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.*
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.ast.base.ParentSentinel
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.FunctionCall
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.processing.IAstVisitor
|
||||
@ -25,7 +24,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
|
||||
val imports = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
|
||||
val importedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
|
||||
val calls = mutableMapOf<INameScope, List<Subroutine>>().withDefault { mutableListOf() }
|
||||
val calls = mutableMapOf<Subroutine, List<Subroutine>>().withDefault { mutableListOf() }
|
||||
val calledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
|
||||
|
||||
// TODO add dataflow graph: what statements use what variables - can be used to eliminate unused vars
|
||||
@ -79,8 +78,10 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
importedBy[importedModule] = importedBy.getValue(importedModule).plus(thisModule)
|
||||
} else if (directive.directive == "%asminclude") {
|
||||
val asm = loadAsmIncludeFile(directive.args[0].str!!, thisModule.source)
|
||||
val scope = directive.definingScope()
|
||||
scanAssemblyCode(asm, directive, scope)
|
||||
val scope = directive.definingSubroutine()
|
||||
if(scope!=null) {
|
||||
scanAssemblyCode(asm, directive, scope)
|
||||
}
|
||||
}
|
||||
|
||||
super.visit(directive)
|
||||
@ -167,12 +168,12 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
|
||||
override fun visit(inlineAssembly: InlineAssembly) {
|
||||
// parse inline asm for subroutine calls (jmp, jsr)
|
||||
val scope = inlineAssembly.definingScope()
|
||||
val scope = inlineAssembly.definingSubroutine()
|
||||
scanAssemblyCode(inlineAssembly.assembly, inlineAssembly, scope)
|
||||
super.visit(inlineAssembly)
|
||||
}
|
||||
|
||||
private fun scanAssemblyCode(asm: String, context: Statement, scope: INameScope) {
|
||||
private fun scanAssemblyCode(asm: String, context: Statement, scope: Subroutine?) {
|
||||
asm.lines().forEach { line ->
|
||||
val matches = asmJumpRx.matchEntire(line)
|
||||
if (matches != null) {
|
||||
@ -180,13 +181,15 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
if (jumptarget != null && (jumptarget[0].isLetter() || jumptarget[0] == '_')) {
|
||||
val node = program.namespace.lookup(jumptarget.split('.'), context)
|
||||
if (node is Subroutine) {
|
||||
calls[scope] = calls.getValue(scope).plus(node)
|
||||
if(scope!=null)
|
||||
calls[scope] = calls.getValue(scope).plus(node)
|
||||
calledBy[node] = calledBy.getValue(node).plus(context)
|
||||
} else if (jumptarget.contains('.')) {
|
||||
// maybe only the first part already refers to a subroutine
|
||||
val node2 = program.namespace.lookup(listOf(jumptarget.substringBefore('.')), context)
|
||||
if (node2 is Subroutine) {
|
||||
calls[scope] = calls.getValue(scope).plus(node2)
|
||||
if(scope!=null)
|
||||
calls[scope] = calls.getValue(scope).plus(node2)
|
||||
calledBy[node2] = calledBy.getValue(node2).plus(context)
|
||||
}
|
||||
}
|
||||
@ -199,7 +202,8 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
if (target.contains('.')) {
|
||||
val node = program.namespace.lookup(listOf(target.substringBefore('.')), context)
|
||||
if (node is Subroutine) {
|
||||
calls[scope] = calls.getValue(scope).plus(node)
|
||||
if(scope!=null)
|
||||
calls[scope] = calls.getValue(scope).plus(node)
|
||||
calledBy[node] = calledBy.getValue(node).plus(context)
|
||||
}
|
||||
}
|
||||
@ -208,4 +212,55 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun checkRecursiveCalls(errors: ErrorReporter) {
|
||||
val cycles = recursionCycles()
|
||||
if(cycles.any()) {
|
||||
errors.warn("Program contains recursive subroutine calls. These only works in very specific limited scenarios!", Position.DUMMY)
|
||||
val printed = mutableSetOf<Subroutine>()
|
||||
for(chain in cycles) {
|
||||
if(chain[0] !in printed) {
|
||||
val chainStr = chain.joinToString(" <-- ") { "${it.name} at ${it.position}" }
|
||||
errors.warn("Cycle in (a subroutine call in) $chainStr", Position.DUMMY)
|
||||
printed.add(chain[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun recursionCycles(): List<List<Subroutine>> {
|
||||
val chains = mutableListOf<MutableList<Subroutine>>()
|
||||
for(caller in calls.keys) {
|
||||
val visited = calls.keys.associateWith { false }.toMutableMap()
|
||||
val recStack = calls.keys.associateWith { false }.toMutableMap()
|
||||
val chain = mutableListOf<Subroutine>()
|
||||
if(hasCycle(caller, visited, recStack, chain))
|
||||
chains.add(chain)
|
||||
}
|
||||
return chains
|
||||
}
|
||||
|
||||
private fun hasCycle(sub: Subroutine, visited: MutableMap<Subroutine, Boolean>, recStack: MutableMap<Subroutine, Boolean>, chain: MutableList<Subroutine>): Boolean {
|
||||
// mark current node as visited and add to recursion stack
|
||||
if(recStack[sub]==true)
|
||||
return true
|
||||
if(visited[sub]==true)
|
||||
return false
|
||||
|
||||
// mark visited and add to recursion stack
|
||||
visited[sub] = true
|
||||
recStack[sub] = true
|
||||
|
||||
// recurse for all neighbours
|
||||
for(called in calls.getValue(sub)) {
|
||||
if(hasCycle(called, visited, recStack, chain)) {
|
||||
chain.add(called)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// pop from recursion stack
|
||||
recStack[sub] = false
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -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,6 +98,43 @@ 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>()
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val subExpr: BinaryExpression? = when {
|
||||
leftconst!=null -> expr.right as? BinaryExpression
|
||||
@ -111,7 +149,8 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
|
||||
val change = groupTwoConstsTogether(expr, subExpr,
|
||||
leftconst != null, rightconst != null,
|
||||
subleftconst != null, subrightconst != null)
|
||||
return change?.let { listOf(it) } ?: noModifications
|
||||
if(change!=null)
|
||||
modifications += change
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,10 +158,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> {
|
||||
|
@ -13,7 +13,7 @@ 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, private val errors: ErrorReporter) : AstWalker() {
|
||||
internal class VarConstantValueTypeAdjuster(private val program: Program) : AstWalker() {
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
@ -58,15 +58,16 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
// the initializer value can't refer to the variable itself (recursive definition)
|
||||
// TODO: use call graph for this?
|
||||
if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true) {
|
||||
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexVar?.referencesIdentifier(decl.name) == true) {
|
||||
errors.err("recursive var declaration", decl.position)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
if(decl.type== VarDeclType.CONST || decl.type== VarDeclType.VAR) {
|
||||
if(decl.isArray){
|
||||
if(decl.arraysize==null) {
|
||||
// for arrays that have no size specifier (or a non-constant one) attempt to deduce the size
|
||||
val arraysize = decl.arraysize
|
||||
if(arraysize==null) {
|
||||
// for arrays that have no size specifier attempt to deduce the size
|
||||
val arrayval = decl.value as? ArrayLiteralValue
|
||||
if(arrayval!=null) {
|
||||
return listOf(IAstModification.SetExpression(
|
||||
@ -75,14 +76,13 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
decl
|
||||
))
|
||||
}
|
||||
}
|
||||
else if(decl.arraysize?.constIndex()==null) {
|
||||
val size = decl.arraysize!!.index.constValue(program)
|
||||
if(size!=null) {
|
||||
return listOf(IAstModification.SetExpression(
|
||||
{ decl.arraysize = ArrayIndex(it, decl.position) },
|
||||
size, decl
|
||||
))
|
||||
} else if(arraysize.constIndex()==null) {
|
||||
// see if we can calculate the size from other fields
|
||||
val cval = arraysize.indexVar?.constValue(program) ?: arraysize.origExpression?.constValue(program)
|
||||
if(cval!=null) {
|
||||
arraysize.indexVar = null
|
||||
arraysize.origExpression = null
|
||||
arraysize.indexNum = cval
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
@ -175,28 +182,6 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
// unsigned >= 0 --> true
|
||||
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(true, expr.position), parent))
|
||||
}
|
||||
when(leftDt) {
|
||||
DataType.BYTE -> {
|
||||
// signed >=0 --> signed ^ $80
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr,
|
||||
BinaryExpression(expr.left, "^", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
|
||||
parent
|
||||
))
|
||||
}
|
||||
DataType.WORD -> {
|
||||
// signedw >=0 --> msb(signedw) ^ $80
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr,
|
||||
BinaryExpression(FunctionCall(IdentifierReference(listOf("msb"), expr.position),
|
||||
mutableListOf(expr.left),
|
||||
expr.position
|
||||
), "^", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
|
||||
parent
|
||||
))
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
if(expr.operator == "<" && rightVal?.number == 0) {
|
||||
@ -360,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
|
||||
@ -373,6 +358,13 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
}
|
||||
// no need to check for left val constant (because of associativity)
|
||||
|
||||
val rnum = rightVal?.number?.toDouble()
|
||||
if(rnum!=null && rnum<0.0) {
|
||||
expr.operator = "-"
|
||||
expr.right = NumericLiteralValue(rightVal.type, -rnum, rightVal.position)
|
||||
return expr
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@ -387,12 +379,16 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
|
||||
if (rightVal != null) {
|
||||
// right value is a constant, see if we can optimize
|
||||
val rightConst: NumericLiteralValue = rightVal
|
||||
when (rightConst.number.toDouble()) {
|
||||
0.0 -> {
|
||||
// left
|
||||
return expr.left
|
||||
}
|
||||
val rnum = rightVal.number.toDouble()
|
||||
if (rnum == 0.0) {
|
||||
// left
|
||||
return expr.left
|
||||
}
|
||||
|
||||
if(rnum<0.0) {
|
||||
expr.operator = "+"
|
||||
expr.right = NumericLiteralValue(rightVal.type, -rnum, rightVal.position)
|
||||
return expr
|
||||
}
|
||||
}
|
||||
if (leftVal != null) {
|
||||
@ -405,6 +401,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@ -487,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)
|
||||
@ -569,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
|
||||
@ -616,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)
|
||||
@ -627,7 +629,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
if (amount >= 16) {
|
||||
return NumericLiteralValue(targetDt, 0, expr.position)
|
||||
} else if (amount >= 8) {
|
||||
val lsb = TypecastExpression(expr.left, DataType.UBYTE, true, expr.position)
|
||||
val lsb = FunctionCall(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
|
||||
if (amount == 8) {
|
||||
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(lsb, NumericLiteralValue.optimalInteger(0, expr.position)), expr.position)
|
||||
}
|
||||
@ -649,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)
|
||||
@ -666,8 +671,9 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
return NumericLiteralValue.optimalInteger(0, expr.position)
|
||||
} else if (amount >= 8) {
|
||||
val msb = FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
|
||||
if (amount == 8)
|
||||
return msb
|
||||
if (amount == 8) {
|
||||
return TypecastExpression(msb, DataType.UWORD, true, expr.position)
|
||||
}
|
||||
return BinaryExpression(msb, ">>", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position)
|
||||
}
|
||||
}
|
||||
@ -675,14 +681,6 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
if (amount > 16) {
|
||||
expr.right = NumericLiteralValue.optimalInteger(16, expr.right.position)
|
||||
return null
|
||||
} else if (amount >= 8) {
|
||||
val msbAsByte = TypecastExpression(
|
||||
FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position),
|
||||
DataType.BYTE,
|
||||
true, expr.position)
|
||||
if (amount == 8)
|
||||
return msbAsByte
|
||||
return BinaryExpression(msbAsByte, ">>", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
@ -691,17 +689,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, errors)
|
||||
val valuetypefixer = VarConstantValueTypeAdjuster(this)
|
||||
valuetypefixer.visit(this)
|
||||
if(errors.isEmpty()) {
|
||||
valuetypefixer.applyModifications()
|
||||
@ -53,3 +53,9 @@ internal fun Program.simplifyExpressions() : Int {
|
||||
opti.visit(this)
|
||||
return opti.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.splitBinaryExpressions() : Int {
|
||||
val opti = BinExprSplitter(this)
|
||||
opti.visit(this)
|
||||
return opti.applyModifications()
|
||||
}
|
||||
|
@ -25,12 +25,12 @@ internal class StatementOptimizer(private val program: Program,
|
||||
if("force_output" !in block.options()) {
|
||||
if (block.containsNoCodeNorVars()) {
|
||||
errors.warn("removing empty block '${block.name}'", block.position)
|
||||
return listOf(IAstModification.Remove(block, parent))
|
||||
return listOf(IAstModification.Remove(block, parent as INameScope))
|
||||
}
|
||||
|
||||
if (block !in callgraph.usedSymbols) {
|
||||
errors.warn("removing unused block '${block.name}'", block.position)
|
||||
return listOf(IAstModification.Remove(block, parent))
|
||||
return listOf(IAstModification.Remove(block, parent as INameScope))
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
@ -42,38 +42,28 @@ internal class StatementOptimizer(private val program: Program,
|
||||
if(subroutine.containsNoCodeNorVars()) {
|
||||
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
|
||||
val removals = callgraph.calledBy.getValue(subroutine).map {
|
||||
IAstModification.Remove(it, it.parent)
|
||||
IAstModification.Remove(it, it.definingScope())
|
||||
}.toMutableList()
|
||||
removals += IAstModification.Remove(subroutine, parent)
|
||||
removals += IAstModification.Remove(subroutine, subroutine.definingScope())
|
||||
return removals
|
||||
}
|
||||
}
|
||||
|
||||
val linesToRemove = deduplicateAssignments(subroutine.statements)
|
||||
if(linesToRemove.isNotEmpty()) {
|
||||
linesToRemove.reversed().forEach{subroutine.statements.removeAt(it)}
|
||||
}
|
||||
|
||||
if(subroutine !in callgraph.usedSymbols && !forceOutput) {
|
||||
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
|
||||
return listOf(IAstModification.Remove(subroutine, parent))
|
||||
return listOf(IAstModification.Remove(subroutine, subroutine.definingScope()))
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
val linesToRemove = deduplicateAssignments(scope.statements)
|
||||
return linesToRemove.reversed().map { IAstModification.Remove(scope.statements[it], scope) }
|
||||
}
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
val forceOutput = "force_output" in decl.definingBlock().options()
|
||||
if(decl !in callgraph.usedSymbols && !forceOutput) {
|
||||
if(decl.type == VarDeclType.VAR)
|
||||
errors.warn("removing unused variable '${decl.name}'", decl.position)
|
||||
|
||||
return listOf(IAstModification.Remove(decl, parent))
|
||||
return listOf(IAstModification.Remove(decl, decl.definingScope()))
|
||||
}
|
||||
|
||||
return noModifications
|
||||
@ -84,7 +74,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
val functionName = functionCallStatement.target.nameInSource[0]
|
||||
if (functionName in pureBuiltinFunctions) {
|
||||
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
|
||||
return listOf(IAstModification.Remove(functionCallStatement, parent))
|
||||
return listOf(IAstModification.Remove(functionCallStatement, functionCallStatement.definingScope()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,7 +127,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
if(subroutine!=null) {
|
||||
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||
if(first is Return)
|
||||
return listOf(IAstModification.Remove(functionCallStatement, parent))
|
||||
return listOf(IAstModification.Remove(functionCallStatement, functionCallStatement.definingScope()))
|
||||
}
|
||||
|
||||
return noModifications
|
||||
@ -160,7 +150,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
|
||||
// remove empty if statements
|
||||
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsNoCodeNorVars())
|
||||
return listOf(IAstModification.Remove(ifStatement, parent))
|
||||
return listOf(IAstModification.Remove(ifStatement, ifStatement.definingScope()))
|
||||
|
||||
// empty true part? switch with the else part
|
||||
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsCodeOrVars()) {
|
||||
@ -193,12 +183,12 @@ internal class StatementOptimizer(private val program: Program,
|
||||
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
|
||||
if(forLoop.body.containsNoCodeNorVars()) {
|
||||
errors.warn("removing empty for loop", forLoop.position)
|
||||
return listOf(IAstModification.Remove(forLoop, parent))
|
||||
return listOf(IAstModification.Remove(forLoop, forLoop.definingScope()))
|
||||
} else if(forLoop.body.statements.size==1) {
|
||||
val loopvar = forLoop.body.statements[0] as? VarDecl
|
||||
if(loopvar!=null && loopvar.name==forLoop.loopVar.nameInSource.singleOrNull()) {
|
||||
// remove empty for loop (only loopvar decl in it)
|
||||
return listOf(IAstModification.Remove(forLoop, parent))
|
||||
return listOf(IAstModification.Remove(forLoop, forLoop.definingScope()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -249,11 +239,11 @@ internal class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
|
||||
override fun before(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
|
||||
val constvalue = untilLoop.untilCondition.constValue(program)
|
||||
val constvalue = untilLoop.condition.constValue(program)
|
||||
if(constvalue!=null) {
|
||||
if(constvalue.asBooleanValue) {
|
||||
// always true -> keep only the statement block (if there are no break statements)
|
||||
errors.warn("condition is always true", untilLoop.untilCondition.position)
|
||||
errors.warn("condition is always true", untilLoop.condition.position)
|
||||
if(!hasBreak(untilLoop.body))
|
||||
return listOf(IAstModification.ReplaceNode(untilLoop, untilLoop.body, parent))
|
||||
} else {
|
||||
@ -275,7 +265,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
} else {
|
||||
// always false -> remove the while statement altogether
|
||||
errors.warn("condition is always false", whileLoop.condition.position)
|
||||
listOf(IAstModification.Remove(whileLoop, parent))
|
||||
listOf(IAstModification.Remove(whileLoop, whileLoop.definingScope()))
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
@ -286,12 +276,12 @@ internal class StatementOptimizer(private val program: Program,
|
||||
if(iter!=null) {
|
||||
if(repeatLoop.body.containsNoCodeNorVars()) {
|
||||
errors.warn("empty loop removed", repeatLoop.position)
|
||||
return listOf(IAstModification.Remove(repeatLoop, parent))
|
||||
return listOf(IAstModification.Remove(repeatLoop, repeatLoop.definingScope()))
|
||||
}
|
||||
val iterations = iter.constValue(program)?.number?.toInt()
|
||||
if (iterations == 0) {
|
||||
errors.warn("iterations is always 0, removed loop", iter.position)
|
||||
return listOf(IAstModification.Remove(repeatLoop, parent))
|
||||
return listOf(IAstModification.Remove(repeatLoop, repeatLoop.definingScope()))
|
||||
}
|
||||
if (iterations == 1) {
|
||||
errors.warn("iterations is always 1", iter.position)
|
||||
@ -318,7 +308,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
val scope = jump.definingScope()
|
||||
val label = jump.identifier?.targetStatement(scope)
|
||||
if(label!=null && scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1)
|
||||
return listOf(IAstModification.Remove(jump, parent))
|
||||
return listOf(IAstModification.Remove(jump, jump.definingScope()))
|
||||
|
||||
return noModifications
|
||||
}
|
||||
@ -333,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))
|
||||
}
|
||||
@ -351,7 +341,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
|
||||
IAstModification.InsertAfter(assignment, addConstant, parent))
|
||||
IAstModification.InsertAfter(assignment, addConstant, assignment.definingScope()))
|
||||
} else if (op2 == "-") {
|
||||
// A = A +/- B - N
|
||||
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
|
||||
@ -362,7 +352,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
|
||||
IAstModification.InsertAfter(assignment, subConstant, parent))
|
||||
IAstModification.InsertAfter(assignment, subConstant, assignment.definingScope()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -384,10 +374,10 @@ internal class StatementOptimizer(private val program: Program,
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
if(assignment.target isSameAs assignment.value) {
|
||||
// remove assignment to self
|
||||
return listOf(IAstModification.Remove(assignment, parent))
|
||||
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")
|
||||
|
||||
@ -395,21 +385,21 @@ internal class StatementOptimizer(private val program: Program,
|
||||
val targetDt = targetIDt.typeOrElse(DataType.STRUCT)
|
||||
val bexpr=assignment.value as? BinaryExpression
|
||||
if(bexpr!=null) {
|
||||
val cv = bexpr.right.constValue(program)?.number?.toDouble()
|
||||
if (cv != null && assignment.target isSameAs bexpr.left) {
|
||||
val rightCv = bexpr.right.constValue(program)?.number?.toDouble()
|
||||
if (rightCv != null && assignment.target isSameAs bexpr.left) {
|
||||
// assignments of the form: X = X <operator> <expr>
|
||||
// remove assignments that have no effect (such as X=X+0)
|
||||
// optimize/rewrite some other expressions
|
||||
val vardeclDt = (assignment.target.identifier?.targetVarDecl(program.namespace))?.type
|
||||
when (bexpr.operator) {
|
||||
"+" -> {
|
||||
if (cv == 0.0) {
|
||||
return listOf(IAstModification.Remove(assignment, parent))
|
||||
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
|
||||
if (vardeclDt != VarDeclType.MEMORY && cv in 1.0..4.0) {
|
||||
if (rightCv == 0.0) {
|
||||
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
|
||||
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0) {
|
||||
// replace by several INCs if it's not a memory address (inc on a memory mapped register doesn't work very well)
|
||||
val incs = AnonymousScope(mutableListOf(), assignment.position)
|
||||
repeat(cv.toInt()) {
|
||||
repeat(rightCv.toInt()) {
|
||||
incs.statements.add(PostIncrDecr(assignment.target, "++", assignment.position))
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(assignment, incs, parent))
|
||||
@ -417,62 +407,38 @@ internal class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
}
|
||||
"-" -> {
|
||||
if (cv == 0.0) {
|
||||
return listOf(IAstModification.Remove(assignment, parent))
|
||||
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
|
||||
if (vardeclDt != VarDeclType.MEMORY && cv in 1.0..4.0) {
|
||||
if (rightCv == 0.0) {
|
||||
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
|
||||
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0) {
|
||||
// replace by several DECs if it's not a memory address (dec on a memory mapped register doesn't work very well)
|
||||
val decs = AnonymousScope(mutableListOf(), assignment.position)
|
||||
repeat(cv.toInt()) {
|
||||
repeat(rightCv.toInt()) {
|
||||
decs.statements.add(PostIncrDecr(assignment.target, "--", assignment.position))
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(assignment, decs, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
"*" -> if (cv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||
"/" -> if (cv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||
"**" -> if (cv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||
"|" -> if (cv == 0.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||
"^" -> if (cv == 0.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||
"*" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
"/" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
"**" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
"|" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
"^" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
"<<" -> {
|
||||
if (cv == 0.0)
|
||||
return listOf(IAstModification.Remove(assignment, parent))
|
||||
if (rightCv == 0.0)
|
||||
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
}
|
||||
">>" -> {
|
||||
if (cv == 0.0)
|
||||
return listOf(IAstModification.Remove(assignment, parent))
|
||||
if (rightCv == 0.0)
|
||||
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun deduplicateAssignments(statements: List<Statement>): MutableList<Int> {
|
||||
// removes 'duplicate' assignments that assign the isSameAs target
|
||||
val linesToRemove = mutableListOf<Int>()
|
||||
var previousAssignmentLine: Int? = null
|
||||
for (i in statements.indices) {
|
||||
val stmt = statements[i] as? Assignment
|
||||
if (stmt != null && stmt.value is NumericLiteralValue) {
|
||||
if (previousAssignmentLine == null) {
|
||||
previousAssignmentLine = i
|
||||
continue
|
||||
} else {
|
||||
val prev = statements[previousAssignmentLine] as Assignment
|
||||
if (prev.target.isSameAs(stmt.target, program)) {
|
||||
// get rid of the previous assignment, if the target is not MEMORY
|
||||
if (prev.target.isNotMemory(program.namespace))
|
||||
linesToRemove.add(previousAssignmentLine)
|
||||
}
|
||||
previousAssignmentLine = i
|
||||
}
|
||||
} else
|
||||
previousAssignmentLine = null
|
||||
}
|
||||
return linesToRemove
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun hasBreak(scope: INameScope): Boolean {
|
||||
|
@ -8,7 +8,8 @@ import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModification
|
||||
import prog8.ast.statements.*
|
||||
|
||||
internal class UnusedCodeRemover(private val errors: ErrorReporter): AstWalker() {
|
||||
|
||||
internal class UnusedCodeRemover(private val program: Program, private val errors: ErrorReporter): AstWalker() {
|
||||
|
||||
override fun before(program: Program, parent: Node): Iterable<IAstModification> {
|
||||
val callgraph = CallGraph(program)
|
||||
@ -19,20 +20,20 @@ internal class UnusedCodeRemover(private val errors: ErrorReporter): AstWalker()
|
||||
program.modules.forEach {
|
||||
callgraph.forAllSubroutines(it) { sub ->
|
||||
if (sub !== entrypoint && !sub.isAsmSubroutine && (callgraph.calledBy[sub].isNullOrEmpty() || sub.containsNoCodeNorVars())) {
|
||||
removals.add(IAstModification.Remove(sub, sub.definingScope() as Node))
|
||||
removals.add(IAstModification.Remove(sub, sub.definingScope()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
program.modules.flatMap { it.statements }.filterIsInstance<Block>().forEach { block ->
|
||||
if (block.containsNoCodeNorVars() && "force_output" !in block.options())
|
||||
removals.add(IAstModification.Remove(block, block.definingScope() as Node))
|
||||
removals.add(IAstModification.Remove(block, block.definingScope()))
|
||||
}
|
||||
|
||||
// remove modules that are not imported, or are empty (unless it's a library modules)
|
||||
program.modules.forEach {
|
||||
if (!it.isLibraryModule && (it.importedBy.isEmpty() || it.containsNoCodeNorVars()))
|
||||
removals.add(IAstModification.Remove(it, it.parent))
|
||||
removals.add(IAstModification.Remove(it, it.definingScope()))
|
||||
}
|
||||
|
||||
return removals
|
||||
@ -66,4 +67,37 @@ internal class UnusedCodeRemover(private val errors: ErrorReporter): AstWalker()
|
||||
else -> errors.warn("unreachable code", next.position)
|
||||
}
|
||||
}
|
||||
|
||||
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
val removeDoubleAssignments = deduplicateAssignments(scope.statements)
|
||||
return removeDoubleAssignments.map { IAstModification.Remove(it, scope) }
|
||||
}
|
||||
|
||||
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
|
||||
val removeDoubleAssignments = deduplicateAssignments(block.statements)
|
||||
return removeDoubleAssignments.map { IAstModification.Remove(it, block) }
|
||||
}
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
val removeDoubleAssignments = deduplicateAssignments(subroutine.statements)
|
||||
return removeDoubleAssignments.map { IAstModification.Remove(it, subroutine) }
|
||||
}
|
||||
|
||||
private fun deduplicateAssignments(statements: List<Statement>): List<Assignment> {
|
||||
// removes 'duplicate' assignments that assign the same target directly after another
|
||||
val linesToRemove = mutableListOf<Assignment>()
|
||||
|
||||
for (stmtPairs in statements.windowed(2, step = 1)) {
|
||||
val assign1 = stmtPairs[0] as? Assignment
|
||||
val assign2 = stmtPairs[1] as? Assignment
|
||||
if (assign1 != null && assign2 != null && !assign2.isAugmentable) {
|
||||
if (assign1.target.isSameAs(assign2.target, program) && assign1.target.isInRegularRAM(program.namespace)) {
|
||||
if(assign2.target.identifier==null || !assign2.value.referencesIdentifier(*(assign2.target.identifier!!.nameInSource.toTypedArray())))
|
||||
linesToRemove.add(assign1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return linesToRemove
|
||||
}
|
||||
}
|
||||
|
@ -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) }
|
||||
|
||||
@ -102,7 +102,7 @@ internal class ModuleImporter {
|
||||
|
||||
private fun discoverImportedModuleFile(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)
|
||||
@ -131,13 +131,14 @@ internal class ModuleImporter {
|
||||
if(existing!=null)
|
||||
return null
|
||||
|
||||
val resource = tryGetEmbeddedResource("$moduleName.p8")
|
||||
val rsc = tryGetEmbeddedResource("$moduleName.p8")
|
||||
val importedModule =
|
||||
if(resource!=null) {
|
||||
if(rsc!=null) {
|
||||
// load the module from the embedded resource
|
||||
val (resource, resourcePath) = rsc
|
||||
resource.use {
|
||||
println("importing '$moduleName' (library)")
|
||||
importModule(program, CharStreams.fromStream(it), Paths.get("@embedded@/$moduleName"), true)
|
||||
importModule(program, CharStreams.fromStream(it), Paths.get("@embedded@/$resourcePath"), true)
|
||||
}
|
||||
} else {
|
||||
val modulePath = discoverImportedModuleFile(moduleName, source, import.position)
|
||||
@ -148,11 +149,18 @@ internal class ModuleImporter {
|
||||
return importedModule
|
||||
}
|
||||
|
||||
private fun tryGetEmbeddedResource(name: String): InputStream? {
|
||||
private fun tryGetEmbeddedResource(name: String): Pair<InputStream, String>? {
|
||||
val target = CompilationTarget.instance.name
|
||||
val targetSpecific = object{}.javaClass.getResourceAsStream("/prog8lib/$target/$name")
|
||||
if(targetSpecific!=null)
|
||||
return targetSpecific
|
||||
return object{}.javaClass.getResourceAsStream("/prog8lib/$name")
|
||||
val targetSpecificPath = "/prog8lib/$target/$name"
|
||||
val targetSpecificResource = object{}.javaClass.getResourceAsStream(targetSpecificPath)
|
||||
if(targetSpecificResource!=null)
|
||||
return Pair(targetSpecificResource, targetSpecificPath)
|
||||
|
||||
val generalPath = "/prog8lib/$name"
|
||||
val generalResource = object{}.javaClass.getResourceAsStream(generalPath)
|
||||
if(generalResource!=null)
|
||||
return Pair(generalResource, generalPath)
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
@ -5,17 +5,18 @@ import org.hamcrest.Matchers.closeTo
|
||||
import org.hamcrest.Matchers.equalTo
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.StringLiteralValue
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.*
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_NEGATIVE
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_POSITIVE
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.Mflpt5
|
||||
import prog8.compiler.target.c64.Petscii
|
||||
import prog8.compiler.target.cx16.CX16MachineDefinition
|
||||
import java.io.CharConversionException
|
||||
import kotlin.test.*
|
||||
|
||||
@ -129,7 +130,7 @@ class TestC64Zeropage {
|
||||
|
||||
@Test
|
||||
fun testNames() {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false))
|
||||
|
||||
zp.allocate("", DataType.UBYTE, null, errors)
|
||||
zp.allocate("", DataType.UBYTE, null, errors)
|
||||
@ -142,37 +143,37 @@ class TestC64Zeropage {
|
||||
|
||||
@Test
|
||||
fun testZpFloatEnable() {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
|
||||
assertFailsWith<CompilerException> {
|
||||
zp.allocate("", DataType.FLOAT, null, errors)
|
||||
}
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true))
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false))
|
||||
assertFailsWith<CompilerException> {
|
||||
zp2.allocate("", DataType.FLOAT, null, errors)
|
||||
}
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true))
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false))
|
||||
zp3.allocate("", DataType.FLOAT, null, errors)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testZpModesWithFloats() {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false))
|
||||
assertFailsWith<CompilerException> {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true, false))
|
||||
}
|
||||
assertFailsWith<CompilerException> {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testZpDontuse() {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false))
|
||||
println(zp.free)
|
||||
assertEquals(0, zp.available())
|
||||
assertFailsWith<CompilerException> {
|
||||
@ -182,19 +183,19 @@ class TestC64Zeropage {
|
||||
|
||||
@Test
|
||||
fun testFreeSpaces() {
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
|
||||
assertEquals(16, zp1.available())
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false))
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
|
||||
assertEquals(18, zp1.available())
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false))
|
||||
assertEquals(89, zp2.available())
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false))
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false))
|
||||
assertEquals(125, zp3.available())
|
||||
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
|
||||
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
|
||||
assertEquals(238, zp4.available())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testReservedSpace() {
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
|
||||
assertEquals(238, zp1.available())
|
||||
assertTrue(50 in zp1.free)
|
||||
assertTrue(100 in zp1.free)
|
||||
@ -203,7 +204,7 @@ class TestC64Zeropage {
|
||||
assertTrue(200 in zp1.free)
|
||||
assertTrue(255 in zp1.free)
|
||||
assertTrue(199 in zp1.free)
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false))
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false, false))
|
||||
assertEquals(139, zp2.available())
|
||||
assertFalse(50 in zp2.free)
|
||||
assertFalse(100 in zp2.free)
|
||||
@ -216,8 +217,8 @@ class TestC64Zeropage {
|
||||
|
||||
@Test
|
||||
fun testBasicsafeAllocation() {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
|
||||
assertEquals(16, zp.available())
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
|
||||
assertEquals(18, zp.available())
|
||||
|
||||
assertFailsWith<ZeropageDepletedError> {
|
||||
// in regular zp there aren't 5 sequential bytes free
|
||||
@ -239,7 +240,7 @@ class TestC64Zeropage {
|
||||
|
||||
@Test
|
||||
fun testFullAllocation() {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
|
||||
assertEquals(238, zp.available())
|
||||
val loc = zp.allocate("", DataType.UWORD, null, errors)
|
||||
assertTrue(loc > 3)
|
||||
@ -269,20 +270,38 @@ class TestC64Zeropage {
|
||||
|
||||
@Test
|
||||
fun testEfficientAllocation() {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
|
||||
assertEquals(16, zp.available())
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
|
||||
assertEquals(18, zp.available())
|
||||
assertEquals(0x04, zp.allocate("", DataType.WORD, null, errors))
|
||||
assertEquals(0x06, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
assertEquals(0x0a, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
assertEquals(0x94, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0xa7, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0xa9, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0xb5, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0xf7, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0x9b, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0x9e, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0xa5, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0xb0, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0xbe, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0x0e, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
assertEquals(0x92, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
assertEquals(0x96, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
assertEquals(0xf9, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
assertEquals(0, zp.available())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testReservedLocations() {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false))
|
||||
assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestCx16Zeropage {
|
||||
@Test
|
||||
fun testReservedLocations() {
|
||||
val zp = CX16MachineDefinition.CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false))
|
||||
assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -379,3 +398,169 @@ class TestPetscii {
|
||||
assertFalse(abc!=abc)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class TestMemory {
|
||||
@Test
|
||||
fun testInValidRamC64_memory_addresses() {
|
||||
CompilationTarget.instance = C64Target
|
||||
|
||||
var memexpr = NumericLiteralValue.optimalInteger(0x0000, Position.DUMMY)
|
||||
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
var scope = AnonymousScope(mutableListOf(), Position.DUMMY)
|
||||
assertTrue(target.isInRegularRAM(scope))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
|
||||
assertTrue(target.isInRegularRAM(scope))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0x9fff, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
|
||||
assertTrue(target.isInRegularRAM(scope))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xc000, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
|
||||
assertTrue(target.isInRegularRAM(scope))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xcfff, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
|
||||
assertTrue(target.isInRegularRAM(scope))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotInValidRamC64_memory_addresses() {
|
||||
CompilationTarget.instance = C64Target
|
||||
|
||||
var memexpr = NumericLiteralValue.optimalInteger(0xa000, Position.DUMMY)
|
||||
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
var scope = AnonymousScope(mutableListOf(), Position.DUMMY)
|
||||
assertFalse(target.isInRegularRAM(scope))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xafff, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
|
||||
assertFalse(target.isInRegularRAM(scope))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xd000, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
|
||||
assertFalse(target.isInRegularRAM(scope))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xffff, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
|
||||
assertFalse(target.isInRegularRAM(scope))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_memory_identifiers() {
|
||||
CompilationTarget.instance = C64Target
|
||||
var target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.VAR)
|
||||
assertTrue(target.isInRegularRAM(target.definingScope()))
|
||||
target = createTestProgramForMemoryRefViaVar(0xd020, VarDeclType.VAR)
|
||||
assertFalse(target.isInRegularRAM(target.definingScope()))
|
||||
target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.CONST)
|
||||
assertTrue(target.isInRegularRAM(target.definingScope()))
|
||||
target = createTestProgramForMemoryRefViaVar(0xd020, VarDeclType.CONST)
|
||||
assertFalse(target.isInRegularRAM(target.definingScope()))
|
||||
target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.MEMORY)
|
||||
assertFalse(target.isInRegularRAM(target.definingScope()))
|
||||
}
|
||||
|
||||
@Test
|
||||
private fun createTestProgramForMemoryRefViaVar(address: Int, vartype: VarDeclType): AssignTarget {
|
||||
val decl = VarDecl(vartype, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
|
||||
val memexpr = IdentifierReference(listOf("address"), Position.DUMMY)
|
||||
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
subroutine.linkParents(ParentSentinel)
|
||||
return target
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_memory_expression() {
|
||||
CompilationTarget.instance = C64Target
|
||||
val memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
val scope = AnonymousScope(mutableListOf(), Position.DUMMY)
|
||||
assertFalse(target.isInRegularRAM(scope))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_variable() {
|
||||
CompilationTarget.instance = C64Target
|
||||
val decl = VarDecl(VarDeclType.VAR, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, null, false, false, Position.DUMMY)
|
||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
subroutine.linkParents(ParentSentinel)
|
||||
assertTrue(target.isInRegularRAM(target.definingScope()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_memmap_variable() {
|
||||
CompilationTarget.instance = C64Target
|
||||
val address = 0x1000
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
|
||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
subroutine.linkParents(ParentSentinel)
|
||||
assertTrue(target.isInRegularRAM(target.definingScope()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotInValidRamC64_memmap_variable() {
|
||||
CompilationTarget.instance = C64Target
|
||||
val address = 0xd020
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
|
||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
subroutine.linkParents(ParentSentinel)
|
||||
assertFalse(target.isInRegularRAM(target.definingScope()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_array() {
|
||||
CompilationTarget.instance = C64Target
|
||||
val decl = VarDecl(VarDeclType.VAR, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, null, false, false, Position.DUMMY)
|
||||
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
subroutine.linkParents(ParentSentinel)
|
||||
assertTrue(target.isInRegularRAM(target.definingScope()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_array_memmapped() {
|
||||
CompilationTarget.instance = C64Target
|
||||
val address = 0x1000
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
|
||||
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
subroutine.linkParents(ParentSentinel)
|
||||
assertTrue(target.isInRegularRAM(target.definingScope()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotValidRamC64_array_memmapped() {
|
||||
CompilationTarget.instance = C64Target
|
||||
val address = 0xe000
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
|
||||
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
subroutine.linkParents(ParentSentinel)
|
||||
assertFalse(target.isInRegularRAM(target.definingScope()))
|
||||
}
|
||||
}
|
||||
|
BIN
docs/source/_static/primes_cx16.png
Normal file
BIN
docs/source/_static/primes_cx16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
@ -41,7 +41,7 @@ of that build task, you can start the compiler with:
|
||||
|
||||
(You should probably make an alias...)
|
||||
|
||||
.. note::
|
||||
.. hint::
|
||||
Development and testing is done on Linux, but the compiler should run on most
|
||||
operating systems. If you do have trouble building or running
|
||||
the compiler on another operating system, please let me know!
|
||||
@ -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
|
||||
^^^^^^^^^^^^^
|
||||
|
@ -38,6 +38,27 @@ 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``, ``memset``, ``memcopy``, ``substr``, ``sort`` and ``reverse`` (and others)
|
||||
- If you only use standard kernel and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
|
||||
|
||||
|
||||
Code example
|
||||
------------
|
||||
|
||||
@ -96,37 +117,12 @@ when compiled an ran on a C-64 you get this:
|
||||
:align: center
|
||||
:alt: result when run on C-64
|
||||
|
||||
when the exact same program is compiled for the Commander X16 target, and run on the emulator, you get this:
|
||||
|
||||
.. image:: _static/primes_cx16.png
|
||||
:align: center
|
||||
:alt: result when run on CX16 emulator
|
||||
|
||||
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:
|
||||
@ -137,14 +133,17 @@ Required tools
|
||||
`64tass <https://sourceforge.net/projects/tass64/>`_ - cross assembler. Install this on your shell path.
|
||||
It's very easy to compile yourself.
|
||||
A recent precompiled .exe for Windows can be obtained from my `clone <https://github.com/irmen/64tass/releases>`_ of this project.
|
||||
*You need at least version 1.55.2257 of this assembler to correctly use the breakpoints feature.*
|
||||
It's possible to use older versions, but it is very likely that the automatic Vice breakpoints won't work with them.
|
||||
|
||||
A **Java runtime (jre or jdk), version 8 or newer** is required to run the prog8 compiler itself.
|
||||
If you're scared of Oracle's licensing terms, most Linux distributions ship OpenJDK instead.
|
||||
Fnd for Windows it's possible to get that as well. Check out `AdoptOpenJDK <https://adoptopenjdk.net/>`_ .
|
||||
A **Java runtime (jre or jdk), version 11 or newer** is required to run the prog8 compiler itself.
|
||||
If you're scared of Oracle's licensing terms, most Linux distributions ship OpenJDK in their packages repository instead.
|
||||
For Windows it's possible to get that as well; check out `AdoptOpenJDK <https://adoptopenjdk.net/>`_ .
|
||||
For MacOS you can use the Homebrew system to install a recent version of OpenJDK.
|
||||
|
||||
Finally: a **C-64 emulator** (or a real C-64 ofcourse) can be nice to test and run your programs on.
|
||||
The compiler assumes the presence of the `Vice emulator <http://vice-emu.sourceforge.net/>`_.
|
||||
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'*)
|
||||
@ -165,6 +164,7 @@ If you're targeting the CommanderX16, there's the `x16emu <https://github.com/co
|
||||
building.rst
|
||||
programming.rst
|
||||
syntaxreference.rst
|
||||
libraries.rst
|
||||
todo.rst
|
||||
|
||||
|
||||
|
104
docs/source/libraries.rst
Normal file
104
docs/source/libraries.rst
Normal file
@ -0,0 +1,104 @@
|
||||
************************
|
||||
Compiler library modules
|
||||
************************
|
||||
|
||||
The compiler provides several "built-in" library modules with useful subroutine and variables.
|
||||
|
||||
Some of these may be specific for a certain compilation target, or work slightly different,
|
||||
but some effort is put into making them available across compilation targets.
|
||||
|
||||
This means that as long as your program is only using the subroutines from these
|
||||
libraries and not using hardware- and/or system dependent code, and isn't hardcoding certain
|
||||
assumptions like the screen size, the exact same source program can
|
||||
be compiled for multiple different target platforms. Many of the example programs that come
|
||||
with Prog8 are written like this.
|
||||
|
||||
You can ``%import`` and use these modules explicitly, but the compiler may also import one or more
|
||||
of these library modules automatically as required.
|
||||
|
||||
.. caution::
|
||||
The resulting compiled binary program *only works on the target machine it was compiled for*.
|
||||
You must recompile the program for every target you want to run it on.
|
||||
|
||||
|
||||
|
||||
syslib
|
||||
------
|
||||
The "system library" for your target machine. It contains many system-specific definitions such
|
||||
as ROM/kernal subroutine definitions, memory location constants, and utility subroutines.
|
||||
|
||||
Many of these definitions overlap for the C64 and Commander X16 targets so it is still possible
|
||||
to write programs that work on both targets without modifications.
|
||||
|
||||
conv
|
||||
----
|
||||
Routines to convert strings to numbers or vice versa.
|
||||
|
||||
- numbers to strings, in various formats (binary, hex, decimal)
|
||||
- strings in decimal, hex and binary format into numbers
|
||||
|
||||
|
||||
textio (txt.*)
|
||||
--------------
|
||||
This will probably be the most used library module. It contains a whole lot of routines
|
||||
dealing with text-based input and output (to the screen). Such as
|
||||
|
||||
- printing strings and numbers
|
||||
- reading text input from the user via the keyboard
|
||||
- filling or clearing the screen and colors
|
||||
- scrolling the text on the screen
|
||||
- placing individual characters on the screen
|
||||
|
||||
|
||||
diskio
|
||||
------
|
||||
Provides several routines that deal with disk drive I/O, such as:
|
||||
|
||||
- list files on disk, optionally filtering by prefix or suffix
|
||||
- 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
|
||||
|
||||
|
||||
floats
|
||||
------
|
||||
Provides definitions for the ROM/kernel subroutines and utility routines dealing with floating
|
||||
point variables. This includes ``print_f``, the routine used to print floating point numbers.
|
||||
|
||||
|
||||
graphics
|
||||
--------
|
||||
High-res monochrome bitmap graphics routines:
|
||||
|
||||
- clearing the screen
|
||||
- drawing lines
|
||||
- drawing circles and discs (filled circles)
|
||||
- plotting individual pixels
|
||||
|
||||
|
||||
math
|
||||
----
|
||||
Low level math routines. You should not normally have to bother with this directly.
|
||||
The compiler needs it to implement most of the math operations in your programs.
|
||||
|
||||
|
||||
cx16logo
|
||||
--------
|
||||
A 'fun' module that contains the Commander X16 logo and that allows you
|
||||
to print it anywhere on the screen.
|
||||
|
||||
|
||||
c64colors
|
||||
---------
|
||||
Available for the CommanderX16 target, a module that contains a few better
|
||||
color palettes for how the colors of the VIC-II looked on the Commodore-64.
|
||||
There are subroutines to activate one of the several palettes of your liking.
|
||||
The Commander X16's default colors for this (the first 16 colors) are too saturated
|
||||
and are quite different than how a C-64 looked.
|
||||
|
||||
|
||||
prog8_lib
|
||||
---------
|
||||
Low level language support. You should not normally have to bother with this directly.
|
||||
The compiler needs it for verious built-in system routines.
|
@ -236,12 +236,15 @@ The largest 5-byte MFLPT float that can be stored is: **1.7014118345e+38** (ne
|
||||
|
||||
Arrays
|
||||
^^^^^^
|
||||
Array types are also supported. They can be made of bytes, words or floats::
|
||||
Array types are also supported. They can be made of bytes, words or floats, strings, and other arrays
|
||||
(although the usefulness of the latter is very limited for now)::
|
||||
|
||||
byte[10] array ; array of 10 bytes, initially set to 0
|
||||
byte[] array = [1, 2, 3, 4] ; initialize the array, size taken from value
|
||||
byte[99] array = 255 ; initialize array with 99 times 255 [255, 255, 255, 255, ...]
|
||||
byte[] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
|
||||
str[] names = ["ally", "pete"] ; array of string pointers/addresses (equivalent to uword)
|
||||
uword[] others = [names, array] ; array of pointers/addresses to other arrays
|
||||
|
||||
value = array[3] ; the fourth value in the array (index is 0-based)
|
||||
char = string[4] ; the fifth character (=byte) in the string
|
||||
@ -257,6 +260,12 @@ Note that the various keywords for the data type and variable type (``byte``, ``
|
||||
can't be used as *identifiers* elsewhere. You can't make a variable, block or subroutine with the name ``byte``
|
||||
for instance.
|
||||
|
||||
|
||||
It's possible to assign a new array to another array, this will overwrite all elements in the original
|
||||
array with those in the value array. The number and types of elements have to match.
|
||||
For large arrays this is a slow operation because every element is copied over. It should probably be avoided.
|
||||
|
||||
|
||||
**Arrays at a specific memory location:**
|
||||
Using the memory-mapped syntax it is possible to define an array to be located at a specific memory location.
|
||||
For instance to reference the first 5 rows of the Commodore 64's screen matrix as an array, you can define::
|
||||
@ -284,7 +293,7 @@ This @-prefix can also be used for character byte values.
|
||||
You can concatenate two string literals using '+' (not very useful though) or repeat
|
||||
a string literal a given number of times using '*'. You can also assign a new string
|
||||
value to another string. No bounds check is done so be sure the destination string is
|
||||
large enough to contain the new value::
|
||||
large enough to contain the new value (it is overwritten in memory)::
|
||||
|
||||
str string1 = "first part" + "second part"
|
||||
str string2 = "hello!" * 10
|
||||
@ -293,13 +302,28 @@ large enough to contain the new value::
|
||||
string1 = "new value"
|
||||
|
||||
|
||||
There are several 'escape sequences' to help you put special characters into strings, such
|
||||
as newlines, quote characters themselves, and so on. The ones used most often are
|
||||
``\\``, ``\"``, ``\n``, ``\r``. For a detailed description of all of them and what they mean,
|
||||
read the syntax reference on strings.
|
||||
|
||||
|
||||
.. hint::
|
||||
Strings/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
|
||||
@ -327,7 +351,7 @@ and then create a variable with it::
|
||||
ubyte blue
|
||||
}
|
||||
|
||||
Color rgb = {255,122,0} ; note the curly braces here instead of brackets
|
||||
Color rgb = [255,122,0] ; note that struct initializer value is same as an array
|
||||
Color another ; the init value is optional, like arrays
|
||||
|
||||
another = rgb ; assign all of the values of rgb to another
|
||||
@ -352,13 +376,6 @@ address you specified, and setting the varible will directly modify that memory
|
||||
&word SCREENCOLORS = $d020 ; a 16-bit word at the addres $d020-$d021
|
||||
|
||||
|
||||
.. note::
|
||||
Directly accessing random memory locations is not yet supported without the
|
||||
intermediate step of declaring a memory-mapped variable for the memory location.
|
||||
The advantages of this however, is that it's clearer what the memory location
|
||||
stands for, and the compiler also knows the data type.
|
||||
|
||||
|
||||
Converting types into other types
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@ -636,23 +653,20 @@ There's a set of predefined functions in the language. These are fixed and can't
|
||||
You can use them in expressions and the compiler will evaluate them at compile-time if possible.
|
||||
|
||||
|
||||
sin(x)
|
||||
Sine. (floating point version)
|
||||
Math
|
||||
^^^^
|
||||
|
||||
abs(x)
|
||||
Absolute value.
|
||||
|
||||
atan(x)
|
||||
Arctangent.
|
||||
|
||||
ceil(x)
|
||||
Rounds the floating point up to an integer towards positive infinity.
|
||||
|
||||
cos(x)
|
||||
Cosine. (floating point version)
|
||||
|
||||
sin8u(x)
|
||||
Fast 8-bit ubyte sine of angle 0..255, result is in range 0..255
|
||||
|
||||
sin8(x)
|
||||
Fast 8-bit byte sine of angle 0..255, result is in range -127..127
|
||||
|
||||
sin16u(x)
|
||||
Fast 16-bit uword sine of angle 0..255, result is in range 0..65535
|
||||
|
||||
sin16(x)
|
||||
Fast 16-bit word sine of angle 0..255, result is in range -32767..32767
|
||||
Cosine. (floating point version)
|
||||
|
||||
cos8u(x)
|
||||
Fast 8-bit ubyte cosine of angle 0..255, result is in range 0..255
|
||||
@ -666,14 +680,11 @@ cos16u(x)
|
||||
cos16(x)
|
||||
Fast 16-bit word cosine of angle 0..255, result is in range -32767..32767
|
||||
|
||||
abs(x)
|
||||
Absolute value.
|
||||
deg(x)
|
||||
Radians to degrees.
|
||||
|
||||
tan(x)
|
||||
Tangent.
|
||||
|
||||
atan(x)
|
||||
Arctangent.
|
||||
floor (x)
|
||||
Rounds the floating point down to an integer towards minus infinity.
|
||||
|
||||
ln(x)
|
||||
Natural logarithm (base e).
|
||||
@ -681,45 +692,48 @@ ln(x)
|
||||
log2(x)
|
||||
Base 2 logarithm.
|
||||
|
||||
rad(x)
|
||||
Degrees to radians.
|
||||
|
||||
round(x)
|
||||
Rounds the floating point to the closest integer.
|
||||
|
||||
sin(x)
|
||||
Sine. (floating point version)
|
||||
|
||||
sgn(x)
|
||||
Get the sign of the value. Result is -1, 0 or 1 (negative, zero, positive).
|
||||
|
||||
sin8u(x)
|
||||
Fast 8-bit ubyte sine of angle 0..255, result is in range 0..255
|
||||
|
||||
sin8(x)
|
||||
Fast 8-bit byte sine of angle 0..255, result is in range -127..127
|
||||
|
||||
sin16u(x)
|
||||
Fast 16-bit uword sine of angle 0..255, result is in range 0..65535
|
||||
|
||||
sin16(x)
|
||||
Fast 16-bit word sine of angle 0..255, result is in range -32767..32767
|
||||
|
||||
sqrt16(w)
|
||||
16 bit unsigned integer Square root. Result is unsigned byte.
|
||||
|
||||
sqrt(x)
|
||||
Floating point Square root.
|
||||
|
||||
round(x)
|
||||
Rounds the floating point to the closest integer.
|
||||
tan(x)
|
||||
Tangent.
|
||||
|
||||
floor (x)
|
||||
Rounds the floating point down to an integer towards minus infinity.
|
||||
|
||||
ceil(x)
|
||||
Rounds the floating point up to an integer towards positive infinity.
|
||||
Array operations
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
rad(x)
|
||||
Degrees to radians.
|
||||
any(x)
|
||||
1 ('true') if any of the values in the array value x is 'true' (not zero), else 0 ('false')
|
||||
|
||||
deg(x)
|
||||
Radians to degrees.
|
||||
|
||||
max(x)
|
||||
Maximum of the values in the array value x
|
||||
|
||||
min(x)
|
||||
Minimum of the values in the array value x
|
||||
|
||||
sum(x)
|
||||
Sum of the values in the array value x
|
||||
|
||||
sort(array)
|
||||
Sort the array in ascending order (in-place)
|
||||
Note: sorting a floating-point array is not supported right now, as a general sorting routine for this will
|
||||
be extremely slow. Either build one yourself or find another solution that doesn't require sorting
|
||||
floating point values.
|
||||
|
||||
reverse(array)
|
||||
Reverse the values in the array (in-place). Supports all data types including floats.
|
||||
Can be used after sort() to sort an array in descending order.
|
||||
all(x)
|
||||
1 ('true') if all of the values in the array value x are 'true' (not zero), else 0 ('false')
|
||||
|
||||
len(x)
|
||||
Number of values in the array value x, or the number of characters in a string (excluding the size or 0-byte).
|
||||
@ -728,15 +742,89 @@ len(x)
|
||||
length of the string during execution, the value of len(string) may no longer be correct!
|
||||
(use strlen function if you want to dynamically determine the length)
|
||||
|
||||
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.
|
||||
max(x)
|
||||
Maximum of the values in the array value x
|
||||
|
||||
min(x)
|
||||
Minimum of the values in the array value x
|
||||
|
||||
reverse(array)
|
||||
Reverse the values in the array (in-place).
|
||||
Can be used after sort() to sort an array in descending order.
|
||||
|
||||
sum(x)
|
||||
Sum of the values in the array value x
|
||||
|
||||
sort(array)
|
||||
Sort the array in ascending order (in-place)
|
||||
Supported are arrays of bytes or word values.
|
||||
Sorting a floating-point array is not supported right now, as a general sorting routine for this will
|
||||
be extremely slow. Either build one yourself or find another solution that doesn't require sorting.
|
||||
Finally, note that sorting an array with strings in it will not do what you might think;
|
||||
it considers the array as just an array of integer words and sorts the string *pointers* accordingly.
|
||||
Sorting strings alphabetically has to be programmed yourself if you need it.
|
||||
|
||||
|
||||
Strings and memory blocks
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
memcopy(from, to, numbytes)
|
||||
Efficiently copy a number of bytes from a memory location to another.
|
||||
NOTE: 'to' must NOT overlap with 'from', unless it is *before* 'from'.
|
||||
Because this function imposes some overhead to handle the parameters,
|
||||
it is only faster if the number of bytes is larger than a certain threshold.
|
||||
Compare the generated code to see if it was beneficial or not.
|
||||
The most efficient will often be to write a specialized copy routine in assembly yourself!
|
||||
|
||||
memset(address, numbytes, bytevalue)
|
||||
Efficiently set a part of memory to the given (u)byte value.
|
||||
But the most efficient will always be to write a specialized fill routine in assembly yourself!
|
||||
Note that for clearing the screen, very fast specialized subroutines are
|
||||
available in the ``textio`` and ``graphics`` library modules.
|
||||
|
||||
memsetw(address, numwords, wordvalue)
|
||||
Efficiently set a part of memory to the given (u)word value.
|
||||
But the most efficient will always be to write a specialized fill routine in assembly yourself!
|
||||
|
||||
leftstr(source, target, length)
|
||||
Copies the left side of the source string of the given length to target string.
|
||||
It is assumed the target string buffer is large enough to contain the result.
|
||||
Also, you have to make sure yourself that length is smaller or equal to the length of the source string.
|
||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||
|
||||
rightstr(source, target, length)
|
||||
Copies the right side of the source string of the given length to target string.
|
||||
It is assumed the target string buffer is large enough to contain the result.
|
||||
Also, you have to make sure yourself that length is smaller or equal to the length of the source string.
|
||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||
|
||||
strlen(str)
|
||||
Number of bytes in the string. This value is determined during runtime and counts upto
|
||||
the first terminating 0 byte in the string, regardless of the size of the string during compilation time.
|
||||
Don't confuse this with ``len`` and ``sizeof``
|
||||
|
||||
strcmp(string1, string2)
|
||||
Returns -1, 0 or 1 depeding on wether string1 sorts before, equal or after string2.
|
||||
Note that you can also directly compare strings and string values with eachother
|
||||
using ``==``, ``<`` etcetera (it will use strcmp for you under water automatically).
|
||||
|
||||
substr(source, target, start, length)
|
||||
Copies a segment from the source string, starting at the given index,
|
||||
and of the given length to target string.
|
||||
It is assumed the target string buffer is large enough to contain the result.
|
||||
Also, you have to make sure yourself that start and length are within bounds of the strings.
|
||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||
|
||||
strcopy(from, to)
|
||||
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.
|
||||
|
||||
|
||||
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".
|
||||
@ -744,18 +832,15 @@ lsb(x)
|
||||
msb(x)
|
||||
Get the most significant byte of the word x.
|
||||
|
||||
sgn(x)
|
||||
Get the sign of the value. Result is -1, 0 or 1 (negative, zero, positive).
|
||||
|
||||
mkword(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.
|
||||
|
||||
any(x)
|
||||
1 ('true') if any of the values in the array value x is 'true' (not zero), else 0 ('false')
|
||||
.. 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)
|
||||
|
||||
all(x)
|
||||
1 ('true') if all of the values in the array value x are 'true' (not zero), else 0 ('false')
|
||||
|
||||
rnd()
|
||||
returns a pseudo-random byte from 0..255
|
||||
@ -790,51 +875,6 @@ ror2(x)
|
||||
It uses some extra logic to not consider the carry flag as extra rotation bit.
|
||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||
|
||||
memcopy(from, to, numbytes)
|
||||
Efficiently copy a number of bytes (1 - 256) from a memory location to another.
|
||||
NOTE: 'to' must NOT overlap with 'from', unless it is *before* 'from'.
|
||||
Because this function imposes some overhead to handle the parameters,
|
||||
it is only faster if the number of bytes is larger than a certain threshold.
|
||||
Compare the generated code to see if it was beneficial or not.
|
||||
The most efficient will always be to write a specialized copy routine in assembly yourself!
|
||||
|
||||
memset(address, numbytes, bytevalue)
|
||||
Efficiently set a part of memory to the given (u)byte value.
|
||||
But the most efficient will always be to write a specialized fill routine in assembly yourself!
|
||||
Note that for clearing the character screen, very fast specialized subroutines are
|
||||
available in the ``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).
|
||||
|
||||
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).
|
||||
|
||||
swap(x, y)
|
||||
Swap the values of numerical variables (or memory locations) x and y in a fast way.
|
||||
|
||||
set_carry() / clear_carry()
|
||||
Set (or clear) the CPU status register Carry flag. No result value.
|
||||
(translated into ``SEC`` or ``CLC`` cpu instruction)
|
||||
|
||||
set_irqd() / clear_irqd()
|
||||
Set (or clear) the CPU status register Interrupt Disable flag. No result value.
|
||||
(translated into ``SEI`` or ``CLI`` cpu instruction)
|
||||
|
||||
rsave()
|
||||
Saves the CPU registers and the status flags.
|
||||
You can now more or less 'safely' use the registers directly, until you
|
||||
@ -849,10 +889,26 @@ rrestore()
|
||||
read_flags()
|
||||
Returns the current value of the CPU status register.
|
||||
|
||||
exit(returncode)
|
||||
Immediately stops the program and exits it, with the returncode in the A register.
|
||||
Note: custom interrupt handlers remain active unless manually cleared first!
|
||||
sizeof(name)
|
||||
Number of bytes that the object 'name' occupies in memory. This is a constant determined by the data type of
|
||||
the object. For instance, for a variable of type uword, the sizeof is 2.
|
||||
For an 10 element array of floats, it is 50 (on the C-64, where a float is 5 bytes).
|
||||
Note: usually you will be interested in the number of elements in an array, use len() for that.
|
||||
|
||||
set_carry() / clear_carry()
|
||||
Set (or clear) the CPU status register Carry flag. No result value.
|
||||
(translated into ``SEC`` or ``CLC`` cpu instruction)
|
||||
|
||||
set_irqd() / clear_irqd()
|
||||
Set (or clear) the CPU status register Interrupt Disable flag. No result value.
|
||||
(translated into ``SEI`` or ``CLI`` cpu instruction)
|
||||
|
||||
swap(x, y)
|
||||
Swap the values of numerical variables (or memory locations) x and y in a fast way.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
Library routines
|
||||
|
@ -67,7 +67,8 @@ Directives
|
||||
- style ``kernalsafe`` -- use the part of the ZP that is 'free' or only used by BASIC routines,
|
||||
and don't change anything else. This allows full use of KERNAL ROM routines (but not BASIC routines),
|
||||
including default IRQs during normal system operation.
|
||||
When the program exits, a system reset is performed (because BASIC will be in a corrupt state).
|
||||
It's not possible to return cleanly to BASIC when the program exits. The only choice is
|
||||
to perform a system reset. (A ``system_reset`` subroutine is available in the syslib to help you do this)
|
||||
- style ``floatsafe`` -- like the previous one but also reserves the addresses that
|
||||
are required to perform floating point operations (from the BASIC kernel). No clean exit is possible.
|
||||
- style ``basicsafe`` -- the most restricted mode; only use the handful 'free' addresses in the ZP, and don't
|
||||
@ -77,10 +78,11 @@ Directives
|
||||
- style ``full`` -- claim the whole ZP for variables for the program, overwriting everything,
|
||||
except the few addresses mentioned above that are used by the system's IRQ routine.
|
||||
Even though the default IRQ routine is still active, it is impossible to use most BASIC and KERNAL ROM routines.
|
||||
This includes many floating point operations and several utility routines that do I/O, such as ``print_string``.
|
||||
As with ``kernalsafe``, it is not possible to cleanly exit the program, other than to reset the machine.
|
||||
This includes many floating point operations and several utility routines that do I/O, such as ``print``.
|
||||
This option makes programs smaller and faster because even more variables can
|
||||
be stored in the ZP (which allows for more efficient assembly code).
|
||||
It's not possible to return cleanly to BASIC when the program exits. The only choice is
|
||||
to perform a system reset. (A ``system_reset`` subroutine is available in the syslib to help you do this)
|
||||
- style ``dontuse`` -- don't use *any* location in the zeropage.
|
||||
|
||||
Also read :ref:`zeropage`.
|
||||
@ -117,33 +119,39 @@ Directives
|
||||
|
||||
Level: module, block.
|
||||
Sets special compiler options.
|
||||
For a module option, only the ``enable_floats`` option is recognised, which will tell the compiler
|
||||
to deal with floating point numbers (by using various subroutines from the Commodore-64 kernal).
|
||||
Otherwise, floating point support is not enabled.
|
||||
When used in a block with the ``force_output`` option, it will force the block to be outputted
|
||||
in the final program. Can be useful to make sure some
|
||||
data is generated that would otherwise be discarded because it's not referenced (such as sprite data).
|
||||
|
||||
- For a module option, there is ``enable_floats``, which will tell the compiler
|
||||
to deal with floating point numbers (by using various subroutines from the Commodore-64 kernal).
|
||||
Otherwise, floating point support is not enabled. Normally you don't have to use this yourself as
|
||||
importing the ``floats`` library is required anyway and that will enable it for you automatically.
|
||||
- There's also ``no_sysinit`` which cause the resulting program to *not* include
|
||||
the system re-initialization logic of clearing the screen, resetting I/O config etc. You'll have to
|
||||
take care of that yourself. The program will just start running from whatever state the machine is in when the
|
||||
program was launched.
|
||||
- When used in a block with the ``force_output`` option, it will force the block to be outputted
|
||||
in the final program. Can be useful to make sure some
|
||||
data is generated that would otherwise be discarded because it's not referenced (such as sprite data).
|
||||
|
||||
|
||||
.. data:: %asmbinary "<filename>" [, <offset>[, <length>]]
|
||||
|
||||
Level: block.
|
||||
This directive can only be used inside a block.
|
||||
The assembler will include the file as binary bytes at this point, prog8 will not process this at all.
|
||||
The optional offset and length can be used to select a particular piece of the file.
|
||||
The file is located relative to the current working directory!
|
||||
Level: block.
|
||||
This directive can only be used inside a block.
|
||||
The assembler will include the file as binary bytes at this point, prog8 will not process this at all.
|
||||
The optional offset and length can be used to select a particular piece of the file.
|
||||
The file is located relative to the current working directory!
|
||||
|
||||
.. data:: %asminclude "<filename>", "scopelabel"
|
||||
|
||||
Level: block.
|
||||
This directive can only be used inside a block.
|
||||
The assembler will include the file as raw assembly source text at this point,
|
||||
prog8 will not process this at all, with one exception: the labels.
|
||||
The scopelabel argument will be used as a prefix to access the labels from the included source code,
|
||||
otherwise you would risk symbol redefinitions or duplications.
|
||||
If you know what you are doing you can leave it as an empty string to not have a scope prefix.
|
||||
The compiler first looks for the file relative to the same directory as the module containing this statement is in,
|
||||
if the file can't be found there it is searched relative to the current directory.
|
||||
Level: block.
|
||||
This directive can only be used inside a block.
|
||||
The assembler will include the file as raw assembly source text at this point,
|
||||
prog8 will not process this at all, with one exception: the labels.
|
||||
The scopelabel argument will be used as a prefix to access the labels from the included source code,
|
||||
otherwise you would risk symbol redefinitions or duplications.
|
||||
If you know what you are doing you can leave it as an empty string to not have a scope prefix.
|
||||
The compiler first looks for the file relative to the same directory as the module containing this statement is in,
|
||||
if the file can't be found there it is searched relative to the current directory.
|
||||
|
||||
.. data:: %breakpoint
|
||||
|
||||
@ -274,6 +282,7 @@ type identifier type storage size example var declara
|
||||
``word[]`` signed word array depends on value ``word[] myvar = [1, 2, 3, 4]``
|
||||
``uword[]`` unsigned word array depends on value ``uword[] myvar = [1, 2, 3, 4]``
|
||||
``float[]`` floating-point array depends on value ``float[] myvar = [1.1, 2.2, 3.3, 4.4]``
|
||||
``str[]`` array with string ptrs 2*x bytes + strs ``str[] names = ["ally", "pete"]``
|
||||
``str`` string (petscii) varies ``str myvar = "hello."``
|
||||
implicitly terminated by a 0-byte
|
||||
=============== ======================= ================= =========================================
|
||||
@ -391,7 +400,25 @@ After defining a struct you can use the name of the struct as a data type to dec
|
||||
|
||||
Struct variables can be assigned a struct literal value (also in their declaration as initial value)::
|
||||
|
||||
Color rgb = {255, 100, 0} ; curly braces instead of brackets
|
||||
Color rgb = [255, 100, 0] ; note that the value is an array
|
||||
|
||||
|
||||
String
|
||||
^^^^^^
|
||||
|
||||
``"hello"`` is a string translated into the default character encoding (PETSCII)
|
||||
|
||||
``@"hello"`` is a string translated into the alternate character encoding (Screencodes/pokes)
|
||||
|
||||
There are several escape sequences available to put special characters into your string value:
|
||||
|
||||
- ``\\`` - the backslash itself, has to be escaped because it is the escape symbol by itself
|
||||
- ``\n`` - newline character (move cursor down and to beginning of next line)
|
||||
- ``\r`` - carriage return character (more or less the same as newline if printing to the screen)
|
||||
- ``\"`` - quote character (otherwise it would terminate the string)
|
||||
- ``\'`` - apostrophe character (has to be escaped in character literals, is okay inside a string)
|
||||
- ``\uHHHH`` - a unicode codepoint \u0000 - \uffff (16-bit hexadecimal)
|
||||
- ``\xHH`` - 8-bit hex value that will be copied verbatim *without encoding*
|
||||
|
||||
|
||||
Operators
|
||||
@ -478,6 +505,8 @@ takes no parameters. If the subroutine returns a value, usually you assign it t
|
||||
If you're not interested in the return value, prefix the function call with the ``void`` keyword.
|
||||
Otherwise the compiler will warn you about discarding the result of the call.
|
||||
|
||||
Multiple return values
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
Normal subroutines can only return zero or one return values.
|
||||
However, the special ``asmsub`` routines (implemented in assembly code) or ``romsub`` routines
|
||||
(referencing a routine in kernel ROM) can return more than one return value.
|
||||
@ -485,9 +514,16 @@ For example a status in the carry bit and a number in A, or a 16-bit value in A/
|
||||
It is not possible to process the results of a call to these kind of routines
|
||||
directly from the language, because only single value assignments are possible.
|
||||
You can still call the subroutine and not store the results.
|
||||
But if you want to do something with the values it returns, you'll have to write
|
||||
a small block of custom inline assembly that does the call and stores the values
|
||||
appropriately. Don't forget to save/restore the registers if required.
|
||||
|
||||
**There is an exception:** if there's just one return value in a register, and one or more others that are returned
|
||||
as bits in the status register (such as the Carry bit), the compiler allows you to call the subroutine.
|
||||
It will then store the result value in a variable if required, and *keep the status register untouched
|
||||
after the call* so you can use a conditional branch statement for that.
|
||||
Note that this makes no sense inside an expression, so the compiler will still give an error for that.
|
||||
|
||||
If there really are multiple return values (other than a combined 16 bit return value in 2 registers),
|
||||
you'll have to write a small block of custom inline assembly that does the call and stores the values
|
||||
appropriately. Don't forget to save/restore any registers that are modified.
|
||||
|
||||
|
||||
Subroutine definitions
|
||||
|
@ -16,8 +16,9 @@ Currently there are two machines that are supported as compiler target (selectab
|
||||
|
||||
This chapter explains the relevant system details of these machines.
|
||||
|
||||
.. note::
|
||||
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)!
|
||||
.. hint::
|
||||
If you only use standard kernel and prog8 library routines,
|
||||
it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
|
||||
|
||||
|
||||
Memory Model
|
||||
|
@ -2,32 +2,25 @@
|
||||
TODO
|
||||
====
|
||||
|
||||
- get rid of all other TODO's in the code ;-)
|
||||
- move the ldx #$ff | clc | cld from the startup logic into the start() function as first instructions
|
||||
- add an %option that omits the 'system-init' code at the start. Useful to create separate standalone routines that shouldn't re-init the whole machine every time they're called
|
||||
- line-circle-gfx examples are now a few hundred bytes larger than before. Why is that, can it be fixed?
|
||||
- until condition should be able to refer to variables defined IN the do-until block itself.
|
||||
- add support? example? for processing arguments to a sys call : sys 999, 1, 2, "aaa"
|
||||
- make it possible for array literals to not only contain compile time constants
|
||||
- further optimize assignment codegeneration
|
||||
- implement @stack for asmsub parameters
|
||||
- see if we can group some errors together for instance the (now single) errors about unidentified symbols
|
||||
- Cx16 target: support full-screen 640x480 and 320x240 graphics? That requires our own custom graphics routines though to draw lines.
|
||||
- hoist all variable declarations up to the subroutine scope *before* even the constant folding takes place (to avoid undefined symbol errors when referring to a variable from another nested scope in the subroutine)
|
||||
- make it possible to use cpu opcodes such as 'nop' as variable names by prefixing all asm vars with something such as '_'
|
||||
- option to load the built-in library files from a directory instead of the embedded ones (for easier library development/debugging)
|
||||
- see if we can group some errors together for instance the (now single) errors about unidentified symbols
|
||||
- use VIC banking to move up the graphics bitmap memory location. Don't move it under the ROM though as that would require IRQ disabling and memory bank swapping for every bitmap manipulation
|
||||
- add some primitives/support/examples for using custom char sets, copying the default charset.
|
||||
- use VIC banking to move up the graphics bitmap memory location. Move it to $e000 under the kernal rom?
|
||||
- some support for recursive subroutines?
|
||||
- via %option recursive?: allocate all params and local vars on estack, don't allow nested subroutines, can begin by first not allowing any local variables just fixing the parameters
|
||||
- Or via a special recursive call operation that copies the current values of all local vars (including arguments) to the stack, replaces the arguments, jsr subroutine, and after returning copy the stack back to the local variables
|
||||
- get rid of all other TODO's in the code ;-)
|
||||
|
||||
More optimizations
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Add more compiler optimizations to the existing ones.
|
||||
|
||||
- more targeted optimizations for assigment asm code, such as the following:
|
||||
- subroutine calling convention? like: 1 byte arg -> pass in A, 2 bytes -> pass in A+Y, return value likewise.
|
||||
- can such parameter passing to subroutines be optimized to avoid copying?
|
||||
- add a compiler option to not include variable initialization code (useful if the program is expected to run only once, such as a game)
|
||||
the program will then rely solely on the values as they are in memory at the time of program startup.
|
||||
- Also some library routines and code patterns could perhaps be optimized further
|
||||
- further optimize assignment codegeneration, such as the following:
|
||||
- binexpr splitting (beware self-referencing expressions and asm code ballooning though)
|
||||
- detect var->var argument passing to subroutines and avoid the second variable and copying of the value
|
||||
- more optimizations on the language AST level
|
||||
- more optimizations on the final assembly source level
|
||||
- note: subroutine inlining is abandoned because of problems referencing non-local stuff. Can't move everything around.
|
||||
|
840
examples/arithmetic/builtins.p8
Normal file
840
examples/arithmetic/builtins.p8
Normal file
@ -0,0 +1,840 @@
|
||||
%import textio
|
||||
%import floats
|
||||
%import syslib
|
||||
%import test_stack
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
rotations()
|
||||
strings()
|
||||
integers()
|
||||
floatingpoint()
|
||||
|
||||
test_stack.test()
|
||||
}
|
||||
|
||||
sub rotations() {
|
||||
ubyte[] ubarr = [%11000111]
|
||||
uword[] uwarr = [%1100111110101010]
|
||||
|
||||
repeat(10) {
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
txt.print_uwbin(uwarr[0], true)
|
||||
txt.chrout('\n')
|
||||
set_carry()
|
||||
rol(uwarr[0])
|
||||
txt.print_uwbin(uwarr[0], true)
|
||||
txt.chrout('\n')
|
||||
set_carry()
|
||||
rol(uwarr[0])
|
||||
txt.print_uwbin(uwarr[0], true)
|
||||
txt.chrout('\n')
|
||||
set_carry()
|
||||
rol(uwarr[0])
|
||||
txt.print_uwbin(uwarr[0], true)
|
||||
txt.chrout('\n')
|
||||
set_carry()
|
||||
rol(uwarr[0])
|
||||
txt.print_uwbin(uwarr[0], true)
|
||||
txt.chrout('\n')
|
||||
txt.chrout('\n')
|
||||
|
||||
uwarr[0] = %1100111110101010
|
||||
ror(uwarr[0])
|
||||
txt.print_uwbin(uwarr[0], true)
|
||||
txt.chrout('\n')
|
||||
set_carry()
|
||||
ror(uwarr[0])
|
||||
txt.print_uwbin(uwarr[0], true)
|
||||
txt.chrout('\n')
|
||||
set_carry()
|
||||
ror(uwarr[0])
|
||||
txt.print_uwbin(uwarr[0], true)
|
||||
txt.chrout('\n')
|
||||
set_carry()
|
||||
ror(uwarr[0])
|
||||
txt.print_uwbin(uwarr[0], true)
|
||||
txt.chrout('\n')
|
||||
set_carry()
|
||||
ror(uwarr[0])
|
||||
txt.print_uwbin(uwarr[0], true)
|
||||
txt.chrout('\n')
|
||||
txt.chrout('\n')
|
||||
|
||||
clear_carry()
|
||||
txt.print_uwbin(uwarr[0], true)
|
||||
txt.chrout('\n')
|
||||
rol2(uwarr[0])
|
||||
txt.print_uwbin(uwarr[0], true)
|
||||
txt.chrout('\n')
|
||||
rol2(uwarr[0])
|
||||
txt.print_uwbin(uwarr[0], true)
|
||||
txt.chrout('\n')
|
||||
rol2(uwarr[0])
|
||||
txt.print_uwbin(uwarr[0], true)
|
||||
txt.chrout('\n')
|
||||
rol2(uwarr[0])
|
||||
txt.print_uwbin(uwarr[0], true)
|
||||
txt.chrout('\n')
|
||||
txt.chrout('\n')
|
||||
|
||||
txt.print_uwbin(uwarr[0], true)
|
||||
txt.chrout('\n')
|
||||
ror2(uwarr[0])
|
||||
txt.print_uwbin(uwarr[0], true)
|
||||
txt.chrout('\n')
|
||||
ror2(uwarr[0])
|
||||
txt.print_uwbin(uwarr[0], true)
|
||||
txt.chrout('\n')
|
||||
ror2(uwarr[0])
|
||||
txt.print_uwbin(uwarr[0], true)
|
||||
txt.chrout('\n')
|
||||
ror2(uwarr[0])
|
||||
txt.print_uwbin(uwarr[0], true)
|
||||
txt.chrout('\n')
|
||||
txt.chrout('\n')
|
||||
|
||||
txt.print_ubbin(ubarr[0], true)
|
||||
txt.chrout('\n')
|
||||
set_carry()
|
||||
rol(ubarr[0])
|
||||
txt.print_ubbin(ubarr[0], true)
|
||||
txt.chrout('\n')
|
||||
set_carry()
|
||||
rol(ubarr[0])
|
||||
txt.print_ubbin(ubarr[0], true)
|
||||
txt.chrout('\n')
|
||||
set_carry()
|
||||
rol(ubarr[0])
|
||||
txt.print_ubbin(ubarr[0], true)
|
||||
txt.chrout('\n')
|
||||
set_carry()
|
||||
rol(ubarr[0])
|
||||
txt.print_ubbin(ubarr[0], true)
|
||||
txt.chrout('\n')
|
||||
txt.chrout('\n')
|
||||
|
||||
set_carry()
|
||||
ror(ubarr[0])
|
||||
txt.print_ubbin(ubarr[0], true)
|
||||
txt.chrout('\n')
|
||||
set_carry()
|
||||
ror(ubarr[0])
|
||||
txt.print_ubbin(ubarr[0], true)
|
||||
txt.chrout('\n')
|
||||
set_carry()
|
||||
ror(ubarr[0])
|
||||
txt.print_ubbin(ubarr[0], true)
|
||||
txt.chrout('\n')
|
||||
set_carry()
|
||||
ror(ubarr[0])
|
||||
txt.print_ubbin(ubarr[0], true)
|
||||
txt.chrout('\n')
|
||||
set_carry()
|
||||
ror(ubarr[0])
|
||||
txt.print_ubbin(ubarr[0], true)
|
||||
txt.chrout('\n')
|
||||
txt.chrout('\n')
|
||||
|
||||
|
||||
txt.print_ubbin(ubarr[0], true)
|
||||
txt.chrout('\n')
|
||||
rol2(ubarr[0])
|
||||
txt.print_ubbin(ubarr[0], true)
|
||||
txt.chrout('\n')
|
||||
rol2(ubarr[0])
|
||||
txt.print_ubbin(ubarr[0], true)
|
||||
txt.chrout('\n')
|
||||
rol2(ubarr[0])
|
||||
txt.print_ubbin(ubarr[0], true)
|
||||
txt.chrout('\n')
|
||||
rol2(ubarr[0])
|
||||
txt.print_ubbin(ubarr[0], true)
|
||||
txt.chrout('\n')
|
||||
txt.chrout('\n')
|
||||
|
||||
txt.print_ubbin(ubarr[0], true)
|
||||
txt.chrout('\n')
|
||||
ror2(ubarr[0])
|
||||
txt.print_ubbin(ubarr[0], true)
|
||||
txt.chrout('\n')
|
||||
ror2(ubarr[0])
|
||||
txt.print_ubbin(ubarr[0], true)
|
||||
txt.chrout('\n')
|
||||
ror2(ubarr[0])
|
||||
txt.print_ubbin(ubarr[0], true)
|
||||
txt.chrout('\n')
|
||||
ror2(ubarr[0])
|
||||
txt.print_ubbin(ubarr[0], true)
|
||||
txt.chrout('\n')
|
||||
txt.chrout('\n')
|
||||
|
||||
&ubyte membyte = $c000
|
||||
uword addr = $c000
|
||||
|
||||
@(addr) = %10110101
|
||||
txt.print_ubbin(@(addr), true)
|
||||
txt.chrout('\n')
|
||||
set_carry()
|
||||
rol(@(addr))
|
||||
txt.print_ubbin(@(addr), true)
|
||||
txt.chrout('\n')
|
||||
set_carry()
|
||||
rol(@(addr))
|
||||
txt.print_ubbin(@(addr), true)
|
||||
txt.chrout('\n')
|
||||
set_carry()
|
||||
rol(@(addr))
|
||||
txt.print_ubbin(@(addr), true)
|
||||
txt.chrout('\n')
|
||||
set_carry()
|
||||
rol(@(addr))
|
||||
txt.print_ubbin(@(addr), true)
|
||||
txt.chrout('\n')
|
||||
txt.chrout('\n')
|
||||
|
||||
@(addr) = %10110101
|
||||
txt.print_ubbin(@(addr), true)
|
||||
txt.chrout('\n')
|
||||
set_carry()
|
||||
ror(@(addr))
|
||||
txt.print_ubbin(@(addr), true)
|
||||
txt.chrout('\n')
|
||||
set_carry()
|
||||
ror(@(addr))
|
||||
txt.print_ubbin(@(addr), true)
|
||||
txt.chrout('\n')
|
||||
set_carry()
|
||||
ror(@(addr))
|
||||
txt.print_ubbin(@(addr), true)
|
||||
txt.chrout('\n')
|
||||
set_carry()
|
||||
ror(@(addr))
|
||||
txt.print_ubbin(@(addr), true)
|
||||
txt.chrout('\n')
|
||||
txt.chrout('\n')
|
||||
|
||||
@(addr) = %10110101
|
||||
txt.print_ubbin(@(addr), true)
|
||||
txt.chrout('\n')
|
||||
rol2(@(addr))
|
||||
txt.print_ubbin(@(addr), true)
|
||||
txt.chrout('\n')
|
||||
rol2(@(addr))
|
||||
txt.print_ubbin(@(addr), true)
|
||||
txt.chrout('\n')
|
||||
rol2(@(addr))
|
||||
txt.print_ubbin(@(addr), true)
|
||||
txt.chrout('\n')
|
||||
rol2(@(addr))
|
||||
txt.print_ubbin(@(addr), true)
|
||||
txt.chrout('\n')
|
||||
txt.chrout('\n')
|
||||
|
||||
@(addr) = %10110101
|
||||
txt.print_ubbin(@(addr), true)
|
||||
txt.chrout('\n')
|
||||
ror2(@(addr))
|
||||
txt.print_ubbin(@(addr), true)
|
||||
txt.chrout('\n')
|
||||
ror2(@(addr))
|
||||
txt.print_ubbin(@(addr), true)
|
||||
txt.chrout('\n')
|
||||
ror2(@(addr))
|
||||
txt.print_ubbin(@(addr), true)
|
||||
txt.chrout('\n')
|
||||
ror2(@(addr))
|
||||
txt.print_ubbin(@(addr), true)
|
||||
txt.chrout('\n')
|
||||
txt.chrout('\n')
|
||||
|
||||
test_stack.test()
|
||||
|
||||
}
|
||||
|
||||
sub strings() {
|
||||
const uword ADDR = $8400
|
||||
const uword ADDR2 = $8000
|
||||
|
||||
memset(ADDR2, 40*25, '*')
|
||||
memset(ADDR2, 40, '1')
|
||||
memset(ADDR2+24*40, 39, '2')
|
||||
memsetw(ADDR2, 40*25/2, $3132)
|
||||
memsetw(ADDR2, 20, $4142)
|
||||
memsetw(ADDR2+24*40, 19, $4241)
|
||||
memcopy(ADDR2, ADDR, 200)
|
||||
|
||||
str result = "?" *10
|
||||
str s1 = "irmen"
|
||||
str s2 = "hello"
|
||||
str dots = "....."
|
||||
|
||||
ubyte ub
|
||||
byte bb
|
||||
ubyte zero=0
|
||||
|
||||
bb = strcmp(s1, s2)
|
||||
txt.print_b(bb)
|
||||
txt.chrout('\n')
|
||||
bb = strcmp(s2, s1)
|
||||
txt.print_b(bb)
|
||||
txt.chrout('\n')
|
||||
txt.print_ub(s1==s2)
|
||||
txt.chrout('\n')
|
||||
txt.print_ub(s1<s2)
|
||||
txt.chrout('\n')
|
||||
txt.print_ub(s1>s2)
|
||||
txt.chrout('\n')
|
||||
bb = zero+strcmp(s1,s2)*1+zero
|
||||
txt.print_b(bb)
|
||||
txt.chrout('\n')
|
||||
bb = zero+strcmp(s2,s1)*1+zero
|
||||
txt.print_b(bb)
|
||||
txt.chrout('\n')
|
||||
|
||||
ub = strlen(s1)
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
ub = zero+strlen(s1)*1+zero
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
|
||||
leftstr(s1, result, 3)
|
||||
txt.print(result)
|
||||
txt.chrout('\n')
|
||||
leftstr(s1, result, len(s1))
|
||||
txt.print(result)
|
||||
txt.chrout('\n')
|
||||
txt.chrout('\n')
|
||||
|
||||
result = "x"*8
|
||||
rightstr(s2, result, 3)
|
||||
txt.print(result)
|
||||
txt.chrout('\n')
|
||||
rightstr(s2, result, len(s1))
|
||||
txt.print(result)
|
||||
txt.chrout('\n')
|
||||
|
||||
result = "y"*10
|
||||
substr(s2, result, 1, 3)
|
||||
txt.print(result)
|
||||
txt.chrout('\n')
|
||||
|
||||
void strcopy(s2, s1)
|
||||
txt.print_ub(99+strcopy(s2,s1))
|
||||
txt.chrout('\n')
|
||||
|
||||
test_stack.test()
|
||||
|
||||
}
|
||||
|
||||
sub integers() {
|
||||
ubyte[] ubarr = [1,2,3,4,5,0,4,3,2,1, 255, 255, 255]
|
||||
byte[] barr = [1,2,3,4,5,-4,0,-3,2,1, -128, -128, -127]
|
||||
uword[] uwarr = [100,200,300,400,0,500,400,300,200,100]
|
||||
word[] warr = [100,200,300,400,500,0,-400,-300,200,100,-99, -4096]
|
||||
|
||||
ubyte zero=0
|
||||
ubyte ub
|
||||
ubyte ub2
|
||||
byte bb
|
||||
uword uw
|
||||
word ww
|
||||
|
||||
repeat(20) {
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
ub = read_flags()
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
ub = zero+read_flags()*1+zero
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
|
||||
|
||||
ub = rnd()
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
ub = zero+rnd()*1+zero
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
|
||||
uw = rndw()
|
||||
txt.print_uw(uw)
|
||||
txt.chrout('\n')
|
||||
uw = zero+rndw()*1+zero
|
||||
txt.print_uw(uw)
|
||||
txt.chrout('\n')
|
||||
|
||||
|
||||
uw = 50000
|
||||
ub = sqrt16(uw)
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
ub = zero+sqrt16(uw)*1+zero
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
|
||||
bb = -100
|
||||
bb = sgn(bb)
|
||||
txt.print_b(bb)
|
||||
txt.chrout('\n')
|
||||
bb = -100
|
||||
bb = zero+sgn(bb)*1+zero
|
||||
txt.print_b(bb)
|
||||
txt.chrout('\n')
|
||||
|
||||
ub = 100
|
||||
bb = sgn(ub)
|
||||
txt.print_b(bb)
|
||||
txt.chrout('\n')
|
||||
ub = 100
|
||||
bb = zero+sgn(ub)*1+zero
|
||||
txt.print_b(bb)
|
||||
txt.chrout('\n')
|
||||
|
||||
ww = -1000
|
||||
bb = sgn(ww)
|
||||
txt.print_b(bb)
|
||||
txt.chrout('\n')
|
||||
bb = zero+sgn(ww)*1+zero
|
||||
txt.print_b(bb)
|
||||
txt.chrout('\n')
|
||||
|
||||
uw = 1000
|
||||
bb = sgn(uw)
|
||||
txt.print_b(bb)
|
||||
txt.chrout('\n')
|
||||
bb = zero+sgn(uw)*1+zero
|
||||
txt.print_b(bb)
|
||||
txt.chrout('\n')
|
||||
|
||||
ub = 0
|
||||
uw = sin16u(ub)
|
||||
txt.print_uw(uw)
|
||||
txt.chrout('\n')
|
||||
uw = zero+sin16u(ub)*1+zero
|
||||
txt.print_uw(uw)
|
||||
txt.chrout('\n')
|
||||
|
||||
ub = 0
|
||||
uw = cos16u(ub)
|
||||
txt.print_uw(uw)
|
||||
txt.chrout('\n')
|
||||
uw = zero+cos16u(ub)*1+zero
|
||||
txt.print_uw(uw)
|
||||
txt.chrout('\n')
|
||||
|
||||
ub = 0
|
||||
ww = sin16(ub)
|
||||
txt.print_w(ww)
|
||||
txt.chrout('\n')
|
||||
ww = zero+sin16(ub)*1+zero
|
||||
txt.print_w(ww)
|
||||
txt.chrout('\n')
|
||||
|
||||
ub = 0
|
||||
ww = cos16(ub)
|
||||
txt.print_w(ww)
|
||||
txt.chrout('\n')
|
||||
uw = 0
|
||||
ww = zero+cos16(ub)*1+zero
|
||||
txt.print_w(ww)
|
||||
txt.chrout('\n')
|
||||
|
||||
ub2 = 0
|
||||
ub = sin8u(ub2)
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
ub = zero+sin8u(ub2)*1+zero
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
|
||||
ub2 = 0
|
||||
ub = cos8u(ub2)
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
ub = zero+cos8u(ub2)*1+zero
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
|
||||
ub2 = 0
|
||||
bb = sin8(ub2)
|
||||
txt.print_b(bb)
|
||||
txt.chrout('\n')
|
||||
bb = zero+sin8(ub2)*1+zero
|
||||
txt.print_b(bb)
|
||||
txt.chrout('\n')
|
||||
|
||||
ub2 = 0
|
||||
bb = cos8(ub2)
|
||||
txt.print_b(bb)
|
||||
txt.chrout('\n')
|
||||
bb = zero+cos8(ub2)*1+zero
|
||||
txt.print_b(bb)
|
||||
txt.chrout('\n')
|
||||
|
||||
bb = -100
|
||||
bb = abs(bb)
|
||||
txt.print_b(bb)
|
||||
txt.chrout('\n')
|
||||
bb = -100
|
||||
bb = zero+abs(bb)*1+zero
|
||||
txt.print_b(bb)
|
||||
txt.chrout('\n')
|
||||
|
||||
ww = -1000
|
||||
ww = abs(ww)
|
||||
txt.print_w(ww)
|
||||
txt.chrout('\n')
|
||||
ww = -1000
|
||||
ww = zero+abs(ww)*1+zero
|
||||
txt.print_w(ww)
|
||||
txt.chrout('\n')
|
||||
|
||||
ub = min(ubarr)
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
ub = zero+min(ubarr)*1+zero
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
|
||||
bb = min(barr)
|
||||
txt.print_b(bb)
|
||||
txt.chrout('\n')
|
||||
bb = zero+min(barr)*1+zero
|
||||
txt.print_b(bb)
|
||||
txt.chrout('\n')
|
||||
|
||||
uw = min(uwarr)
|
||||
txt.print_uw(uw)
|
||||
txt.chrout('\n')
|
||||
uw = zero+min(uwarr)*1+zero
|
||||
txt.print_uw(uw)
|
||||
txt.chrout('\n')
|
||||
|
||||
ww = min(warr)
|
||||
txt.print_w(ww)
|
||||
txt.chrout('\n')
|
||||
ww = zero+min(warr)*1+zero
|
||||
txt.print_w(ww)
|
||||
txt.chrout('\n')
|
||||
|
||||
ub = max(ubarr)
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
ub = zero+max(ubarr)*1+zero
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
|
||||
bb = max(barr)
|
||||
txt.print_b(bb)
|
||||
txt.chrout('\n')
|
||||
bb = zero+max(barr)*1+zero
|
||||
txt.print_b(bb)
|
||||
txt.chrout('\n')
|
||||
|
||||
uw = max(uwarr)
|
||||
txt.print_uw(uw)
|
||||
txt.chrout('\n')
|
||||
uw = zero+max(uwarr)*1+zero
|
||||
txt.print_uw(uw)
|
||||
txt.chrout('\n')
|
||||
|
||||
ww = max(warr)
|
||||
txt.print_w(ww)
|
||||
txt.chrout('\n')
|
||||
ww = zero+max(warr)*1+zero
|
||||
txt.print_w(ww)
|
||||
txt.chrout('\n')
|
||||
|
||||
ub = any(ubarr)
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
ub = zero+any(ubarr)*1+zero
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
|
||||
ub = any(barr)
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
ub = zero+any(barr)*1+zero
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
|
||||
ub = any(uwarr)
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
ub = zero+any(uwarr)*1+zero
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
|
||||
ub = any(warr)
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
ub = zero+any(warr)*1+zero
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
|
||||
ub = all(ubarr)
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
ub = zero+all(ubarr)*1+zero
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
|
||||
ub = all(barr)
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
ub = zero+all(barr)*1+zero
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
|
||||
ub = all(uwarr)
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
ub = zero+all(uwarr)*1+zero
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
|
||||
ub = all(warr)
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
ub = zero+all(warr)*1+zero
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
|
||||
|
||||
uw = sum(ubarr)
|
||||
txt.print_uw(uw)
|
||||
txt.chrout('\n')
|
||||
uw = zero+sum(ubarr)*1+zero
|
||||
txt.print_uw(uw)
|
||||
txt.chrout('\n')
|
||||
|
||||
ww = sum(barr)
|
||||
txt.print_w(ww)
|
||||
txt.chrout('\n')
|
||||
ww = zero+sum(barr)*1+zero
|
||||
txt.print_w(ww)
|
||||
txt.chrout('\n')
|
||||
|
||||
uw = sum(uwarr)
|
||||
txt.print_uw(uw)
|
||||
txt.chrout('\n')
|
||||
uw = zero+sum(uwarr)*1+zero
|
||||
txt.print_uw(uw)
|
||||
txt.chrout('\n')
|
||||
|
||||
ww = sum(warr)
|
||||
txt.print_w(ww)
|
||||
txt.chrout('\n')
|
||||
ww = zero+sum(warr)*1+zero
|
||||
txt.print_w(ww)
|
||||
txt.chrout('\n')
|
||||
|
||||
sort(ubarr)
|
||||
sort(barr)
|
||||
sort(uwarr)
|
||||
sort(warr)
|
||||
reverse(ubarr)
|
||||
reverse(barr)
|
||||
reverse(uwarr)
|
||||
reverse(warr)
|
||||
|
||||
test_stack.test()
|
||||
}
|
||||
|
||||
sub floatingpoint() {
|
||||
ubyte[] barr = [1,2,3,4,5,0,4,3,2,1]
|
||||
float[] flarr = [1.1, 2.2, 3.3, 0.0, -9.9, 5.5, 4.4]
|
||||
|
||||
ubyte zero=0
|
||||
ubyte ub
|
||||
byte bb
|
||||
uword uw
|
||||
float fl
|
||||
float fzero=0.0
|
||||
|
||||
fl = -9.9
|
||||
fl = abs(fl)
|
||||
floats.print_f(fl)
|
||||
txt.chrout('\n')
|
||||
fl = -9.9
|
||||
fl = fzero+abs(fl)*1.0+fzero
|
||||
floats.print_f(fl)
|
||||
txt.chrout('\n')
|
||||
|
||||
fl = 9.9
|
||||
fl = atan(fl)
|
||||
floats.print_f(fl)
|
||||
txt.chrout('\n')
|
||||
fl = 9.9
|
||||
fl = fzero+atan(fl)*1.0+fzero
|
||||
floats.print_f(fl)
|
||||
txt.chrout('\n')
|
||||
|
||||
fl = -9.9
|
||||
fl = ceil(fl)
|
||||
floats.print_f(fl)
|
||||
txt.chrout('\n')
|
||||
fl = -9.9
|
||||
fl = fzero+ceil(fl)*1.0+fzero
|
||||
floats.print_f(fl)
|
||||
txt.chrout('\n')
|
||||
|
||||
fl = -9.9
|
||||
fl = cos(fl)
|
||||
floats.print_f(fl)
|
||||
txt.chrout('\n')
|
||||
fl = -9.9
|
||||
fl = fzero+cos(fl)*1.0+fzero
|
||||
floats.print_f(fl)
|
||||
txt.chrout('\n')
|
||||
|
||||
fl = -9.9
|
||||
fl = sin(fl)
|
||||
floats.print_f(fl)
|
||||
txt.chrout('\n')
|
||||
fl = -9.9
|
||||
fl = fzero+sin(fl)*1.0+fzero
|
||||
floats.print_f(fl)
|
||||
txt.chrout('\n')
|
||||
|
||||
fl = 9.9
|
||||
fl = tan(fl)
|
||||
floats.print_f(fl)
|
||||
txt.chrout('\n')
|
||||
fl = 9.9
|
||||
fl = fzero+tan(fl)*1.0+fzero
|
||||
floats.print_f(fl)
|
||||
txt.chrout('\n')
|
||||
|
||||
fl = 3.1415927
|
||||
fl = deg(fl)
|
||||
floats.print_f(fl)
|
||||
txt.chrout('\n')
|
||||
fl = 3.1415927
|
||||
fl = fzero+deg(fl)*1.0+fzero
|
||||
floats.print_f(fl)
|
||||
txt.chrout('\n')
|
||||
|
||||
fl = 90
|
||||
fl = rad(fl)
|
||||
floats.print_f(fl)
|
||||
txt.chrout('\n')
|
||||
fl = 90
|
||||
fl = fzero+rad(fl)*1.0+fzero
|
||||
floats.print_f(fl)
|
||||
txt.chrout('\n')
|
||||
|
||||
fl = -9.9
|
||||
fl = floor(fl)
|
||||
floats.print_f(fl)
|
||||
txt.chrout('\n')
|
||||
fl = -9.9
|
||||
fl = fzero+floor(fl)*1.0+fzero
|
||||
floats.print_f(fl)
|
||||
txt.chrout('\n')
|
||||
|
||||
fl = 3.1415927
|
||||
fl = ln(fl)
|
||||
floats.print_f(fl)
|
||||
txt.chrout('\n')
|
||||
fl = 3.1415927
|
||||
fl = fzero+ln(fl)*1.0+fzero
|
||||
floats.print_f(fl)
|
||||
txt.chrout('\n')
|
||||
|
||||
fl = 3.1415927
|
||||
fl = log2(fl)
|
||||
floats.print_f(fl)
|
||||
txt.chrout('\n')
|
||||
fl = 3.1415927
|
||||
fl = fzero+log2(fl)*1.0+fzero
|
||||
floats.print_f(fl)
|
||||
txt.chrout('\n')
|
||||
|
||||
fl = -9.9
|
||||
fl = round(fl)
|
||||
floats.print_f(fl)
|
||||
txt.chrout('\n')
|
||||
fl = -9.9
|
||||
fl = fzero+round(fl)*1.0+fzero
|
||||
floats.print_f(fl)
|
||||
txt.chrout('\n')
|
||||
|
||||
fl = -9.9
|
||||
bb = sgn(fl)
|
||||
txt.print_b(bb)
|
||||
txt.chrout('\n')
|
||||
fl = -9.9
|
||||
bb = zero+sgn(fl)*1+zero
|
||||
txt.print_b(bb)
|
||||
txt.chrout('\n')
|
||||
|
||||
fl = 3.1415927
|
||||
fl = sqrt(fl)
|
||||
floats.print_f(fl)
|
||||
txt.chrout('\n')
|
||||
fl = 3.1415927
|
||||
fl = fzero+sqrt(fl)*1.0+fzero
|
||||
floats.print_f(fl)
|
||||
txt.chrout('\n')
|
||||
|
||||
fl = rndf()
|
||||
floats.print_f(fl)
|
||||
txt.chrout('\n')
|
||||
fl = fzero+rndf()*1.0+fzero
|
||||
floats.print_f(fl)
|
||||
txt.chrout('\n')
|
||||
|
||||
swap(fl, fzero)
|
||||
swap(fzero, fl)
|
||||
|
||||
ub = any(flarr)
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
ub = zero+any(flarr)*1+zero
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
ub = all(flarr)
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
ub = zero+all(flarr)*1+zero
|
||||
txt.print_ub(ub)
|
||||
txt.chrout('\n')
|
||||
|
||||
reverse(flarr)
|
||||
for ub in 0 to len(flarr)-1 {
|
||||
floats.print_f(flarr[ub])
|
||||
txt.chrout(',')
|
||||
}
|
||||
txt.chrout('\n')
|
||||
fl = max(flarr)
|
||||
floats.print_f(fl)
|
||||
txt.chrout('\n')
|
||||
fl = fzero+max(flarr)*1.0+fzero
|
||||
floats.print_f(fl)
|
||||
txt.chrout('\n')
|
||||
fl = min(flarr)
|
||||
floats.print_f(fl)
|
||||
txt.chrout('\n')
|
||||
fl = fzero+min(flarr)*1.0+fzero
|
||||
floats.print_f(fl)
|
||||
txt.chrout('\n')
|
||||
fl = sum(flarr)
|
||||
floats.print_f(fl)
|
||||
txt.chrout('\n')
|
||||
fl = fzero+sum(flarr)*1.0+fzero
|
||||
floats.print_f(fl)
|
||||
txt.chrout('\n')
|
||||
|
||||
test_stack.test()
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
%import floats
|
||||
%import textio
|
||||
%import test_stack
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
@ -31,6 +32,8 @@ main {
|
||||
minus_float(0,0,0)
|
||||
minus_float(2.5,1.5,1.0)
|
||||
minus_float(-1.5,3.5,-5.0)
|
||||
|
||||
test_stack.test()
|
||||
}
|
||||
|
||||
sub minus_ubyte(ubyte a1, ubyte a2, ubyte c) {
|
||||
@ -97,10 +100,11 @@ main {
|
||||
float r = a1-a2
|
||||
if abs(r-c)<0.00001
|
||||
txt.print(" ok ")
|
||||
else
|
||||
else {
|
||||
txt.print("err! ")
|
||||
}
|
||||
|
||||
txt.print("float ")
|
||||
txt.print(" float ")
|
||||
floats.print_f(a1)
|
||||
txt.print(" - ")
|
||||
floats.print_f(a2)
|
||||
|
315
examples/arithmetic/wordbyte.p8
Normal file
315
examples/arithmetic/wordbyte.p8
Normal file
@ -0,0 +1,315 @@
|
||||
%import textio
|
||||
%import syslib
|
||||
%zeropage basicsafe
|
||||
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
repeat 25 {
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
ubyte ub
|
||||
byte bb
|
||||
uword uwsum
|
||||
word wsum
|
||||
|
||||
uwsum = 50000
|
||||
ub=50
|
||||
uwsum += ub
|
||||
ub=250
|
||||
uwsum += ub
|
||||
|
||||
if uwsum==50300
|
||||
txt.print("1 ok\n")
|
||||
else {
|
||||
txt.print("1 fail:")
|
||||
txt.print_uw(uwsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
wsum = -30000
|
||||
bb = 100
|
||||
wsum += bb
|
||||
bb = -50
|
||||
wsum += bb
|
||||
|
||||
if wsum==-29950
|
||||
txt.print("2 ok\n")
|
||||
else {
|
||||
txt.print("2 fail:")
|
||||
txt.print_w(wsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
uwsum = 50000
|
||||
ub=50
|
||||
uwsum -= ub
|
||||
ub=250
|
||||
uwsum -= ub
|
||||
|
||||
if uwsum==49700
|
||||
txt.print("3 ok\n")
|
||||
else {
|
||||
txt.print("3 fail:")
|
||||
txt.print_uw(uwsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
wsum = -30000
|
||||
bb = 100
|
||||
wsum -= bb
|
||||
bb = -50
|
||||
wsum -= bb
|
||||
|
||||
if wsum==-30050
|
||||
txt.print("4 ok\n")
|
||||
else
|
||||
txt.print("4 fail\n")
|
||||
|
||||
|
||||
|
||||
uwsum = 50000
|
||||
bb=50
|
||||
uwsum += bb as uword
|
||||
bb=-100
|
||||
uwsum += bb as uword
|
||||
|
||||
if uwsum==49950
|
||||
txt.print("5 ok\n")
|
||||
else
|
||||
txt.print("5 fail\n")
|
||||
|
||||
uwsum = 50000
|
||||
bb=50
|
||||
uwsum -= bb as uword
|
||||
bb=100
|
||||
uwsum -= bb as uword
|
||||
|
||||
if uwsum==49850
|
||||
txt.print("6 ok\n")
|
||||
else {
|
||||
txt.print("6 fail:")
|
||||
txt.print_uw(uwsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
wsum = -30000
|
||||
ub = 50
|
||||
wsum += ub
|
||||
ub = 250
|
||||
wsum += ub
|
||||
|
||||
if wsum==-29700
|
||||
txt.print("7 ok\n")
|
||||
else {
|
||||
txt.print("7 fail:")
|
||||
txt.print_w(wsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
wsum = -30000
|
||||
ub = 50
|
||||
wsum -= ub
|
||||
ub = 250
|
||||
wsum -= ub
|
||||
|
||||
if wsum==-30300
|
||||
txt.print("8 ok\n")
|
||||
else {
|
||||
txt.print("8 fail:")
|
||||
txt.print_w(wsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
txt.chrout('\n')
|
||||
|
||||
|
||||
|
||||
uwsum = 50000
|
||||
ub=0
|
||||
uwsum += (50+ub)
|
||||
uwsum += (250+ub)
|
||||
|
||||
if uwsum==50300
|
||||
txt.print("1b ok\n")
|
||||
else {
|
||||
txt.print("1b fail:")
|
||||
txt.print_uw(uwsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
bb = 0
|
||||
wsum = -30000
|
||||
wsum += (100+bb)
|
||||
wsum += (-50+bb)
|
||||
|
||||
if wsum==-29950
|
||||
txt.print("2b ok\n")
|
||||
else {
|
||||
txt.print("2b fail:")
|
||||
txt.print_w(wsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
uwsum = 50000
|
||||
uwsum -= (50+ub)
|
||||
uwsum -= (250+ub)
|
||||
|
||||
if uwsum==49700
|
||||
txt.print("3b ok\n")
|
||||
else {
|
||||
txt.print("3b fail:")
|
||||
txt.print_uw(uwsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
wsum = -30000
|
||||
wsum -= (100+bb)
|
||||
wsum -= (-50+bb)
|
||||
|
||||
if wsum==-30050
|
||||
txt.print("4b ok\n")
|
||||
else
|
||||
txt.print("4b fail\n")
|
||||
|
||||
|
||||
uwsum = 50000
|
||||
uwsum += (50+bb) as uword
|
||||
uwsum += (-100+bb) as uword
|
||||
|
||||
if uwsum==49950
|
||||
txt.print("5b ok\n")
|
||||
else
|
||||
txt.print("5b fail\n")
|
||||
|
||||
uwsum = 50000
|
||||
uwsum -= (50+bb) as uword
|
||||
uwsum -= (100+bb) as uword
|
||||
|
||||
if uwsum==49850
|
||||
txt.print("6b ok\n")
|
||||
else {
|
||||
txt.print("6b fail:")
|
||||
txt.print_uw(uwsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
wsum = -30000
|
||||
wsum += (50+ub)
|
||||
wsum += (250+ub)
|
||||
|
||||
if wsum==-29700
|
||||
txt.print("7b ok\n")
|
||||
else {
|
||||
txt.print("7b fail:")
|
||||
txt.print_w(wsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
wsum = -30000
|
||||
wsum -= (50+ub)
|
||||
wsum -= (250+ub)
|
||||
|
||||
if wsum==-30300
|
||||
txt.print("8b ok\n")
|
||||
else {
|
||||
txt.print("8b fail:")
|
||||
txt.print_w(wsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
txt.chrout('\n')
|
||||
|
||||
|
||||
|
||||
uwsum = 50000
|
||||
uwsum += 50
|
||||
uwsum += 250
|
||||
|
||||
if uwsum==50300
|
||||
txt.print("1c ok\n")
|
||||
else {
|
||||
txt.print("1c fail:")
|
||||
txt.print_uw(uwsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
wsum = -30000
|
||||
wsum += 100
|
||||
wsum += -50
|
||||
|
||||
if wsum==-29950
|
||||
txt.print("2c ok\n")
|
||||
else {
|
||||
txt.print("2c fail:")
|
||||
txt.print_w(wsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
uwsum = 50000
|
||||
uwsum -= 50
|
||||
uwsum -= 250
|
||||
|
||||
if uwsum==49700
|
||||
txt.print("3c ok\n")
|
||||
else {
|
||||
txt.print("3c fail:")
|
||||
txt.print_uw(uwsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
wsum = -30000
|
||||
wsum -= 100
|
||||
wsum -= -50
|
||||
|
||||
if wsum==-30050
|
||||
txt.print("4c ok\n")
|
||||
else
|
||||
txt.print("4c fail\n")
|
||||
|
||||
|
||||
uwsum = 50000
|
||||
uwsum += 50 as uword
|
||||
uwsum += -100 as uword
|
||||
|
||||
if uwsum==49950
|
||||
txt.print("5c ok\n")
|
||||
else
|
||||
txt.print("5c fail\n")
|
||||
|
||||
uwsum = 50000
|
||||
uwsum -= 50 as uword
|
||||
uwsum -= 100 as uword
|
||||
|
||||
if uwsum==49850
|
||||
txt.print("6c ok\n")
|
||||
else {
|
||||
txt.print("6c fail:")
|
||||
txt.print_uw(uwsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
wsum = -30000
|
||||
wsum += 50
|
||||
wsum += 250
|
||||
|
||||
if wsum==-29700
|
||||
txt.print("7c ok\n")
|
||||
else {
|
||||
txt.print("7c fail:")
|
||||
txt.print_w(wsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
wsum = -30000
|
||||
wsum -= 50
|
||||
wsum -= 250
|
||||
|
||||
if wsum==-30300
|
||||
txt.print("8c ok\n")
|
||||
else {
|
||||
txt.print("8c fail:")
|
||||
txt.print_w(wsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,8 +1,15 @@
|
||||
%target c64
|
||||
%import syslib
|
||||
%import textio
|
||||
%import test_stack
|
||||
%zeropage basicsafe
|
||||
|
||||
; note: The flickering in the scrolling is caused by the CPU requiring
|
||||
; too long to scroll the characters + the colors in course scroll.
|
||||
; This takes nearly a full frame to accomplish, and causes tearing.
|
||||
; It's very difficult to remove this flicker: it requires double buffering
|
||||
; and splitting the coarse character scrolling on multiple phases...
|
||||
|
||||
main {
|
||||
|
||||
ubyte perform_scroll = false
|
||||
@ -16,13 +23,16 @@ main {
|
||||
|
||||
c64.SCROLX &= %11110111 ; 38 column mode
|
||||
|
||||
c64.set_rasterirq(1) ; enable animation
|
||||
c64.set_rasterirq(200) ; enable animation
|
||||
|
||||
ubyte target_height = 10
|
||||
ubyte active_height = 24
|
||||
ubyte upwards = true
|
||||
|
||||
repeat {
|
||||
;txt.plot(0,0)
|
||||
;test_stack.test()
|
||||
|
||||
ubyte mountain = 223 ; slope upwards
|
||||
if active_height < target_height {
|
||||
active_height++
|
||||
@ -45,7 +55,9 @@ main {
|
||||
|
||||
perform_scroll = false
|
||||
txt.scroll_left(true)
|
||||
if c64.RASTER & 1
|
||||
|
||||
; float the balloon
|
||||
if rnd() & %10000
|
||||
c64.SPXY[1] ++
|
||||
else
|
||||
c64.SPXY[1] --
|
||||
@ -117,10 +129,10 @@ spritedata $0f00 {
|
||||
|
||||
|
||||
irq {
|
||||
ubyte smoothx=7
|
||||
ubyte smoothx=0
|
||||
sub irq() {
|
||||
smoothx = (smoothx-1) & 7
|
||||
main.perform_scroll = smoothx==0
|
||||
main.perform_scroll = smoothx==7
|
||||
c64.SCROLX = (c64.SCROLX & %11111000) | smoothx
|
||||
}
|
||||
}
|
||||
|
103
examples/balls.p8
Normal file
103
examples/balls.p8
Normal file
@ -0,0 +1,103 @@
|
||||
%import textio
|
||||
%import test_stack
|
||||
|
||||
%zeropage basicsafe
|
||||
|
||||
; Note: this program is compatible with C64 and CX16.
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
str input = ".........."
|
||||
ubyte ballCount
|
||||
ubyte[255] BX
|
||||
ubyte[255] BY
|
||||
ubyte[255] BC
|
||||
ubyte[255] DX
|
||||
ubyte[255] DY
|
||||
|
||||
txt.print("number of balls (1-255)? ")
|
||||
void txt.input_chars(input)
|
||||
ballCount = conv.str2ubyte(input)
|
||||
txt.fill_screen(81, 0)
|
||||
|
||||
; Setup Starting Ball Positions
|
||||
ubyte lp
|
||||
for lp in 0 to ballCount-1 {
|
||||
BX[lp] = rnd() % txt.DEFAULT_WIDTH
|
||||
BY[lp] = rnd() % txt.DEFAULT_HEIGHT
|
||||
BC[lp] = rnd() & 15
|
||||
DX[lp] = rnd() & 1
|
||||
DY[lp] = rnd() & 1
|
||||
void rnd()
|
||||
}
|
||||
|
||||
; start clock
|
||||
c64.SETTIM(0,0,0)
|
||||
|
||||
; display balls
|
||||
uword frame
|
||||
for frame in 0 to 999 {
|
||||
; Loop though all balls clearing current spot and setting new spot
|
||||
for lp in 0 to ballCount-1 {
|
||||
|
||||
; Clear existing Location the ball is at
|
||||
txt.setclr(BX[lp], BY[lp], 0)
|
||||
|
||||
if DX[lp] == 0 {
|
||||
if (BX[lp] == 0)
|
||||
{
|
||||
DX[lp] = 1
|
||||
} else {
|
||||
BX[lp]=BX[lp]-1
|
||||
}
|
||||
} else if DX[lp] == 1 {
|
||||
if (BX[lp] == txt.DEFAULT_WIDTH-1)
|
||||
{
|
||||
BX[lp] = txt.DEFAULT_WIDTH-2
|
||||
DX[lp] = 0
|
||||
} else {
|
||||
BX[lp]=BX[lp]+1
|
||||
}
|
||||
}
|
||||
if DY[lp] == 0 {
|
||||
if (BY[lp] == 0)
|
||||
{
|
||||
DY[lp] = 1
|
||||
} else {
|
||||
BY[lp]=BY[lp]-1
|
||||
}
|
||||
} else if DY[lp] == 1 {
|
||||
if (BY[lp] == txt.DEFAULT_HEIGHT-1)
|
||||
{
|
||||
BY[lp] = txt.DEFAULT_HEIGHT-2
|
||||
DY[lp] = 0
|
||||
} else {
|
||||
BY[lp]=BY[lp]+1
|
||||
}
|
||||
}
|
||||
|
||||
; Put the new ball possition
|
||||
txt.setclr(BX[lp], BY[lp], BC[lp])
|
||||
}
|
||||
|
||||
;txt.plot(0,0)
|
||||
;txt.print_uw(frame)
|
||||
}
|
||||
|
||||
; read clock
|
||||
uword jiffies
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr c64.RDTIM
|
||||
sta jiffies
|
||||
stx jiffies+1
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
}}
|
||||
txt.print("\nbenchmark: ")
|
||||
txt.print_uw(jiffies)
|
||||
txt.print(" jiffies for 1000 frames.\n")
|
||||
|
||||
; test_stack.test()
|
||||
}
|
||||
}
|
85
examples/charset.p8
Normal file
85
examples/charset.p8
Normal file
@ -0,0 +1,85 @@
|
||||
%target c64
|
||||
%import syslib
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
%option no_sysinit
|
||||
|
||||
; Create a custom character set on the C64.
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
txt.color(1)
|
||||
txt.print("creating charset...\n")
|
||||
charset.make_custom_charset()
|
||||
|
||||
; activate the new charset in RAM
|
||||
ubyte block = c64.CIA2PRA
|
||||
const ubyte PAGE1 = ((c64.Screen >> 6) & $F0) | ((charset.CHARSET >> 10) & $0E)
|
||||
|
||||
c64.CIA2PRA = (block & $FC) | (lsb(c64.Screen >> 14) ^ $03)
|
||||
c64.VMCSB = PAGE1
|
||||
|
||||
txt.print("\n @ @ @ @\n")
|
||||
}
|
||||
}
|
||||
|
||||
charset {
|
||||
const uword CHARSET = $2000
|
||||
|
||||
sub copy_rom_charset() {
|
||||
; copies the charset from ROM to RAM so we can modify it
|
||||
|
||||
set_irqd()
|
||||
ubyte bank = @($0001)
|
||||
@($0001) = bank & %11111011 ; enable CHAREN, so the character rom accessible at $d000
|
||||
memcopy($d000, CHARSET, 256*8*2) ; copy the charset to RAM
|
||||
|
||||
@($0001) = bank ; reset previous memory banking
|
||||
clear_irqd()
|
||||
}
|
||||
|
||||
sub make_custom_charset() {
|
||||
copy_rom_charset()
|
||||
|
||||
; make all characters italic
|
||||
ubyte c
|
||||
for c in 0 to 255 {
|
||||
uword ptr = CHARSET + c*$0008
|
||||
@(ptr) >>= 2
|
||||
@(ptr+1) >>= 2
|
||||
@(ptr+2) >>= 1
|
||||
@(ptr+3) >>= 1
|
||||
;@(ptr+4) >>= 0
|
||||
;@(ptr+5) >>= 0
|
||||
@(ptr+6) <<= 1
|
||||
@(ptr+7) <<= 1
|
||||
|
||||
ptr = CHARSET + 256*8 + c*$0008
|
||||
@(ptr) >>= 2
|
||||
@(ptr+1) >>= 2
|
||||
@(ptr+2) >>= 1
|
||||
@(ptr+3) >>= 1
|
||||
;@(ptr+4) >>= 0
|
||||
;@(ptr+5) >>= 0
|
||||
@(ptr+6) <<= 1
|
||||
@(ptr+7) <<= 1
|
||||
}
|
||||
|
||||
; add a smiley over the '@'
|
||||
|
||||
ubyte[] smiley = [
|
||||
%00111100,
|
||||
%01000010,
|
||||
%10100101,
|
||||
%10000001,
|
||||
%10100101,
|
||||
%10011001,
|
||||
%01000010,
|
||||
%00111100
|
||||
]
|
||||
|
||||
memcopy(smiley, CHARSET, len(smiley))
|
||||
|
||||
}
|
||||
}
|
1008
examples/cmp/comparisons.p8
Normal file
1008
examples/cmp/comparisons.p8
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,95 +0,0 @@
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
|
||||
; Note: this program is compatible with C64 and CX16.
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
|
||||
byte v1
|
||||
byte v2
|
||||
ubyte cr
|
||||
|
||||
txt.print("signed byte ")
|
||||
|
||||
cr=v1==v2
|
||||
cr=v1==v2
|
||||
cr=v1==v2
|
||||
cr=v1!=v2
|
||||
cr=v1!=v2
|
||||
cr=v1!=v2
|
||||
cr=v1<v2
|
||||
cr=v1<v2
|
||||
cr=v1<v2
|
||||
cr=v1<v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1<=v2
|
||||
cr=v1<=v2
|
||||
cr=v1<=v2
|
||||
cr=v1<=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
|
||||
; comparisons:
|
||||
v1=-20
|
||||
v2=125
|
||||
txt.print("v1=-20, v2=125\n")
|
||||
compare()
|
||||
|
||||
v1=80
|
||||
v2=80
|
||||
txt.print("v1 = v2 = 80\n")
|
||||
compare()
|
||||
|
||||
v1=20
|
||||
v2=-111
|
||||
txt.print("v1=20, v2=-111\n")
|
||||
compare()
|
||||
|
||||
return
|
||||
|
||||
sub compare() {
|
||||
txt.print(" == != < > <= >=\n")
|
||||
|
||||
if v1==v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
if v1!=v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1<v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1>v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1<=v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1>=v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
c64.CHROUT('\n')
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,112 +0,0 @@
|
||||
%import textio
|
||||
%import floats
|
||||
%zeropage basicsafe
|
||||
|
||||
; Note: this program is compatible with C64 and CX16.
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
|
||||
float v1
|
||||
float v2
|
||||
ubyte cr
|
||||
|
||||
txt.print("floating point ")
|
||||
|
||||
cr=v1==v2
|
||||
cr=v1==v2
|
||||
cr=v1==v2
|
||||
cr=v1!=v2
|
||||
cr=v1!=v2
|
||||
cr=v1!=v2
|
||||
cr=v1<v2
|
||||
cr=v1<v2
|
||||
cr=v1<v2
|
||||
cr=v1<v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1<=v2
|
||||
cr=v1<=v2
|
||||
cr=v1<=v2
|
||||
cr=v1<=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
|
||||
; comparisons:
|
||||
v1=20
|
||||
v2=666.66
|
||||
txt.print("v1=20, v2=666.66\n")
|
||||
compare()
|
||||
|
||||
v1=-20
|
||||
v2=666.66
|
||||
txt.print("v1=-20, v2=666.66\n")
|
||||
compare()
|
||||
|
||||
v1=666.66
|
||||
v2=555.55
|
||||
txt.print("v1=666.66, v2=555.55\n")
|
||||
compare()
|
||||
|
||||
v1=3.1415
|
||||
v2=-3.1415
|
||||
txt.print("v1 = 3.1415, v2 = -3.1415\n")
|
||||
compare()
|
||||
|
||||
v1=3.1415
|
||||
v2=3.1415
|
||||
txt.print("v1 = v2 = 3.1415\n")
|
||||
compare()
|
||||
|
||||
v1=0
|
||||
v2=0
|
||||
txt.print("v1 = v2 = 0\n")
|
||||
compare()
|
||||
|
||||
return
|
||||
|
||||
sub compare() {
|
||||
txt.print(" == != < > <= >=\n")
|
||||
|
||||
if v1==v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
if v1!=v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1<v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1>v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1<=v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1>=v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
c64.CHROUT('\n')
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
|
||||
; Note: this program is compatible with C64 and CX16.
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
|
||||
ubyte v1
|
||||
ubyte v2
|
||||
ubyte cr
|
||||
|
||||
txt.print("unsigned byte ")
|
||||
|
||||
cr=v1==v2
|
||||
cr=v1==v2
|
||||
cr=v1==v2
|
||||
cr=v1!=v2
|
||||
cr=v1!=v2
|
||||
cr=v1!=v2
|
||||
cr=v1<v2
|
||||
cr=v1<v2
|
||||
cr=v1<v2
|
||||
cr=v1<v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1<=v2
|
||||
cr=v1<=v2
|
||||
cr=v1<=v2
|
||||
cr=v1<=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
|
||||
; comparisons:
|
||||
v1=20
|
||||
v2=199
|
||||
txt.print("v1=20, v2=199\n")
|
||||
compare()
|
||||
|
||||
v1=80
|
||||
v2=80
|
||||
txt.print("v1 = v2 = 80\n")
|
||||
compare()
|
||||
|
||||
v1=220
|
||||
v2=10
|
||||
txt.print("v1=220, v2=10\n")
|
||||
compare()
|
||||
|
||||
return
|
||||
|
||||
sub compare() {
|
||||
txt.print(" == != < > <= >=\n")
|
||||
|
||||
if v1==v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
if v1!=v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1<v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1>v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1<=v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1>=v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
c64.CHROUT('\n')
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,125 +0,0 @@
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
|
||||
; Note: this program is compatible with C64 and CX16.
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
|
||||
uword v1
|
||||
uword v2
|
||||
ubyte cr
|
||||
|
||||
txt.print("unsigned word ")
|
||||
|
||||
cr=v1==v2
|
||||
cr=v1==v2
|
||||
cr=v1==v2
|
||||
cr=v1!=v2
|
||||
cr=v1!=v2
|
||||
cr=v1!=v2
|
||||
cr=v1<v2
|
||||
cr=v1<v2
|
||||
cr=v1<v2
|
||||
cr=v1<v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1<=v2
|
||||
cr=v1<=v2
|
||||
cr=v1<=v2
|
||||
cr=v1<=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
|
||||
; comparisons:
|
||||
v1=20
|
||||
v2=$00aa
|
||||
txt.print("v1=20, v2=$00aa\n")
|
||||
compare()
|
||||
|
||||
v1=20
|
||||
v2=$ea00
|
||||
txt.print("v1=20, v2=$ea00\n")
|
||||
compare()
|
||||
|
||||
v1=$c400
|
||||
v2=$22
|
||||
txt.print("v1=$c400, v2=$22\n")
|
||||
compare()
|
||||
|
||||
v1=$c400
|
||||
v2=$2a00
|
||||
txt.print("v1=$c400, v2=$2a00\n")
|
||||
compare()
|
||||
|
||||
v1=$c433
|
||||
v2=$2a00
|
||||
txt.print("v1=$c433, v2=$2a00\n")
|
||||
compare()
|
||||
|
||||
v1=$c433
|
||||
v2=$2aff
|
||||
txt.print("v1=$c433, v2=$2aff\n")
|
||||
compare()
|
||||
|
||||
v1=$aabb
|
||||
v2=$aabb
|
||||
txt.print("v1 = v2 = aabb\n")
|
||||
compare()
|
||||
|
||||
v1=$aa00
|
||||
v2=$aa00
|
||||
txt.print("v1 = v2 = aa00\n")
|
||||
compare()
|
||||
|
||||
v1=$aa
|
||||
v2=$aa
|
||||
txt.print("v1 = v2 = aa\n")
|
||||
compare()
|
||||
|
||||
return
|
||||
|
||||
sub compare() {
|
||||
txt.print(" == != < > <= >=\n")
|
||||
|
||||
if v1==v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
if v1!=v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1<v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1>v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1<=v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1>=v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
c64.CHROUT('\n')
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,161 +0,0 @@
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
|
||||
; Note: this program is compatible with C64 and CX16.
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
|
||||
word v1
|
||||
word v2
|
||||
ubyte cr
|
||||
|
||||
txt.print("signed word ")
|
||||
|
||||
cr=v1==v2
|
||||
cr=v1==v2
|
||||
cr=v1==v2
|
||||
cr=v1!=v2
|
||||
cr=v1!=v2
|
||||
cr=v1!=v2
|
||||
cr=v1<v2
|
||||
cr=v1<v2
|
||||
cr=v1<v2
|
||||
cr=v1<v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1<=v2
|
||||
cr=v1<=v2
|
||||
cr=v1<=v2
|
||||
cr=v1<=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
|
||||
; comparisons:
|
||||
v1=20
|
||||
v2=$00aa
|
||||
txt.print("v1=20, v2=$00aa\n")
|
||||
compare()
|
||||
|
||||
v1=20
|
||||
v2=$7a00
|
||||
txt.print("v1=20, v2=$7a00\n")
|
||||
compare()
|
||||
|
||||
v1=$7400
|
||||
v2=$22
|
||||
txt.print("v1=$7400, v2=$22\n")
|
||||
compare()
|
||||
|
||||
v1=$7400
|
||||
v2=$2a00
|
||||
txt.print("v1=$7400, v2=$2a00\n")
|
||||
compare()
|
||||
|
||||
v1=$7433
|
||||
v2=$2a00
|
||||
txt.print("v1=$7433, v2=$2a00\n")
|
||||
compare()
|
||||
|
||||
v1=$7433
|
||||
v2=$2aff
|
||||
txt.print("v1=$7433, v2=$2aff\n")
|
||||
compare()
|
||||
|
||||
; with negative numbers:
|
||||
v1=-512
|
||||
v2=$00aa
|
||||
txt.print("v1=-512, v2=$00aa\n")
|
||||
compare()
|
||||
|
||||
v1=-512
|
||||
v2=$7a00
|
||||
txt.print("v1=-512, v2=$7a00\n")
|
||||
compare()
|
||||
|
||||
v1=$7400
|
||||
v2=-512
|
||||
txt.print("v1=$7400, v2=-512\n")
|
||||
compare()
|
||||
|
||||
v1=-20000
|
||||
v2=-1000
|
||||
txt.print("v1=-20000, v2=-1000\n")
|
||||
compare()
|
||||
|
||||
v1=-1000
|
||||
v2=-20000
|
||||
txt.print("v1=-1000, v2=-20000\n")
|
||||
compare()
|
||||
|
||||
v1=-1
|
||||
v2=32767
|
||||
txt.print("v1=-1, v2=32767\n")
|
||||
compare()
|
||||
|
||||
v1=32767
|
||||
v2=-1
|
||||
txt.print("v1=32767, v2=-1\n")
|
||||
compare()
|
||||
|
||||
v1=$7abb
|
||||
v2=$7abb
|
||||
txt.print("v1 = v2 = 7abb\n")
|
||||
compare()
|
||||
|
||||
v1=$7a00
|
||||
v2=$7a00
|
||||
txt.print("v1 = v2 = 7a00\n")
|
||||
compare()
|
||||
|
||||
v1=$aa
|
||||
v2=$aa
|
||||
txt.print("v1 = v2 = aa\n")
|
||||
compare()
|
||||
|
||||
return
|
||||
|
||||
sub compare() {
|
||||
txt.print(" == != < > <= >=\n")
|
||||
|
||||
if v1==v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
if v1!=v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1<v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1>v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1<=v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1>=v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
c64.CHROUT('\n')
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
913
examples/cmp/word_comps.p8
Normal file
913
examples/cmp/word_comps.p8
Normal file
@ -0,0 +1,913 @@
|
||||
%import textio
|
||||
%import floats
|
||||
%zeropage basicsafe
|
||||
%import test_stack
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
word_less()
|
||||
word_lessequal()
|
||||
word_greaterequal()
|
||||
word_greater()
|
||||
uword_lessequal()
|
||||
}
|
||||
|
||||
sub uword_lessequal() {
|
||||
uword lessvar
|
||||
uword comparevar
|
||||
|
||||
txt.print("uword <=\n")
|
||||
|
||||
txt.print_uw(65535)
|
||||
txt.chrout('\n')
|
||||
check_lesseq_uw(0, 65535)
|
||||
txt.print_uw(0)
|
||||
txt.chrout('\n')
|
||||
check_not_lesseq_uw(65535, 0)
|
||||
|
||||
comparevar = 65535
|
||||
txt.print_uw(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in comparevar downto 0 {
|
||||
check_lesseq_uw(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 65535-2
|
||||
txt.print_uw(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in comparevar downto 0 {
|
||||
check_lesseq_uw(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 65535-254
|
||||
txt.print_uw(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in comparevar downto 0 {
|
||||
check_lesseq_uw(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 65535-255
|
||||
txt.print_uw(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in comparevar downto 0 {
|
||||
check_lesseq_uw(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 65535-256
|
||||
txt.print_uw(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in comparevar downto 0 {
|
||||
check_lesseq_uw(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 65535-5000
|
||||
txt.print_uw(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in comparevar downto 0 {
|
||||
check_lesseq_uw(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 32769
|
||||
txt.print_uw(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in comparevar downto 0 {
|
||||
check_lesseq_uw(lessvar, comparevar)
|
||||
}
|
||||
|
||||
|
||||
comparevar = 32768
|
||||
txt.print_uw(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in 65535 downto comparevar+1 {
|
||||
check_not_lesseq_uw(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 1
|
||||
txt.print_uw(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in 65535 downto comparevar+1 {
|
||||
check_not_lesseq_uw(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 0
|
||||
txt.print_uw(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in 65535 downto comparevar+1 {
|
||||
check_not_lesseq_uw(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 11111
|
||||
txt.print_uw(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in 65535 downto comparevar+1 {
|
||||
check_not_lesseq_uw(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 255
|
||||
txt.print_uw(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in 65535 downto comparevar+1 {
|
||||
check_not_lesseq_uw(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 256
|
||||
txt.print_uw(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in 65535 downto comparevar+1 {
|
||||
check_not_lesseq_uw(lessvar, comparevar)
|
||||
}
|
||||
|
||||
|
||||
test_stack.test()
|
||||
return
|
||||
|
||||
sub check_lesseq_uw(uword w1, uword w2) {
|
||||
uword zero = 0
|
||||
ubyte error=0
|
||||
|
||||
ubyte ub = w1<=w2
|
||||
if not ub {
|
||||
error++
|
||||
txt.print("ub!")
|
||||
}
|
||||
|
||||
if w1<=(w2+zero) {
|
||||
zero = 0 ; dummy
|
||||
} else {
|
||||
error++
|
||||
txt.print("c!")
|
||||
}
|
||||
|
||||
if error {
|
||||
txt.print(" ")
|
||||
txt.print_uw(w1)
|
||||
txt.print(" <= ")
|
||||
txt.print_uw(w2)
|
||||
txt.chrout('\n')
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
sub check_not_lesseq_uw(uword w1, uword w2) {
|
||||
uword zero = 0
|
||||
ubyte error=0
|
||||
|
||||
ubyte ub = w1<=w2
|
||||
if ub {
|
||||
error++
|
||||
txt.print("ub!")
|
||||
}
|
||||
|
||||
if w1<=(w2+zero) {
|
||||
error++
|
||||
txt.print("c!")
|
||||
} else {
|
||||
zero = 0 ; dummy
|
||||
}
|
||||
|
||||
if error {
|
||||
txt.print(" ")
|
||||
txt.print_uw(w1)
|
||||
txt.print(" not <= ")
|
||||
txt.print_uw(w2)
|
||||
txt.chrout('\n')
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub word_greater() {
|
||||
word biggervar
|
||||
word comparevar
|
||||
|
||||
txt.print("word >\n")
|
||||
|
||||
txt.print_w(-32767)
|
||||
txt.chrout('\n')
|
||||
check_greater_w(32767, -32767)
|
||||
txt.print_w(32766)
|
||||
txt.chrout('\n')
|
||||
check_not_greater_w(-32766, 32766)
|
||||
|
||||
comparevar = 32765
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for biggervar in comparevar downto -32768 {
|
||||
check_not_greater_w(biggervar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = -1
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for biggervar in comparevar downto -32768 {
|
||||
check_not_greater_w(biggervar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 0
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for biggervar in comparevar downto -32768 {
|
||||
check_not_greater_w(biggervar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 11111
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for biggervar in comparevar downto -32768 {
|
||||
check_not_greater_w(biggervar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 0
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for biggervar in 32767 downto comparevar+1 {
|
||||
check_greater_w(biggervar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = -2
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for biggervar in 32767 downto comparevar+1 {
|
||||
check_greater_w(biggervar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = -254
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for biggervar in 32767 downto comparevar+1 {
|
||||
check_greater_w(biggervar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = -255
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for biggervar in 32767 downto comparevar+1 {
|
||||
check_greater_w(biggervar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = -256
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for biggervar in 32767 downto comparevar+1 {
|
||||
check_greater_w(biggervar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = -5000
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for biggervar in 32767 downto comparevar+1 {
|
||||
check_greater_w(biggervar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 1
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for biggervar in 32767 downto comparevar+1 {
|
||||
check_greater_w(biggervar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 255
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for biggervar in 32767 downto comparevar+1 {
|
||||
check_greater_w(biggervar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 256
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for biggervar in 32767 downto comparevar+1 {
|
||||
check_greater_w(biggervar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 257
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for biggervar in 32767 downto comparevar+1 {
|
||||
check_greater_w(biggervar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 32760
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for biggervar in 32767 downto comparevar+1 {
|
||||
check_greater_w(biggervar, comparevar)
|
||||
}
|
||||
|
||||
|
||||
test_stack.test()
|
||||
return
|
||||
|
||||
sub check_greater_w(word w1, word w2) {
|
||||
word zero = 0
|
||||
ubyte error=0
|
||||
|
||||
ubyte ub = w1>(w2+zero)
|
||||
if not ub {
|
||||
error++
|
||||
txt.print("ubz!")
|
||||
}
|
||||
|
||||
ub = w1>w2
|
||||
if not ub {
|
||||
error++
|
||||
txt.print("ub!")
|
||||
}
|
||||
|
||||
if w1>(w2+zero) {
|
||||
zero = 0 ; dummy
|
||||
} else {
|
||||
error++
|
||||
txt.print("c!")
|
||||
}
|
||||
|
||||
if error {
|
||||
txt.print(" ")
|
||||
txt.print_w(w1)
|
||||
txt.print(" > ")
|
||||
txt.print_w(w2)
|
||||
txt.chrout('\n')
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
sub check_not_greater_w(word w1, word w2) {
|
||||
word zero = 0
|
||||
ubyte error=0
|
||||
|
||||
ubyte ub = w1>w2
|
||||
if ub {
|
||||
error++
|
||||
txt.print("ub!")
|
||||
}
|
||||
|
||||
if w1>(w2+zero) {
|
||||
error++
|
||||
txt.print("c!")
|
||||
} else {
|
||||
zero = 0 ; dummy
|
||||
}
|
||||
|
||||
if w1>w2 {
|
||||
error++
|
||||
txt.print("c2!")
|
||||
} else {
|
||||
zero = 0 ; dummy
|
||||
}
|
||||
|
||||
if error {
|
||||
txt.print(" ")
|
||||
txt.print_w(w1)
|
||||
txt.print(" not > ")
|
||||
txt.print_w(w2)
|
||||
txt.chrout('\n')
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub word_greaterequal() {
|
||||
word biggervar
|
||||
word comparevar
|
||||
|
||||
txt.print("word >=\n")
|
||||
|
||||
txt.print_w(-32767)
|
||||
txt.chrout('\n')
|
||||
check_greatereq_w(32767, -32767)
|
||||
txt.print_w(32766)
|
||||
txt.chrout('\n')
|
||||
check_not_greatereq_w(-32766, 32766)
|
||||
|
||||
comparevar = 32765
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for biggervar in comparevar-1 downto -32768 {
|
||||
check_not_greatereq_w(biggervar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = -1
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for biggervar in comparevar-1 downto -32768 {
|
||||
check_not_greatereq_w(biggervar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 0
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for biggervar in comparevar-1 downto -32768 {
|
||||
check_not_greatereq_w(biggervar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 11111
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for biggervar in comparevar-1 downto -32768 {
|
||||
check_not_greatereq_w(biggervar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 0
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for biggervar in 32767 downto comparevar {
|
||||
check_greatereq_w(biggervar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = -2
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for biggervar in 32767 downto comparevar {
|
||||
check_greatereq_w(biggervar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = -254
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for biggervar in 32767 downto comparevar {
|
||||
check_greatereq_w(biggervar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = -255
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for biggervar in 32767 downto comparevar {
|
||||
check_greatereq_w(biggervar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = -256
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for biggervar in 32767 downto comparevar {
|
||||
check_greatereq_w(biggervar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = -5000
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for biggervar in 32767 downto comparevar {
|
||||
check_greatereq_w(biggervar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 1
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for biggervar in 32767 downto comparevar {
|
||||
check_greatereq_w(biggervar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 255
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for biggervar in 32767 downto comparevar {
|
||||
check_greatereq_w(biggervar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 256
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for biggervar in 32767 downto comparevar {
|
||||
check_greatereq_w(biggervar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 257
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for biggervar in 32767 downto comparevar {
|
||||
check_greatereq_w(biggervar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 32767
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for biggervar in 32767 downto comparevar {
|
||||
check_greatereq_w(biggervar, comparevar)
|
||||
}
|
||||
|
||||
|
||||
test_stack.test()
|
||||
return
|
||||
|
||||
sub check_greatereq_w(word w1, word w2) {
|
||||
word zero = 0
|
||||
ubyte error=0
|
||||
|
||||
ubyte ub = w1>=(w2+zero)
|
||||
if not ub {
|
||||
error++
|
||||
txt.print("ubz!")
|
||||
}
|
||||
|
||||
ub = w1>=w2
|
||||
if not ub {
|
||||
error++
|
||||
txt.print("ub!")
|
||||
}
|
||||
|
||||
if w1>=(w2+zero) {
|
||||
zero = 0 ; dummy
|
||||
} else {
|
||||
error++
|
||||
txt.print("c!")
|
||||
}
|
||||
|
||||
if error {
|
||||
txt.print(" ")
|
||||
txt.print_w(w1)
|
||||
txt.print(" >= ")
|
||||
txt.print_w(w2)
|
||||
txt.chrout('\n')
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
sub check_not_greatereq_w(word w1, word w2) {
|
||||
word zero = 0
|
||||
ubyte error=0
|
||||
|
||||
ubyte ub = w1>=w2
|
||||
if ub {
|
||||
error++
|
||||
txt.print("ub!")
|
||||
}
|
||||
|
||||
if w1>=(w2+zero) {
|
||||
error++
|
||||
txt.print("c!")
|
||||
} else {
|
||||
zero = 0 ; dummy
|
||||
}
|
||||
|
||||
if w1>=w2 {
|
||||
error++
|
||||
txt.print("c2!")
|
||||
} else {
|
||||
zero = 0 ; dummy
|
||||
}
|
||||
|
||||
if error {
|
||||
txt.print(" ")
|
||||
txt.print_w(w1)
|
||||
txt.print(" not >= ")
|
||||
txt.print_w(w2)
|
||||
txt.chrout('\n')
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub word_lessequal() {
|
||||
word lessvar
|
||||
word comparevar
|
||||
|
||||
txt.print("word <=\n")
|
||||
|
||||
txt.print_w(32767)
|
||||
txt.chrout('\n')
|
||||
check_lesseq_w(-32767, 32767)
|
||||
txt.print_w(-32767)
|
||||
txt.chrout('\n')
|
||||
check_not_lesseq_w(32767, -32767)
|
||||
|
||||
comparevar = 0
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in comparevar downto -32768 {
|
||||
check_lesseq_w(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = -2
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in comparevar downto -32768 {
|
||||
check_lesseq_w(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = -254
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in comparevar downto -32768 {
|
||||
check_lesseq_w(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = -255
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in comparevar downto -32768 {
|
||||
check_lesseq_w(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = -256
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in comparevar downto -32768 {
|
||||
check_lesseq_w(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = -5000
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in comparevar downto -32768 {
|
||||
check_lesseq_w(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 1
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in comparevar downto -32768 {
|
||||
check_lesseq_w(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 255
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in comparevar downto -32768 {
|
||||
check_lesseq_w(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 256
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in comparevar downto -32768 {
|
||||
check_lesseq_w(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 257
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in comparevar downto -32768 {
|
||||
check_lesseq_w(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 32767
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in comparevar downto -32768 {
|
||||
check_lesseq_w(lessvar, comparevar)
|
||||
}
|
||||
|
||||
|
||||
comparevar = -32768
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in 32766 downto comparevar+1 {
|
||||
check_not_lesseq_w(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = -1
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in 32766 downto comparevar+1 {
|
||||
check_not_lesseq_w(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 0
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in 32766 downto comparevar+1 {
|
||||
check_not_lesseq_w(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 11111
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in 32766 downto comparevar+1 {
|
||||
check_not_lesseq_w(lessvar, comparevar)
|
||||
}
|
||||
|
||||
test_stack.test()
|
||||
return
|
||||
|
||||
sub check_lesseq_w(word w1, word w2) {
|
||||
word zero = 0
|
||||
ubyte error=0
|
||||
|
||||
ubyte ub = w1<=w2
|
||||
if not ub {
|
||||
error++
|
||||
txt.print("ub!")
|
||||
}
|
||||
|
||||
if w1<=(w2+zero) {
|
||||
zero = 0 ; dummy
|
||||
} else {
|
||||
error++
|
||||
txt.print("c!")
|
||||
}
|
||||
|
||||
if error {
|
||||
txt.print(" ")
|
||||
txt.print_w(w1)
|
||||
txt.print(" <= ")
|
||||
txt.print_w(w2)
|
||||
txt.chrout('\n')
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
sub check_not_lesseq_w(word w1, word w2) {
|
||||
word zero = 0
|
||||
ubyte error=0
|
||||
|
||||
ubyte ub = w1<=w2
|
||||
if ub {
|
||||
error++
|
||||
txt.print("ub!")
|
||||
}
|
||||
|
||||
if w1<=(w2+zero) {
|
||||
error++
|
||||
txt.print("c!")
|
||||
} else {
|
||||
zero = 0 ; dummy
|
||||
}
|
||||
|
||||
if error {
|
||||
txt.print(" ")
|
||||
txt.print_w(w1)
|
||||
txt.print(" not <= ")
|
||||
txt.print_w(w2)
|
||||
txt.chrout('\n')
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub word_less() {
|
||||
word lessvar
|
||||
word comparevar
|
||||
|
||||
txt.print("word <\n")
|
||||
|
||||
txt.print_w(32767)
|
||||
txt.chrout('\n')
|
||||
check_less_w(-32767, 32767)
|
||||
txt.print_w(-32767)
|
||||
txt.chrout('\n')
|
||||
check_not_less_w(32767, -32767)
|
||||
|
||||
comparevar = 0
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in -1 downto -32768 {
|
||||
check_less_w(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = -2
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in -3 downto -32768 {
|
||||
check_less_w(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = -254
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in -255 downto -32768 {
|
||||
check_less_w(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = -255
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in -256 downto -32768 {
|
||||
check_less_w(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = -256
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in -257 downto -32768 {
|
||||
check_less_w(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = -5000
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in -5001 downto -32768 {
|
||||
check_less_w(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 1
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in 0 downto -32768 {
|
||||
check_less_w(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 255
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in 254 downto -32768 {
|
||||
check_less_w(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 256
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in 255 downto -32768 {
|
||||
check_less_w(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 257
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in 256 downto -32768 {
|
||||
check_less_w(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 32767
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in 32766 downto -32768 {
|
||||
check_less_w(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = -32768
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in 32766 downto -32768 {
|
||||
check_not_less_w(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = -1
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in 32766 downto -1 {
|
||||
check_not_less_w(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 0
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in 32766 downto 0 {
|
||||
check_not_less_w(lessvar, comparevar)
|
||||
}
|
||||
|
||||
comparevar = 11111
|
||||
txt.print_w(comparevar)
|
||||
txt.chrout('\n')
|
||||
for lessvar in 32766 downto 11111 {
|
||||
check_not_less_w(lessvar, comparevar)
|
||||
}
|
||||
|
||||
test_stack.test()
|
||||
return
|
||||
|
||||
sub check_less_w(word w1, word w2) {
|
||||
word zero = 0
|
||||
ubyte error=0
|
||||
|
||||
ubyte ub = w1<w2
|
||||
if not ub {
|
||||
error++
|
||||
txt.print("ub!")
|
||||
}
|
||||
|
||||
if w1<(w2+zero) {
|
||||
zero = 0 ; dummy
|
||||
} else {
|
||||
error++
|
||||
txt.print("c!")
|
||||
}
|
||||
|
||||
if error {
|
||||
txt.print(" ")
|
||||
txt.print_w(w1)
|
||||
txt.print(" < ")
|
||||
txt.print_w(w2)
|
||||
txt.chrout('\n')
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
sub check_not_less_w(word w1, word w2) {
|
||||
word zero = 0
|
||||
ubyte error=0
|
||||
|
||||
ubyte ub = w1<w2
|
||||
if ub {
|
||||
error++
|
||||
txt.print("ub!")
|
||||
}
|
||||
|
||||
if w1<(w2+zero) {
|
||||
error++
|
||||
txt.print("c!")
|
||||
} else {
|
||||
zero = 0 ; dummy
|
||||
}
|
||||
|
||||
if error {
|
||||
txt.print(" ")
|
||||
txt.print_w(w1)
|
||||
txt.print(" not < ")
|
||||
txt.print_w(w2)
|
||||
txt.chrout('\n')
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Binary file not shown.
BIN
examples/compiled/balls.prg
Normal file
BIN
examples/compiled/balls.prg
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user