Compare commits

...

140 Commits

Author SHA1 Message Date
262e0bd6b9 fix avg() on float arrays 2019-04-21 03:04:36 +02:00
755af6010e fix some more issues with array vardecls without array size specifier 2019-04-21 03:04:13 +02:00
0298cf8b90 scripts use gradle build dirs 2019-04-20 13:26:04 +02:00
a6d0aecd66 fix invalid assignment reordering 2019-04-20 13:24:42 +02:00
ef6e364339 intellij idea config 2019-04-20 01:18:47 +02:00
3b37e0f99d new build scripts 2019-04-20 00:50:15 +02:00
78fbbf7119 %asmbinary implemented 2019-04-17 01:33:07 +02:00
0ee43294c4 check for file with %asmbinary, %asminclude 2019-04-17 00:55:42 +02:00
a81b82495c fix wrong values generated from range expression in array vardecl 2019-04-17 00:27:27 +02:00
390043e9e8 some fixes in syntaxchecking array initializer values 2019-04-16 01:50:12 +02:00
e384822b2c array size in vardecl is optional when initializer array value is given 2019-04-16 01:19:51 +02:00
730e08698d comment 2019-04-13 00:58:39 +02:00
5497de4234 optimize @( &thing )) in ast into just thing 2019-04-12 23:59:26 +02:00
c71b78dee6 use array of pointers to blocks instead of a large if statement 2019-04-12 23:35:27 +02:00
dfcb57a0b0 couple of small shortcuts on identifier ast to lookup what it is pointing to 2019-04-12 23:04:19 +02:00
f219ae43f7 more inspiring code example 2019-04-12 22:34:43 +02:00
a9bbe0bc40 removed the memory keyword instead use & now (reuse the address-of operator to reduce the number of different concepts in the grammar) 2019-04-12 22:00:32 +02:00
35aa954be8 doc 2019-04-12 01:06:46 +02:00
78ddcf9db7 address-of works the test program 2019-04-12 00:58:40 +02:00
cd0fa9405a comments 2019-04-12 00:54:04 +02:00
4462def8ea fix array processing and ASM code gen of arrays with addressOf in them 2019-04-12 00:37:33 +02:00
3f93b87745 fix array processing and ASM code gen of arrays with addressOf in them 2019-04-12 00:04:15 +02:00
9f302cc640 docs about '&' operator 2019-04-11 21:41:46 +02:00
0a73125606 fix auto-insertion of AddressOf expression in function call arguments 2019-04-11 21:32:23 +02:00
7780441524 fix build scripts to point to new IntelliJ version 2019-04-11 21:26:46 +02:00
8bec4eaa87 rename PointerOf to AddressOf 2019-04-11 21:01:02 +02:00
4434d31a3b upgrade to Kotlin 1.3.30 and increase memory settings for command line build script 2019-04-11 19:58:28 +02:00
51454c71c7 Merge branch 'master' into pointerto
# Conflicts:
#	compiler/res/prog8lib/c64flt.p8
2019-04-10 23:16:08 +02:00
fb2796ac06 truly fix min(f)/max(f) also fix ceil(f) 2019-04-10 23:14:28 +02:00
742b15357b fix all(f) 2019-04-10 22:42:48 +02:00
ac6ed27052 restore tweaks in c64flt.p8 2019-04-10 22:18:45 +02:00
f3c1783bf2 correct intermediate code output of pointers in arrayvalues 2019-04-10 22:08:21 +02:00
ce8853ab50 restore tweaks in c64flt.p8 2019-04-08 00:36:19 +02:00
5e3e00fbad fix stackvm 2019-04-08 00:29:10 +02:00
1dde49d644 Merge branch 'master' into pointerto
# Conflicts:
#	compiler/res/prog8lib/c64flt.p8
#	compiler/src/prog8/stackvm/StackVm.kt
2019-04-08 00:19:18 +02:00
fd19298a05 fixed stackvm pop signed byte into register 2019-04-08 00:08:23 +02:00
ede2b83ce4 got rid of unused avg syscalls and fixed stackvm iterable functions (min, max, avg, sum, any, all) 2019-04-08 00:00:43 +02:00
fc47d3feb8 repaired min(f) max(f) fixes #13 2019-04-07 23:19:31 +02:00
87446028e0 no more duplicate auto heap vars, attempt at automatic insertion of & expression for subroutine params 2019-04-05 13:14:19 +02:00
b200f9945f asmgen array with pointer values (w.i.p) 2019-04-04 23:51:22 +02:00
eebd4e5f18 fix float constants prefix mistakes, removed broken max_f/min_f (fix pending), tweaked sum_f 2019-04-04 23:39:28 +02:00
1069b5f5d5 w.i.p pointer-to 2019-04-04 21:45:30 +02:00
3e7e44acfe no hard crash anymore for invalid string escape sequences or unknown petscii characters 2019-04-03 22:25:26 +02:00
518c3bfd76 actually, get rid of integer pow() because a naive multiplication loop approach is way too slow 2019-03-31 18:05:41 +02:00
905d8a0c06 actually, get rid of integer pow() because a naive multiplication loop approach is way too slow 2019-03-31 18:04:19 +02:00
b57c02b0ba don't remove 'duplicate' assignments that aren't removable (i.e. not literalvalues) 2019-03-31 16:10:02 +02:00
03d0411679 pow_f implemented 2019-03-31 14:28:38 +02:00
83ace753b2 got rid of problematic signed POW operator, added compiler checks for this 2019-03-31 13:56:03 +02:00
ec2e7db23e doc fix 2019-03-30 00:40:09 +01:00
c4615591c9 fixing label names, fixes #11 2019-03-30 00:31:40 +01:00
25e3b599e7 fixing label names 2019-03-30 00:15:50 +01:00
5502a3e3ee optimized name checking, no longer depends on scopedname 2019-03-28 21:30:30 +01:00
62ceace941 block names are global (unscoped) 2019-03-25 23:46:58 +01:00
7114d3193c some cleanups in library asm code 2019-03-21 22:36:46 +01:00
f6bc69139d added some example images to the index page of the docs 2019-03-19 21:39:01 +01:00
f3fc2fe523 irq handler saves zeropage scratch registers, fixes #8 2019-03-19 01:22:26 +01:00
1e045b6a62 fixed multi-return value assignment 2019-03-18 04:44:20 +01:00
747c9604dd improve ast check for multiple returnvalues assignment 2019-03-18 04:01:25 +01:00
1e5b2e0be3 for loops can now be over an iterable literal value directly (don't require a variable to hold the iterable) 2019-03-17 23:58:07 +01:00
0820716e7b added sqrt16() integer square root 2019-03-16 19:25:47 +01:00
191707cd37 added new c64utils.str2(u)word that doesn't use kernel float routines
fixed processing of register pair return value of asmsub
2019-03-16 17:50:59 +01:00
223bab21aa less verbose anon label names 2019-03-16 00:11:04 +01:00
563122ac92 stricter argument check for boolean operator 2019-03-15 23:34:15 +01:00
bc9d00922e implemented difference between printing and writing text in vm screen 2019-03-15 23:27:54 +01:00
d9d83248fe implemented strlen() function 2019-03-15 23:10:26 +01:00
f2397527f1 improved text output in stackvm 2019-03-13 22:45:12 +01:00
bf3caaefe1 stackvm now uses a proper instruction pointer call stack instead of instruction linking 2019-03-13 22:00:41 +01:00
1aaf854ef7 identified issue with single instruction linking in vm 2019-03-12 21:59:40 +01:00
ce40f6f862 defined a few more sysasm routines 2019-03-11 22:30:32 +01:00
a349599943 serious endless for loop bug in stackvm because Z and N flags weren't set properly, now fixed 2019-03-11 22:02:00 +01:00
ac7faa8d25 stackvm can now intercept system asm calls (to a rom address) 2019-03-11 02:05:30 +01:00
747ee32e81 updated tehtriz screenshot 2019-03-10 20:22:33 +01:00
75fadaa24f added holding area 2019-03-10 20:17:58 +01:00
e4ea1f1014 tweaked controls, score, sounds 2019-03-10 19:24:11 +01:00
cd2c4e13da cleanups 2019-03-10 18:30:01 +01:00
f5ba072294 removed str_p and str_ps pascal string types, fixes #10 2019-03-10 18:11:26 +01:00
87d6312a37 tetriz screen 2019-03-10 05:38:14 +01:00
3af7d4c930 tweaked tetriz speedup 2019-03-10 05:24:07 +01:00
0fc3071a21 updated examples 2019-03-10 04:36:48 +01:00
7f36d08acc simple sound effects 2019-03-10 04:22:02 +01:00
b040e5ddad speedup at every 10 lines 2019-03-10 03:59:58 +01:00
f36ce5e0ee line clearing 2019-03-10 03:21:14 +01:00
ffbdac7e9a don't draw 8 pieces instead of 7. Implemented simple wall kick when rotating. 2019-03-09 00:42:56 +01:00
f2b03342ac tehtriz joystick input 2019-03-07 23:29:23 +01:00
52ff61470b fixed rotation of I piece to conform to current tetris guidelines 2019-03-07 22:41:59 +01:00
28277469b6 fixed a compiler crash because with noopt, strings weren't put on the heap 2019-03-07 22:04:00 +01:00
aa98104d54 doc 2019-03-07 02:46:24 +01:00
9be70bcbe7 tetris stuff 2019-03-07 02:28:01 +01:00
3a6fae4447 simplified tehtris collision check a bit 2019-03-07 01:46:38 +01:00
06f0984fa1 docs about irq handlers 2019-03-07 01:02:11 +01:00
77dc35dc6a added read_flags() function, uword2bcd routine no longer enables irq again if it wasn't enabled before calling it. 2019-03-05 23:10:00 +01:00
ed43f7cd9b grade: also include parser in fatJar to make it complete, and exclude the huge ic4j library that isn't used 2019-03-02 22:41:21 +01:00
32405a1637 Merge pull request #7 from fboldog/add-antlr4-runtime
possible solution for antlr4-runtime in the fatjar
2019-03-02 22:39:08 +01:00
43cab3f247 possible solution for antlr4-runtime in the fatjar 2019-02-28 15:02:10 +01:00
5ea2f2d4db docs about @zp tag 2019-02-28 00:13:59 +01:00
b8ae808b65 compiler was confused about resulting expression type 2019-02-27 23:58:08 +01:00
96ecbc9fe4 fixed too eager expression operand type adjustment 2019-02-27 23:07:12 +01:00
588133d418 fixed primes.p8 2019-02-25 01:37:05 +01:00
2f1249489b datatype cleanups 2019-02-25 01:22:56 +01:00
95f7c9bad0 asmsubroutines now also return their value on the evalstack (this fixes their use in expressions) 2019-02-24 18:54:25 +01:00
8811d2f7c5 fixed a compiler ast crash and added -noopt command line flag 2019-02-24 16:56:38 +01:00
d6ca1e6a12 fixed len() returntype 2019-02-24 15:25:46 +01:00
b0ad66bd04 added missing bitwise and/or/xor asm code 2019-02-23 23:06:46 +01:00
c1d2b4601b fixed/added logical and/or/xor 2019-02-23 22:13:42 +01:00
c265625ed1 gradle 2019-02-23 13:17:42 +01:00
52352d9d04 added c64scr.getchr/getclr 2019-02-21 01:31:33 +01:00
cc5898d010 more tetriz work 2019-02-15 01:53:20 +01:00
8684f0c8f5 clean exit mandelbrot 2019-02-12 23:24:47 +01:00
d05d8de447 optimize math from Soci's suggestions 2019-02-12 23:16:13 +01:00
29b7d91293 improved tracking of module file locations, fixed relative importing of submodules from same directory as source module 2019-02-12 21:44:56 +01:00
bcdf3f2b83 improved zero page address addresses for floating point support mode 2019-02-09 17:55:18 +01:00
ee497d2ffb improved zero page address addresses for floating point support mode 2019-02-09 17:29:59 +01:00
9f8ae485c3 fix windows path issue 2019-02-04 22:04:31 +01:00
3b32fb74f7 suggest non-Oracle jdk 2019-02-03 22:23:17 +01:00
7ff1af3934 avoid zp corruption issues and added zp mode for floating point (todo: allocate) 2019-02-03 00:26:07 +01:00
ae21e03e1d attempt at optimization to register parameter passing 2019-02-01 22:51:30 +01:00
f0a504baec replaced cube3d compiled program with more optimized version 2019-02-01 01:42:20 +01:00
f83b9732ee User defined blocks are sorted first, before library blocks. This prioritizes zero page allocation to user code (and makes reading the program easier) 2019-02-01 01:22:27 +01:00
86ff08e854 fix abs() and also try to convert const arguments to the desired datatype of subroutine params 2019-02-01 00:51:49 +01:00
b911a95fc2 fix compiler crash for non-const array literals 2019-01-31 23:47:48 +01:00
73b0cc4056 docs 2019-01-31 23:46:58 +01:00
8e36a64d49 error message for invalid operand types for logical or bitwise operators 2019-01-31 21:38:45 +01:00
255c808b16 Merge pull request #2 from fboldog/gradle-fatjar
Create prog8compiler.jar with fatjar like jar process
2019-01-31 21:15:23 +01:00
b311b5ef4a scripts paths fixes 2019-01-31 00:09:22 +01:00
9a8ada7e75 fixed scripts, type check bug fixed, improved some argument error messages 2019-01-30 23:51:46 +01:00
32a9ab30fb fixed stack bug in print_ub, applied Soci's optimizations to prog8lib.asm 2019-01-30 23:43:21 +01:00
5c8d7c1255 Create prog8compiler.jar with fatjar like jar process 2019-01-30 22:39:43 +01:00
53045b9e36 idea 2019-01-29 23:48:26 +01:00
d35d28ac5a docs about gradle and building the compiler, fix shell build script 2019-01-29 23:32:43 +01:00
993dcde985 Merge pull request #1 from fboldog/gradle-build
Gradle build.

Thanks for contributing!
2019-01-29 22:25:16 +01:00
0ecef00b13 enable cmdline build cache
more information: https://blog.jetbrains.com/kotlin/2018/02/using-gradle-build-cache-with-kotlin/
2019-01-29 12:14:08 +01:00
13a5eabc09 ignore gradle build related folders 2019-01-29 12:12:07 +01:00
b8624c72d9 generate p8compiler and p8vm scripts 2019-01-29 12:11:47 +01:00
49c5b2b107 java, resources and test source directories 2019-01-29 12:10:59 +01:00
1e89bea288 just changed by build 2019-01-29 12:10:09 +01:00
2c64f95b06 set antlr source (grammar) directory 2019-01-29 12:09:39 +01:00
037c78cfaf set current java source directory 2019-01-29 12:09:18 +01:00
de777b1308 initial version of compiler and parser gradle module 2019-01-29 12:08:21 +01:00
b3df3c999d package definition for antlr 2019-01-29 12:06:33 +01:00
4790c3a793 gradle wrapper 2019-01-29 12:06:10 +01:00
108 changed files with 6441 additions and 4031 deletions

6
.gitignore vendored
View File

@ -1,11 +1,10 @@
**/.idea/
.idea/workspace.xml
/build/
/dist/
/output/
.*cache/
*.directory
*.prg
*.asm
*.bin
*.labels.txt
*.vm.txt
@ -29,3 +28,6 @@ compiler/src/prog8_kotlin.jar
compiler/src/compiled_java
.attach_pid*
.gradle
build/
/prog8compiler.jar

20
.idea/gradle.xml generated Normal file
View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="/usr/share/java/gradle" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/compiler" />
<option value="$PROJECT_DIR$/parser" />
</set>
</option>
<option name="useQualifiedModuleNames" value="true" />
</GradleProjectSettings>
</option>
</component>
</project>

7
.idea/misc.xml generated Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="false" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@ -50,55 +50,58 @@ of the [Vice emulator](http://vice-emu.sourceforge.net/)
Example code
------------
When this code is compiled::
This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
%import c64lib
%import c64utils
%import c64flt
%zeropage basicsafe
~ main {
ubyte[256] sieve
ubyte candidate_prime = 2
sub start() {
; set text color and activate lowercase charset
c64.COLOR = 13
c64.VMCSB |= 2
memset(sieve, 256, false)
; use optimized routine to write text
c64scr.print("Hello!\n")
; use iteration to write text
str question = "How are you?\n"
for ubyte char in question
c64.CHROUT(char)
; use indexed loop to write characters
str bye = "Goodbye!\n"
for ubyte c in 0 to len(bye)
c64.CHROUT(bye[c])
float clock_seconds = ((mkword(c64.TIME_LO, c64.TIME_MID) as float)
+ (c64.TIME_HI as float)*65536.0)
/ 60
float hours = floor(clock_seconds / 3600)
clock_seconds -= hours*3600
float minutes = floor(clock_seconds / 60)
clock_seconds = floor(clock_seconds - minutes * 60.0)
c64scr.print("system time in ti$ is ")
c64flt.print_f(hours)
c64.CHROUT(':')
c64flt.print_f(minutes)
c64.CHROUT(':')
c64flt.print_f(clock_seconds)
c64scr.print("prime numbers up to 255:\n\n")
ubyte amount=0
while true {
ubyte prime = find_next_prime()
if prime==0
break
c64scr.print_ub(prime)
c64scr.print(", ")
amount++
}
c64.CHROUT('\n')
c64scr.print("number of primes (expected 54): ")
c64scr.print_ub(amount)
c64.CHROUT('\n')
}
sub find_next_prime() -> ubyte {
while sieve[candidate_prime] {
candidate_prime++
if candidate_prime==0
return 0
}
sieve[candidate_prime] = true
uword multiple = candidate_prime
while multiple < len(sieve) {
sieve[lsb(multiple)] = true
multiple += candidate_prime
}
return candidate_prime
}
}
when compiled an ran on a C-64 you'll get:
you get a program that outputs this when loaded on a C-64:
![c64 screen](docs/source/_static/hello_screen.png)
![c64 screen](docs/source/_static/primes_example.png)
One of the included examples (wizzine.p8) animates a bunch of sprite balloons and looks like this:
@ -109,3 +112,6 @@ Another example (cube3d-sprites.p8) draws the vertices of a rotating 3d cube:
![cube3d screen](docs/source/_static/cube3d.png)
If you want to play a video game, a fully working Tetris clone is included in the examples:
![tehtriz_screen](docs/source/_static/tehtriz.png)

97
compiler/build.gradle Normal file
View File

@ -0,0 +1,97 @@
plugins {
id "org.jetbrains.kotlin.jvm" version "1.3.30"
id 'application'
}
repositories {
mavenCentral()
jcenter()
}
def kotlinVersion = '1.3.30'
dependencies {
implementation project(':parser')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
runtime "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
runtime 'org.antlr:antlr4-runtime:4.7.2'
runtime project(':parser')
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5:$kotlinVersion"
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2'
testImplementation 'org.hamcrest:hamcrest-junit:2.0.0.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.2'
}
compileKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
}
resources {
srcDirs = ["${project.projectDir}/res"]
}
}
test {
java {
srcDirs = ["${project.projectDir}/test"]
}
}
}
application {
mainClassName = 'prog8.CompilerMainKt'
applicationName = 'p8compile'
}
task p8vmScript(type: CreateStartScripts) {
mainClassName = "prog8.StackVmMainKt"
applicationName = "p8vm"
outputDir = new File(project.buildDir, 'scripts')
classpath = jar.outputs.files + project.configurations.runtime
}
applicationDistribution.into("bin") {
from(p8vmScript)
fileMode = 0755
}
task fatJar(type: Jar) {
manifest {
attributes 'Main-Class': 'prog8.CompilerMainKt'
}
archiveBaseName = 'prog8compiler'
destinationDirectory = rootProject.projectDir
from {
project.configurations.runtime.collect { it.isDirectory() ? it : zipTree(it) }
}
with jar
}
// build.finalizedBy(fatJar)
// Java target version
sourceCompatibility = 1.8
test {
// Enable JUnit 5 (Gradle 4.6+).
useJUnitPlatform()
// Always run tests, even when nothing changed.
dependsOn 'cleanTest'
// Show test results.
testLogging {
events "passed", "skipped", "failed"
}
}

View File

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
</content>
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="library" name="antlr-runtime-4.7.2" level="project" />
<orderEntry type="library" name="testlibs" level="project" />
<orderEntry type="module" module-name="parser" />
</component>
</module>

View File

@ -9,31 +9,32 @@
~ c64flt {
; ---- this block contains C-64 floating point related functions ----
const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586
; ---- C64 basic and kernal ROM float constants and functions ----
; note: the fac1 and fac2 are working registers and take 6 bytes each,
; floats in memory (and rom) are stored in 5-byte MFLPT packed format.
; constants in five-byte "mflpt" format in the BASIC ROM
memory float FL_PIVAL = $aea8 ; 3.1415926...
memory float FL_N32768 = $b1a5 ; -32768
memory float FL_FONE = $b9bc ; 1
memory float FL_SQRHLF = $b9d6 ; SQR(2) / 2
memory float FL_SQRTWO = $b9db ; SQR(2)
memory float FL_NEGHLF = $b9e0 ; -.5
memory float FL_LOG2 = $b9e5 ; LOG(2)
memory float FL_TENC = $baf9 ; 10
memory float FL_NZMIL = $bdbd ; 1e9 (1 billion)
memory float FL_FHALF = $bf11 ; .5
memory float FL_LOGEB2 = $bfbf ; 1 / LOG(2)
memory float FL_PIHALF = $e2e0 ; PI / 2
memory float FL_TWOPI = $e2e5 ; 2 * PI
memory float FL_FR4 = $e2ea ; .25
&float FL_PIVAL = $aea8 ; 3.1415926...
&float FL_N32768 = $b1a5 ; -32768
&float FL_FONE = $b9bc ; 1
&float FL_SQRHLF = $b9d6 ; SQR(2) / 2
&float FL_SQRTWO = $b9db ; SQR(2)
&float FL_NEGHLF = $b9e0 ; -.5
&float FL_LOG2 = $b9e5 ; LOG(2)
&float FL_TENC = $baf9 ; 10
&float FL_NZMIL = $bdbd ; 1e9 (1 billion)
&float FL_FHALF = $bf11 ; .5
&float FL_LOGEB2 = $bfbf ; 1 / LOG(2)
&float FL_PIHALF = $e2e0 ; PI / 2
&float FL_TWOPI = $e2e5 ; 2 * PI
&float FL_FR4 = $e2ea ; .25
float FL_ZERO = 0.0 ; oddly enough 0.0 isn't available in the kernel
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
@ -165,14 +166,14 @@ asmsub GIVAYFAY (uword value @ AY) -> clobbers(A,X,Y) -> () {
sta c64.SCRATCH_ZPREG
tya
ldy c64.SCRATCH_ZPREG
jmp c64flt.GIVAYF ; this uses the inverse order, Y/A
jmp GIVAYF ; this uses the inverse order, Y/A
}}
}
asmsub FTOSWRDAY () -> clobbers(X) -> (uword @ AY) {
; ---- fac1 to signed word in A/Y
%asm {{
jsr c64flt.FTOSWORDYA ; note the inverse Y/A order
jsr FTOSWORDYA ; note the inverse Y/A order
sta c64.SCRATCH_ZPREG
tya
ldy c64.SCRATCH_ZPREG
@ -183,7 +184,7 @@ asmsub FTOSWRDAY () -> clobbers(X) -> (uword @ AY) {
asmsub GETADRAY () -> clobbers(X) -> (uword @ AY) {
; ---- fac1 to unsigned word in A/Y
%asm {{
jsr c64flt.GETADR ; this uses the inverse order, Y/A
jsr GETADR ; this uses the inverse order, Y/A
sta c64.SCRATCH_ZPB1
tya
ldy c64.SCRATCH_ZPB1
@ -192,13 +193,13 @@ asmsub GETADRAY () -> clobbers(X) -> (uword @ AY) {
}
sub print_f (float value) {
; ---- prints the floating point value (without a newline) using basic rom routines.
; ---- prints the floating point value (without a newline) using basic rom routines.
%asm {{
stx c64.SCRATCH_ZPREGX
lda #<print_f_value
ldy #>print_f_value
jsr c64flt.MOVFM ; load float into fac1
jsr c64flt.FOUT ; fac1 to string in A/Y
jsr MOVFM ; load float into fac1
jsr FOUT ; fac1 to string in A/Y
jsr c64.STROUT ; print string in A/Y
ldx c64.SCRATCH_ZPREGX
rts
@ -211,12 +212,12 @@ sub print_fln (float value) {
stx c64.SCRATCH_ZPREGX
lda #<print_fln_value
ldy #>print_fln_value
jsr c64flt.MOVFM ; load float into fac1
jsr c64flt.FPRINTLN ; print fac1 with newline
jsr MOVFM ; load float into fac1
jsr FPRINTLN ; print fac1 with newline
ldx c64.SCRATCH_ZPREGX
rts
}}
}
@ -229,10 +230,10 @@ ub2float .proc
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
ldy c64.SCRATCH_ZPB1
jsr c64flt.FREADUY
jsr FREADUY
_fac_to_mem ldx c64.SCRATCH_ZPWORD2
ldy c64.SCRATCH_ZPWORD2+1
jsr c64flt.MOVMF
jsr MOVMF
ldx c64.SCRATCH_ZPREGX
rts
.pend
@ -244,7 +245,7 @@ b2float .proc
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
lda c64.SCRATCH_ZPB1
jsr c64flt.FREADSA
jsr FREADSA
jmp ub2float._fac_to_mem
.pend
@ -255,7 +256,7 @@ uw2float .proc
sty c64.SCRATCH_ZPWORD2+1
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr c64flt.GIVUAYFAY
jsr GIVUAYFAY
jmp ub2float._fac_to_mem
.pend
@ -266,26 +267,26 @@ w2float .proc
sty c64.SCRATCH_ZPWORD2+1
ldy c64.SCRATCH_ZPWORD1
lda c64.SCRATCH_ZPWORD1+1
jsr c64flt.GIVAYF
jsr GIVAYF
jmp ub2float._fac_to_mem
.pend
stack_b2float .proc
; -- b2float operating on the stack
inx
lda c64.ESTACK_LO,x
stx c64.SCRATCH_ZPREGX
jsr c64flt.FREADSA
jsr FREADSA
jmp push_fac1_as_result
.pend
stack_w2float .proc
; -- w2float operating on the stack
inx
ldy c64.ESTACK_LO,x
lda c64.ESTACK_HI,x
stx c64.SCRATCH_ZPREGX
jsr c64flt.GIVAYF
jsr GIVAYF
jmp push_fac1_as_result
.pend
@ -295,7 +296,7 @@ stack_ub2float .proc
lda c64.ESTACK_LO,x
stx c64.SCRATCH_ZPREGX
tay
jsr c64flt.FREADUY
jsr FREADUY
jmp push_fac1_as_result
.pend
@ -305,14 +306,14 @@ stack_uw2float .proc
lda c64.ESTACK_LO,x
ldy c64.ESTACK_HI,x
stx c64.SCRATCH_ZPREGX
jsr c64flt.GIVUAYFAY
jsr GIVUAYFAY
jmp push_fac1_as_result
.pend
stack_float2w .proc
stack_float2w .proc
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr c64flt.AYINT
jsr AYINT
ldx c64.SCRATCH_ZPREGX
lda $64
sta c64.ESTACK_HI,x
@ -321,11 +322,11 @@ stack_float2w .proc
dex
rts
.pend
stack_float2uw .proc
stack_float2uw .proc
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr c64flt.GETADR
jsr GETADR
ldx c64.SCRATCH_ZPREGX
sta c64.ESTACK_HI,x
tya
@ -335,7 +336,7 @@ stack_float2uw .proc
.pend
push_float .proc
; ---- push mflpt5 in A/Y onto stack
; ---- push mflpt5 in A/Y onto stack
; (taking 3 stack positions = 6 bytes of which 1 is padding)
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
@ -359,23 +360,23 @@ push_float .proc
dex
rts
.pend
func_rndf .proc
; -- put a random floating point value on the stack
stx c64.SCRATCH_ZPREG
lda #1
jsr c64flt.FREADSA
jsr c64flt.RND ; rng into fac1
jsr FREADSA
jsr RND ; rng into fac1
ldx #<_rndf_rnum5
ldy #>_rndf_rnum5
jsr c64flt.MOVMF ; fac1 to mem X/Y
jsr MOVMF ; fac1 to mem X/Y
ldx c64.SCRATCH_ZPREG
lda #<_rndf_rnum5
ldy #>_rndf_rnum5
jmp push_float
_rndf_rnum5 .byte 0,0,0,0,0
.pend
push_float_from_indexed_var .proc
; -- push the float from the array at A/Y with index on stack, onto the stack.
sta c64.SCRATCH_ZPWORD1
@ -412,7 +413,7 @@ pop_float .proc
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
pop_float_fac1 .proc
; -- pops float from stack into FAC1
lda #<fmath_float1
@ -420,9 +421,9 @@ pop_float_fac1 .proc
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jmp c64flt.MOVFM
jmp MOVFM
.pend
pop_float_to_indexed_var .proc
; -- pop the float on the stack, to the memory in the array at A/Y indexed by the byte on stack
sta c64.SCRATCH_ZPWORD1
@ -435,7 +436,7 @@ pop_float_to_indexed_var .proc
.pend
copy_float .proc
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
@ -462,17 +463,17 @@ inc_var_f .proc
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
stx c64.SCRATCH_ZPREGX
jsr c64flt.MOVFM
jsr MOVFM
lda #<FL_FONE
ldy #>FL_FONE
jsr c64flt.FADD
jsr FADD
ldx c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr c64flt.MOVMF
jsr MOVMF
ldx c64.SCRATCH_ZPREGX
rts
.pend
dec_var_f .proc
; -- subtract 1 from float pointed to by A/Y
sta c64.SCRATCH_ZPWORD1
@ -480,17 +481,17 @@ dec_var_f .proc
stx c64.SCRATCH_ZPREGX
lda #<FL_FONE
ldy #>FL_FONE
jsr c64flt.MOVFM
jsr MOVFM
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr c64flt.FSUB
jsr FSUB
ldx c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr c64flt.MOVMF
jsr MOVMF
ldx c64.SCRATCH_ZPREGX
rts
.pend
inc_indexed_var_f .proc
; -- add 1 to float in array pointed to by A/Y, at index X
pha
@ -508,7 +509,7 @@ inc_indexed_var_f .proc
iny
+ jmp inc_var_f
.pend
dec_indexed_var_f .proc
; -- subtract 1 to float in array pointed to by A/Y, at index X
pha
@ -526,7 +527,7 @@ dec_indexed_var_f .proc
iny
+ jmp dec_var_f
.pend
pop_2_floats_f2_in_fac1 .proc
; -- pop 2 floats from stack, load the second one in FAC1 as well
@ -538,9 +539,10 @@ pop_2_floats_f2_in_fac1 .proc
jsr pop_float
lda #<fmath_float2
ldy #>fmath_float2
jmp c64flt.MOVFM
jmp MOVFM
.pend
fmath_float1 .byte 0,0,0,0,0 ; storage for a mflpt5 value
fmath_float2 .byte 0,0,0,0,0 ; storage for a mflpt5 value
@ -548,13 +550,31 @@ push_fac1_as_result .proc
; -- push the float in FAC1 onto the stack, and return from calculation
ldx #<fmath_float1
ldy #>fmath_float1
jsr c64flt.MOVMF
jsr MOVMF
lda #<fmath_float1
ldy #>fmath_float1
ldx c64.SCRATCH_ZPREGX
jmp push_float
.pend
pow_f .proc
; -- push f1 ** f2 on stack
lda #<fmath_float2
ldy #>fmath_float2
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
stx c64.SCRATCH_ZPREGX
lda #<fmath_float1
ldy #>fmath_float1
jsr CONUPK ; fac2 = float1
lda #<fmath_float2
ldy #>fmath_float2
jsr FPWR
ldx c64.SCRATCH_ZPREGX
jmp push_fac1_as_result
.pend
div_f .proc
; -- push f1/f2 on stack
@ -562,7 +582,7 @@ div_f .proc
stx c64.SCRATCH_ZPREGX
lda #<fmath_float1
ldy #>fmath_float1
jsr c64flt.FDIV
jsr FDIV
jmp push_fac1_as_result
.pend
@ -572,7 +592,7 @@ add_f .proc
stx c64.SCRATCH_ZPREGX
lda #<fmath_float1
ldy #>fmath_float1
jsr c64flt.FADD
jsr FADD
jmp push_fac1_as_result
.pend
@ -582,7 +602,7 @@ sub_f .proc
stx c64.SCRATCH_ZPREGX
lda #<fmath_float1
ldy #>fmath_float1
jsr c64flt.FSUB
jsr FSUB
jmp push_fac1_as_result
.pend
@ -592,15 +612,15 @@ mul_f .proc
stx c64.SCRATCH_ZPREGX
lda #<fmath_float1
ldy #>fmath_float1
jsr c64flt.FMULT
jsr FMULT
jmp push_fac1_as_result
.pend
neg_f .proc
; -- push -flt back on stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr c64flt.NEGOP
jsr NEGOP
jmp push_fac1_as_result
.pend
@ -608,7 +628,7 @@ abs_f .proc
; -- push abs(float) on stack (as float)
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr c64flt.ABS
jsr ABS
jmp push_fac1_as_result
.pend
@ -638,7 +658,7 @@ _equals_store inx
sta c64.ESTACK_LO+1,x
rts
_equals_false lda #0
beq _equals_store
beq _equals_store
.pend
notequal_f .proc
@ -656,7 +676,7 @@ less_f .proc
beq compare_floats._return_true
bne compare_floats._return_false
.pend
lesseq_f .proc
; -- is f1 <= f2?
@ -695,11 +715,11 @@ compare_floats .proc
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jsr c64flt.MOVFM ; fac1 = flt1
jsr MOVFM ; fac1 = flt1
lda #<fmath_float2
ldy #>fmath_float2
stx c64.SCRATCH_ZPREG
jsr c64flt.FCOMP ; A = flt1 compared with flt2 (0=equal, 1=flt1>flt2, 255=flt1<flt2)
jsr FCOMP ; A = flt1 compared with flt2 (0=equal, 1=flt1>flt2, 255=flt1<flt2)
ldx c64.SCRATCH_ZPREG
rts
_return_false lda #0
@ -708,13 +728,13 @@ _return_result sta c64.ESTACK_LO,x
rts
_return_true lda #1
bne _return_result
.pend
.pend
func_sin .proc
; -- push sin(f) back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr c64flt.SIN
jsr SIN
jmp push_fac1_as_result
.pend
@ -722,7 +742,7 @@ func_cos .proc
; -- push cos(f) back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr c64flt.COS
jsr COS
jmp push_fac1_as_result
.pend
@ -730,99 +750,99 @@ func_tan .proc
; -- push tan(f) back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr c64flt.TAN
jsr TAN
jmp push_fac1_as_result
.pend
func_atan .proc
; -- push atan(f) back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr c64flt.ATN
jsr ATN
jmp push_fac1_as_result
.pend
func_ln .proc
; -- push ln(f) back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr c64flt.LOG
jsr LOG
jmp push_fac1_as_result
.pend
func_log2 .proc
; -- push log base 2, ln(f)/ln(2), back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr c64flt.LOG
jsr c64flt.MOVEF
jsr LOG
jsr MOVEF
lda #<c64.FL_LOG2
ldy #>c64.FL_LOG2
jsr c64flt.MOVFM
jsr c64flt.FDIVT
jsr MOVFM
jsr FDIVT
jmp push_fac1_as_result
.pend
func_sqrt .proc
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr c64flt.SQR
jsr SQR
jmp push_fac1_as_result
.pend
func_rad .proc
; -- convert degrees to radians (d * pi / 180)
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
lda #<_pi_div_180
ldy #>_pi_div_180
jsr c64flt.FMULT
jsr FMULT
jmp push_fac1_as_result
_pi_div_180 .byte 123, 14, 250, 53, 18 ; pi / 180
.pend
func_deg .proc
; -- convert radians to degrees (d * (1/ pi * 180))
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
lda #<_one_over_pi_div_180
ldy #>_one_over_pi_div_180
jsr c64flt.FMULT
jsr FMULT
jmp push_fac1_as_result
_one_over_pi_div_180 .byte 134, 101, 46, 224, 211 ; 1 / (pi * 180)
.pend
func_round .proc
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr c64flt.FADDH
jsr c64flt.INT
jsr FADDH
jsr INT
jmp push_fac1_as_result
.pend
func_floor .proc
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr c64flt.INT
jsr INT
jmp push_fac1_as_result
.pend
func_ceil .proc
; -- ceil: tr = int(f); if tr==f -> return else return tr+1
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
ldx #<fmath_float1
ldy #>fmath_float1
jsr MOVMF
jsr INT
lda #<fmath_float1
ldy #>fmath_float1
jsr c64flt.MOVMF
jsr c64flt.INT
lda #<fmath_float1
ldy #>fmath_float1
jsr c64flt.FCOMP
jsr FCOMP
cmp #0
beq +
lda #<FL_FONE
ldy #>FL_FONE
jsr c64flt.FADD
jsr FADD
+ jmp push_fac1_as_result
.pend
@ -834,98 +854,93 @@ func_any_f .proc
asl a
clc
adc c64.SCRATCH_ZPB1 ; times 5 because of float
jmp func_any_b._entry
jmp prog8_lib.func_any_b._entry
.pend
func_all_f .proc
inx
jsr prog8_lib.peek_address
lda c64.ESTACK_LO,x ; array size
sta c64.SCRATCH_ZPB1
asl a
asl a
clc
adc c64.SCRATCH_ZPB1 ; times 5 because of float
sta _cmp_mod+1 ; self-modifying code
jsr peek_address
ldy #0
tay
dey
- lda (c64.SCRATCH_ZPWORD1),y
bne +
iny
lda (c64.SCRATCH_ZPWORD1),y
bne +
iny
lda (c64.SCRATCH_ZPWORD1),y
bne +
iny
lda (c64.SCRATCH_ZPWORD1),y
bne +
iny
lda (c64.SCRATCH_ZPWORD1),y
bne +
lda #0
sta c64.ESTACK_LO+1,x
rts
+ iny
_cmp_mod cpy #255 ; modified
bne -
clc
dey
adc (c64.SCRATCH_ZPWORD1),y
dey
adc (c64.SCRATCH_ZPWORD1),y
dey
adc (c64.SCRATCH_ZPWORD1),y
dey
adc (c64.SCRATCH_ZPWORD1),y
dey
cmp #0
beq +
cpy #255
bne -
lda #1
sta c64.ESTACK_LO+1,x
rts
+ sta c64.ESTACK_LO+1,x
rts
.pend
func_max_f .proc
lda #<_min_float
ldy #>_min_float
jsr c64flt.MOVFM ; fac1=min(float)
lda #255
sta _cmp_mod+1 ; compare using 255 so we keep larger values
_minmax_entry jsr pop_array_and_lengthmin1Y
sta _minmax_cmp+1
lda #<_largest_neg_float
ldy #>_largest_neg_float
_minmax_entry jsr MOVFM
jsr prog8_lib.pop_array_and_lengthmin1Y
stx c64.SCRATCH_ZPREGX
- sty c64.SCRATCH_ZPREG
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr c64flt.FCOMP
_cmp_mod cmp #255 ; will be modified
jsr FCOMP
_minmax_cmp cmp #255 ; modified
bne +
; fac1 is smaller/larger, so store the new value instead
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr c64flt.MOVFM
ldy c64.SCRATCH_ZPREG
dey
cmp #255
beq +
lda c64.SCRATCH_ZPWORD1
jsr MOVFM
+ lda c64.SCRATCH_ZPWORD1
clc
adc #5
sta c64.SCRATCH_ZPWORD1
bcc -
bcc +
inc c64.SCRATCH_ZPWORD1+1
+ ldy c64.SCRATCH_ZPREG
dey
cpy #255
bne -
+ jmp push_fac1_as_result
_min_float .byte 255,255,255,255,255 ; -1.7014118345e+38
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 #<_max_float
ldy #>_max_float
jsr c64flt.MOVFM ; fac1=max(float)
lda #1
sta func_max_f._cmp_mod+1 ; compare using 1 so we keep smaller values
sta func_max_f._minmax_cmp+1
lda #<_largest_pos_float
ldy #>_largest_pos_float
jmp func_max_f._minmax_entry
_max_float .byte 255,127,255,255,255 ; 1.7014118345e+38
_largest_pos_float .byte 255,127,255,255,255 ; largest positive float
rts
.pend
func_sum_f .proc
lda #<c64.FL_NEGHLF
ldy #>c64.FL_NEGHLF
jsr c64flt.MOVFM
jsr pop_array_and_lengthmin1Y
lda #<FL_ZERO
ldy #>FL_ZERO
jsr MOVFM
jsr prog8_lib.pop_array_and_lengthmin1Y
stx c64.SCRATCH_ZPREGX
- sty c64.SCRATCH_ZPREG
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr c64flt.FADD
jsr FADD
ldy c64.SCRATCH_ZPREG
dey
cpy #255
@ -937,8 +952,7 @@ func_sum_f .proc
bcc -
inc c64.SCRATCH_ZPWORD1+1
bne -
+ jsr c64flt.FADDH
jmp push_fac1_as_result
+ jmp push_fac1_as_result
.pend
}}

View File

@ -7,178 +7,178 @@
~ c64 {
memory ubyte SCRATCH_ZPB1 = $02 ; scratch byte 1 in ZP
memory ubyte SCRATCH_ZPREG = $03 ; scratch register in ZP
memory ubyte SCRATCH_ZPREGX = $fa ; temp storage for X register (stack pointer)
memory uword SCRATCH_ZPWORD1 = $fb ; scratch word in ZP ($fb/$fc)
memory uword SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
const uword ESTACK_LO = $ce00 ; evaluation stack (lsb)
const uword ESTACK_HI = $cf00 ; evaluation stack (msb)
&ubyte SCRATCH_ZPB1 = $02 ; scratch byte 1 in ZP
&ubyte SCRATCH_ZPREG = $03 ; scratch register in ZP
&ubyte SCRATCH_ZPREGX = $fa ; temp storage for X register (stack pointer)
&uword SCRATCH_ZPWORD1 = $fb ; scratch word in ZP ($fb/$fc)
&uword SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
memory ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
memory ubyte TIME_MID = $a1 ; .. mid byte
memory ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
memory ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
memory ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
memory ubyte COLOR = $0286 ; cursor color
memory ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address)
memory uword CINV = $0314 ; IRQ vector
memory uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
memory uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
memory uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
&ubyte TIME_MID = $a1 ; .. mid byte
&ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
&ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
&ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
&ubyte COLOR = $0286 ; cursor color
&ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address)
&uword CINV = $0314 ; IRQ vector
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
; the default addresses for the character screen chars and colors
const uword Screen = $0400 ; to have this as an array[40*25] the compiler would have to support array size > 255
const uword Colors = $d800 ; to have this as an array[40*25] the compiler would have to support array size > 255
; the default locations of the 8 sprite pointers (store address of sprite / 64)
memory ubyte SPRPTR0 = 2040
memory ubyte SPRPTR1 = 2041
memory ubyte SPRPTR2 = 2042
memory ubyte SPRPTR3 = 2043
memory ubyte SPRPTR4 = 2044
memory ubyte SPRPTR5 = 2045
memory ubyte SPRPTR6 = 2046
memory ubyte SPRPTR7 = 2047
memory ubyte[8] SPRPTR = 2040 ; the 8 sprite pointers as an array.
&ubyte SPRPTR0 = 2040
&ubyte SPRPTR1 = 2041
&ubyte SPRPTR2 = 2042
&ubyte SPRPTR3 = 2043
&ubyte SPRPTR4 = 2044
&ubyte SPRPTR5 = 2045
&ubyte SPRPTR6 = 2046
&ubyte SPRPTR7 = 2047
&ubyte[8] SPRPTR = 2040 ; the 8 sprite pointers as an array.
; ---- VIC-II 6567/6569/856x registers ----
memory ubyte SP0X = $d000
memory ubyte SP0Y = $d001
memory ubyte SP1X = $d002
memory ubyte SP1Y = $d003
memory ubyte SP2X = $d004
memory ubyte SP2Y = $d005
memory ubyte SP3X = $d006
memory ubyte SP3Y = $d007
memory ubyte SP4X = $d008
memory ubyte SP4Y = $d009
memory ubyte SP5X = $d00a
memory ubyte SP5Y = $d00b
memory ubyte SP6X = $d00c
memory ubyte SP6Y = $d00d
memory ubyte SP7X = $d00e
memory ubyte SP7Y = $d00f
memory ubyte[16] SPXY = $d000 ; the 8 sprite X and Y registers as an array.
memory uword[8] SPXYW = $d000 ; the 8 sprite X and Y registers as a combined xy word array.
&ubyte SP0X = $d000
&ubyte SP0Y = $d001
&ubyte SP1X = $d002
&ubyte SP1Y = $d003
&ubyte SP2X = $d004
&ubyte SP2Y = $d005
&ubyte SP3X = $d006
&ubyte SP3Y = $d007
&ubyte SP4X = $d008
&ubyte SP4Y = $d009
&ubyte SP5X = $d00a
&ubyte SP5Y = $d00b
&ubyte SP6X = $d00c
&ubyte SP6Y = $d00d
&ubyte SP7X = $d00e
&ubyte SP7Y = $d00f
&ubyte[16] SPXY = $d000 ; the 8 sprite X and Y registers as an array.
&uword[8] SPXYW = $d000 ; the 8 sprite X and Y registers as a combined xy word array.
memory ubyte MSIGX = $d010
memory ubyte SCROLY = $d011
memory ubyte RASTER = $d012
memory ubyte LPENX = $d013
memory ubyte LPENY = $d014
memory ubyte SPENA = $d015
memory ubyte SCROLX = $d016
memory ubyte YXPAND = $d017
memory ubyte VMCSB = $d018
memory ubyte VICIRQ = $d019
memory ubyte IREQMASK = $d01a
memory ubyte SPBGPR = $d01b
memory ubyte SPMC = $d01c
memory ubyte XXPAND = $d01d
memory ubyte SPSPCL = $d01e
memory ubyte SPBGCL = $d01f
&ubyte MSIGX = $d010
&ubyte SCROLY = $d011
&ubyte RASTER = $d012
&ubyte LPENX = $d013
&ubyte LPENY = $d014
&ubyte SPENA = $d015
&ubyte SCROLX = $d016
&ubyte YXPAND = $d017
&ubyte VMCSB = $d018
&ubyte VICIRQ = $d019
&ubyte IREQMASK = $d01a
&ubyte SPBGPR = $d01b
&ubyte SPMC = $d01c
&ubyte XXPAND = $d01d
&ubyte SPSPCL = $d01e
&ubyte SPBGCL = $d01f
&ubyte EXTCOL = $d020 ; border color
&ubyte BGCOL0 = $d021 ; screen color
&ubyte BGCOL1 = $d022
&ubyte BGCOL2 = $d023
&ubyte BGCOL4 = $d024
&ubyte SPMC0 = $d025
&ubyte SPMC1 = $d026
&ubyte SP0COL = $d027
&ubyte SP1COL = $d028
&ubyte SP2COL = $d029
&ubyte SP3COL = $d02a
&ubyte SP4COL = $d02b
&ubyte SP5COL = $d02c
&ubyte SP6COL = $d02d
&ubyte SP7COL = $d02e
&ubyte[8] SPCOL = $d027
memory ubyte EXTCOL = $d020 ; border color
memory ubyte BGCOL0 = $d021 ; screen color
memory ubyte BGCOL1 = $d022
memory ubyte BGCOL2 = $d023
memory ubyte BGCOL4 = $d024
memory ubyte SPMC0 = $d025
memory ubyte SPMC1 = $d026
memory ubyte SP0COL = $d027
memory ubyte SP1COL = $d028
memory ubyte SP2COL = $d029
memory ubyte SP3COL = $d02a
memory ubyte SP4COL = $d02b
memory ubyte SP5COL = $d02c
memory ubyte SP6COL = $d02d
memory ubyte SP7COL = $d02e
memory ubyte[8] SPCOL = $d027
; ---- end of VIC-II registers ----
; ---- CIA 6526 1 & 2 registers ----
memory ubyte CIA1PRA = $DC00 ; CIA 1 DRA, keyboard column drive
memory ubyte CIA1PRB = $DC01 ; CIA 1 DRB, keyboard row port
memory ubyte CIA1DDRA = $DC02 ; CIA 1 DDRA, keyboard column
memory ubyte CIA1DDRB = $DC03 ; CIA 1 DDRB, keyboard row
memory ubyte CIA1TAL = $DC04 ; CIA 1 timer A low byte
memory ubyte CIA1TAH = $DC05 ; CIA 1 timer A high byte
memory ubyte CIA1TBL = $DC06 ; CIA 1 timer B low byte
memory ubyte CIA1TBH = $DC07 ; CIA 1 timer B high byte
memory ubyte CIA1TOD10 = $DC08 ; time of day, 1/10 sec.
memory ubyte CIA1TODSEC = $DC09 ; time of day, seconds
memory ubyte CIA1TODMMIN = $DC0A ; time of day, minutes
memory ubyte CIA1TODHR = $DC0B ; time of day, hours
memory ubyte CIA1SDR = $DC0C ; Serial Data Register
memory ubyte CIA1ICR = $DC0D
memory ubyte CIA1CRA = $DC0E
memory ubyte CIA1CRB = $DC0F
&ubyte CIA1PRA = $DC00 ; CIA 1 DRA, keyboard column drive (and joystick control port #2)
&ubyte CIA1PRB = $DC01 ; CIA 1 DRB, keyboard row port (and joystick control port #1)
&ubyte CIA1DDRA = $DC02 ; CIA 1 DDRA, keyboard column
&ubyte CIA1DDRB = $DC03 ; CIA 1 DDRB, keyboard row
&ubyte CIA1TAL = $DC04 ; CIA 1 timer A low byte
&ubyte CIA1TAH = $DC05 ; CIA 1 timer A high byte
&ubyte CIA1TBL = $DC06 ; CIA 1 timer B low byte
&ubyte CIA1TBH = $DC07 ; CIA 1 timer B high byte
&ubyte CIA1TOD10 = $DC08 ; time of day, 1/10 sec.
&ubyte CIA1TODSEC = $DC09 ; time of day, seconds
&ubyte CIA1TODMMIN = $DC0A ; time of day, minutes
&ubyte CIA1TODHR = $DC0B ; time of day, hours
&ubyte CIA1SDR = $DC0C ; Serial Data Register
&ubyte CIA1ICR = $DC0D
&ubyte CIA1CRA = $DC0E
&ubyte CIA1CRB = $DC0F
memory ubyte CIA2PRA = $DD00 ; CIA 2 DRA, serial port and video address
memory ubyte CIA2PRB = $DD01 ; CIA 2 DRB, RS232 port / USERPORT
memory ubyte CIA2DDRA = $DD02 ; CIA 2 DDRA, serial port and video address
memory ubyte CIA2DDRB = $DD03 ; CIA 2 DDRB, RS232 port / USERPORT
memory ubyte CIA2TAL = $DD04 ; CIA 2 timer A low byte
memory ubyte CIA2TAH = $DD05 ; CIA 2 timer A high byte
memory ubyte CIA2TBL = $DD06 ; CIA 2 timer B low byte
memory ubyte CIA2TBH = $DD07 ; CIA 2 timer B high byte
memory ubyte CIA2TOD10 = $DD08 ; time of day, 1/10 sec.
memory ubyte CIA2TODSEC = $DD09 ; time of day, seconds
memory ubyte CIA2TODMIN = $DD0A ; time of day, minutes
memory ubyte CIA2TODHR = $DD0B ; time of day, hours
memory ubyte CIA2SDR = $DD0C ; Serial Data Register
memory ubyte CIA2ICR = $DD0D
memory ubyte CIA2CRA = $DD0E
memory ubyte CIA2CRB = $DD0F
&ubyte CIA2PRA = $DD00 ; CIA 2 DRA, serial port and video address
&ubyte CIA2PRB = $DD01 ; CIA 2 DRB, RS232 port / USERPORT
&ubyte CIA2DDRA = $DD02 ; CIA 2 DDRA, serial port and video address
&ubyte CIA2DDRB = $DD03 ; CIA 2 DDRB, RS232 port / USERPORT
&ubyte CIA2TAL = $DD04 ; CIA 2 timer A low byte
&ubyte CIA2TAH = $DD05 ; CIA 2 timer A high byte
&ubyte CIA2TBL = $DD06 ; CIA 2 timer B low byte
&ubyte CIA2TBH = $DD07 ; CIA 2 timer B high byte
&ubyte CIA2TOD10 = $DD08 ; time of day, 1/10 sec.
&ubyte CIA2TODSEC = $DD09 ; time of day, seconds
&ubyte CIA2TODMIN = $DD0A ; time of day, minutes
&ubyte CIA2TODHR = $DD0B ; time of day, hours
&ubyte CIA2SDR = $DD0C ; Serial Data Register
&ubyte CIA2ICR = $DD0D
&ubyte CIA2CRA = $DD0E
&ubyte CIA2CRB = $DD0F
; ---- end of CIA registers ----
; ---- SID 6581/8580 registers ----
memory ubyte FREQLO1 = $D400 ; channel 1 freq lo
memory ubyte FREQHI1 = $D401 ; channel 1 freq hi
memory uword FREQ1 = $D400 ; channel 1 freq (word)
memory ubyte PWLO1 = $D402 ; channel 1 pulse width lo (7-0)
memory ubyte PWHI1 = $D403 ; channel 1 pulse width hi (11-8)
memory uword PW1 = $D402 ; channel 1 pulse width (word)
memory ubyte CR1 = $D404 ; channel 1 voice control register
memory ubyte AD1 = $D405 ; channel 1 attack & decay
memory ubyte SR1 = $D406 ; channel 1 sustain & release
memory ubyte FREQLO2 = $D407 ; channel 2 freq lo
memory ubyte FREQHI2 = $D408 ; channel 2 freq hi
memory uword FREQ2 = $D407 ; channel 2 freq (word)
memory ubyte PWLO2 = $D409 ; channel 2 pulse width lo (7-0)
memory ubyte PWHI2 = $D40A ; channel 2 pulse width hi (11-8)
memory uword PW2 = $D409 ; channel 2 pulse width (word)
memory ubyte CR2 = $D40B ; channel 2 voice control register
memory ubyte AD2 = $D40C ; channel 2 attack & decay
memory ubyte SR2 = $D40D ; channel 2 sustain & release
memory ubyte FREQLO3 = $D40E ; channel 3 freq lo
memory ubyte FREQHI3 = $D40F ; channel 3 freq hi
memory uword FREQ3 = $D40E ; channel 3 freq (word)
memory ubyte PWLO3 = $D410 ; channel 3 pulse width lo (7-0)
memory ubyte PWHI3 = $D411 ; channel 3 pulse width hi (11-8)
memory uword PW3 = $D410 ; channel 3 pulse width (word)
memory ubyte CR3 = $D412 ; channel 3 voice control register
memory ubyte AD3 = $D413 ; channel 3 attack & decay
memory ubyte SR3 = $D414 ; channel 3 sustain & release
memory ubyte FCLO = $D415 ; filter cutoff lo (2-0)
memory ubyte FCHI = $D416 ; filter cutoff hi (10-3)
memory uword FC = $D415 ; filter cutoff (word)
memory ubyte RESFILT = $D417 ; filter resonance and routing
memory ubyte MVOL = $D418 ; filter mode and main volume control
memory ubyte POTX = $D419 ; potentiometer X
memory ubyte POTY = $D41A ; potentiometer Y
memory ubyte OSC3 = $D41B ; channel 3 oscillator value read
memory ubyte ENV3 = $D41C ; channel 3 envelope value read
&ubyte FREQLO1 = $D400 ; channel 1 freq lo
&ubyte FREQHI1 = $D401 ; channel 1 freq hi
&uword FREQ1 = $D400 ; channel 1 freq (word)
&ubyte PWLO1 = $D402 ; channel 1 pulse width lo (7-0)
&ubyte PWHI1 = $D403 ; channel 1 pulse width hi (11-8)
&uword PW1 = $D402 ; channel 1 pulse width (word)
&ubyte CR1 = $D404 ; channel 1 voice control register
&ubyte AD1 = $D405 ; channel 1 attack & decay
&ubyte SR1 = $D406 ; channel 1 sustain & release
&ubyte FREQLO2 = $D407 ; channel 2 freq lo
&ubyte FREQHI2 = $D408 ; channel 2 freq hi
&uword FREQ2 = $D407 ; channel 2 freq (word)
&ubyte PWLO2 = $D409 ; channel 2 pulse width lo (7-0)
&ubyte PWHI2 = $D40A ; channel 2 pulse width hi (11-8)
&uword PW2 = $D409 ; channel 2 pulse width (word)
&ubyte CR2 = $D40B ; channel 2 voice control register
&ubyte AD2 = $D40C ; channel 2 attack & decay
&ubyte SR2 = $D40D ; channel 2 sustain & release
&ubyte FREQLO3 = $D40E ; channel 3 freq lo
&ubyte FREQHI3 = $D40F ; channel 3 freq hi
&uword FREQ3 = $D40E ; channel 3 freq (word)
&ubyte PWLO3 = $D410 ; channel 3 pulse width lo (7-0)
&ubyte PWHI3 = $D411 ; channel 3 pulse width hi (11-8)
&uword PW3 = $D410 ; channel 3 pulse width (word)
&ubyte CR3 = $D412 ; channel 3 voice control register
&ubyte AD3 = $D413 ; channel 3 attack & decay
&ubyte SR3 = $D414 ; channel 3 sustain & release
&ubyte FCLO = $D415 ; filter cutoff lo (2-0)
&ubyte FCHI = $D416 ; filter cutoff hi (10-3)
&uword FC = $D415 ; filter cutoff (word)
&ubyte RESFILT = $D417 ; filter resonance and routing
&ubyte MVOL = $D418 ; filter mode and main volume control
&ubyte POTX = $D419 ; potentiometer X
&ubyte POTY = $D41A ; potentiometer Y
&ubyte OSC3 = $D41B ; channel 3 oscillator value read
&ubyte ENV3 = $D41C ; channel 3 envelope value read
; ---- end of SID registers ----
@ -235,7 +235,7 @@ asmsub GETIN () -> clobbers(X,Y) -> (ubyte @ A) = $FFE4 ; (via 810 ($32A))
asmsub CLALL () -> clobbers(A,X) -> () = $FFE7 ; (via 812 ($32C)) close all files
asmsub UDTIM () -> clobbers(A,X) -> () = $FFEA ; update the software clock
asmsub SCREEN () -> clobbers() -> (ubyte @ X, ubyte @ Y) = $FFED ; read number of screen rows and columns
asmsub PLOT (ubyte dir @ Pc, ubyte col @ Y, ubyte row @ X) -> clobbers() -> (ubyte @ X, ubyte @ Y) = $FFF0 ; read/set position of cursor on screen. See c64scr.PLOT for a 'safe' wrapper that preserves X.
asmsub PLOT (ubyte dir @ Pc, ubyte col @ Y, ubyte row @ X) -> clobbers() -> (ubyte @ X, ubyte @ Y) = $FFF0 ; read/set position of cursor on screen. Use c64scr.plot for a 'safe' wrapper that preserves X.
asmsub IOBASE () -> clobbers() -> (uword @ XY) = $FFF3 ; read base address of I/O devices
; ---- end of C64 kernal routines ----

View File

@ -54,40 +54,39 @@ asmsub ubyte2hex (ubyte value @ A) -> clobbers() -> (ubyte @ A, ubyte @ Y) {
pha
and #$0f
tax
ldy hex_digits,x
ldy _hex_digits,x
pla
lsr a
lsr a
lsr a
lsr a
tax
lda hex_digits,x
lda _hex_digits,x
ldx c64.SCRATCH_ZPREGX
rts
hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as well
_hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as well
}}
}
str word2hex_output = "1234" ; 0-terminated, to make printing easier
asmsub uword2hex (uword value @ AY) -> clobbers(A,Y) -> () {
; ---- convert 16 bit uword in A/Y into 4-character hexadecimal string into memory 'word2hex_output'
; ---- convert 16 bit uword in A/Y into 4-character hexadecimal string 'uword2hex.output' (0-terminated)
%asm {{
sta c64.SCRATCH_ZPREG
tya
jsr ubyte2hex
sta word2hex_output
sty word2hex_output+1
sta output
sty output+1
lda c64.SCRATCH_ZPREG
jsr ubyte2hex
sta word2hex_output+2
sty word2hex_output+3
sta output+2
sty output+3
rts
output .text "0000", $00 ; 0-terminated output buffer (to make printing easier)
}}
}
ubyte[3] word2bcd_bcdbuff = [0, 0, 0]
asmsub uword2bcd (uword value @ AY) -> clobbers(A,Y) -> () {
; Convert an 16 bit binary value to BCD
;
@ -99,47 +98,55 @@ asmsub uword2bcd (uword value @ AY) -> clobbers(A,Y) -> () {
%asm {{
sta c64.SCRATCH_ZPB1
sty c64.SCRATCH_ZPREG
php
pla ; read status register
and #%00000100
sta _had_irqd
sei ; disable interrupts because of bcd math
sed ; switch to decimal mode
lda #0 ; ensure the result is clear
sta word2bcd_bcdbuff+0
sta word2bcd_bcdbuff+1
sta word2bcd_bcdbuff+2
sta bcdbuff+0
sta bcdbuff+1
sta bcdbuff+2
ldy #16 ; the number of source bits
- asl c64.SCRATCH_ZPB1 ; shift out one bit
rol c64.SCRATCH_ZPREG
lda word2bcd_bcdbuff+0 ; and add into result
adc word2bcd_bcdbuff+0
sta word2bcd_bcdbuff+0
lda word2bcd_bcdbuff+1 ; propagating any carry
adc word2bcd_bcdbuff+1
sta word2bcd_bcdbuff+1
lda word2bcd_bcdbuff+2 ; ... thru whole result
adc word2bcd_bcdbuff+2
sta word2bcd_bcdbuff+2
lda bcdbuff+0 ; and add into result
adc bcdbuff+0
sta bcdbuff+0
lda bcdbuff+1 ; propagating any carry
adc bcdbuff+1
sta bcdbuff+1
lda bcdbuff+2 ; ... thru whole result
adc bcdbuff+2
sta bcdbuff+2
dey ; and repeat for next bit
bne -
cld ; back to binary
cli ; enable interrupts again @todo don't re-enable if it wasn't enabled before
rts
lda _had_irqd
bne +
cli ; enable interrupts again (only if they were enabled before)
+ rts
_had_irqd .byte 0
bcdbuff .byte 0,0,0
}}
}
ubyte[5] word2decimal_output = 0
asmsub uword2decimal (uword value @ AY) -> clobbers(A,Y) -> () {
; ---- convert 16 bit uword in A/Y into decimal string into memory 'word2decimal_output'
asmsub uword2decimal (uword value @ AY) -> clobbers(A) -> (ubyte @ Y) {
; ---- convert 16 bit uword in A/Y into 0-terminated decimal string into memory 'uword2decimal.output'
; returns length of resulting string in Y
%asm {{
jsr uword2bcd
lda word2bcd_bcdbuff+2
lda uword2bcd.bcdbuff+2
clc
adc #'0'
sta word2decimal_output
sta output
ldy #1
lda word2bcd_bcdbuff+1
lda uword2bcd.bcdbuff+1
jsr +
lda word2bcd_bcdbuff+0
lda uword2bcd.bcdbuff+0
+ pha
lsr a
@ -148,153 +155,135 @@ asmsub uword2decimal (uword value @ AY) -> clobbers(A,Y) -> () {
lsr a
clc
adc #'0'
sta word2decimal_output,y
sta output,y
iny
pla
and #$0f
adc #'0'
sta word2decimal_output,y
sta output,y
iny
rts
}}
}
asmsub str2byte (str string @ AY) -> clobbers(Y) -> (byte @ A) {
%asm {{
; -- convert string (address in A/Y) to byte in A
; doesn't use any kernal routines
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
ldy #0
lda (c64.SCRATCH_ZPWORD1),y
cmp #'-'
beq +
jmp str2ubyte._enter
+ inc c64.SCRATCH_ZPWORD1
bne +
inc c64.SCRATCH_ZPWORD1+1
+ jsr str2ubyte._enter
eor #$ff
sec
adc #0
rts
}}
}
asmsub str2ubyte (str string @ AY) -> clobbers(Y) -> (ubyte @ A) {
%asm {{
; -- convert string (address in A/Y) to ubyte in A
; doesn't use any kernal routines
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
_enter jsr _numlen ; Y= slen
lda #0
dey
bpl +
sta output,y
rts
+ lda (c64.SCRATCH_ZPWORD1),y
sec
sbc #'0'
dey
bpl +
rts
+ sta c64.SCRATCH_ZPREG ;result
lda (c64.SCRATCH_ZPWORD1),y
sec
sbc #'0'
asl a
sta c64.SCRATCH_ZPB1
asl a
asl a
clc
adc c64.SCRATCH_ZPB1
clc
adc c64.SCRATCH_ZPREG
dey
bpl +
rts
+ sta c64.SCRATCH_ZPREG
lda (c64.SCRATCH_ZPWORD1),y
tay
lda _hundreds-'0',y
clc
adc c64.SCRATCH_ZPREG
rts
_hundreds .byte 0, 100, 200
_numlen
;-- return the length of the numeric string at ZPWORD1, in Y
output .text "00000", $00 ; 0 terminated
}}
}
asmsub str2uword(str string @ AY) -> clobbers() -> (uword @ AY) {
; -- returns the unsigned word value of the string number argument in AY
; the number may NOT be preceded by a + sign and may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
%asm {{
_result = c64.SCRATCH_ZPWORD2
sta _mod+1
sty _mod+2
ldy #0
- lda (c64.SCRATCH_ZPWORD1),y
cmp #'0'
bmi +
cmp #':' ; one after '9'
sty _result
sty _result+1
_mod lda $ffff,y ; modified
sec
sbc #48
bpl +
iny
bne -
+ rts
_done ; return result
lda _result
ldy _result+1
rts
+ cmp #10
bcs _done
; add digit to result
pha
jsr _result_times_10
pla
clc
adc _result
sta _result
bcc +
inc _result+1
+ iny
bne _mod
; never reached
_result_times_10 ; (W*4 + W)*2
lda _result+1
sta c64.SCRATCH_ZPREG
lda _result
asl a
rol c64.SCRATCH_ZPREG
asl a
rol c64.SCRATCH_ZPREG
clc
adc _result
sta _result
lda c64.SCRATCH_ZPREG
adc _result+1
asl _result
rol a
sta _result+1
rts
}}
}
asmsub c64flt_FREADSTR (ubyte length @ A) -> clobbers(A,X,Y) -> () = $b7b5 ; @todo needed for (slow) str conversion below
asmsub c64flt_GETADR () -> clobbers(X) -> (ubyte @ Y, ubyte @ A) = $b7f7 ; @todo needed for (slow) str conversion below
asmsub c64flt_FTOSWORDYA () -> clobbers(X) -> (ubyte @ Y, ubyte @ A) = $b1aa ; @todo needed for (slow) str conversion below
asmsub str2uword(str string @ AY) -> clobbers() -> (uword @ AY) {
asmsub str2word(str string @ AY) -> clobbers() -> (word @ AY) {
; -- returns the signed word value of the string number argument in AY
; the number may be preceded by a + or - sign but may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
%asm {{
;-- convert string (address in A/Y) to uword number in A/Y
; @todo don't use the (slow) kernel floating point conversion
sta $22
sty $23
jsr _strlen2233
tya
stx c64.SCRATCH_ZPREGX
jsr c64flt_FREADSTR ; string to fac1
jsr c64flt_GETADR ; fac1 to unsigned word in Y/A
ldx c64.SCRATCH_ZPREGX
sta c64.SCRATCH_ZPREG
tya
ldy c64.SCRATCH_ZPREG
rts
_strlen2233
;-- return the length of the (zero-terminated) string at $22/$23, in Y
_result = c64.SCRATCH_ZPWORD2
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
ldy #0
- lda ($22),y
sty _result
sty _result+1
sty _negative
lda (c64.SCRATCH_ZPWORD1),y
cmp #'+'
bne +
iny
+ cmp #'-'
bne _parse
inc _negative
iny
_parse lda (c64.SCRATCH_ZPWORD1),y
sec
sbc #48
bpl _digit
_done ; return result
lda _negative
beq +
iny
bne -
+ rts
}}
}
asmsub str2word(str string @ AY) -> clobbers() -> (word @ AY) {
%asm {{
;-- convert string (address in A/Y) to signed word number in A/Y
; @todo don't use the (slow) kernel floating point conversion
sta $22
sty $23
jsr str2uword._strlen2233
tya
stx c64.SCRATCH_ZPREGX
jsr c64flt_FREADSTR ; string to fac1
jsr c64flt_FTOSWORDYA ; fac1 to unsigned word in Y/A
ldx c64.SCRATCH_ZPREGX
sta c64.SCRATCH_ZPREG
tya
ldy c64.SCRATCH_ZPREG
sec
lda #0
sbc _result
sta _result
lda #0
sbc _result+1
sta _result+1
+ lda _result
ldy _result+1
rts
_digit cmp #10
bcs _done
; add digit to result
pha
jsr str2uword._result_times_10
pla
clc
adc _result
sta _result
bcc +
inc _result+1
+ iny
bne _parse
; never reached
_negative .byte 0
}}
}
; @todo string to 32 bit unsigned integer http://www.6502.org/source/strings/ascii-to-32bit.html
asmsub set_irqvec_excl() -> clobbers(A) -> () {
%asm {{
sei
@ -304,7 +293,9 @@ asmsub set_irqvec_excl() -> clobbers(A) -> () {
sta c64.CINV+1
cli
rts
_irq_handler jsr irq.irq
_irq_handler jsr set_irqvec._irq_handler_init
jsr irq.irq
jsr set_irqvec._irq_handler_end
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
lda c64.CIA1ICR ; acknowledge CIA1 interrupt
@ -321,10 +312,64 @@ asmsub set_irqvec() -> clobbers(A) -> () {
sta c64.CINV+1
cli
rts
_irq_handler jsr irq.irq
_irq_handler jsr _irq_handler_init
jsr irq.irq
jsr _irq_handler_end
jmp c64.IRQDFRT ; continue with normal kernel irq routine
}}
_irq_handler_init
; save all zp scratch registers and the X register as these might be clobbered by the irq routine
stx IRQ_X_REG
lda c64.SCRATCH_ZPB1
sta IRQ_SCRATCH_ZPB1
lda c64.SCRATCH_ZPREG
sta IRQ_SCRATCH_ZPREG
lda c64.SCRATCH_ZPREGX
sta IRQ_SCRATCH_ZPREGX
lda c64.SCRATCH_ZPWORD1
sta IRQ_SCRATCH_ZPWORD1
lda c64.SCRATCH_ZPWORD1+1
sta IRQ_SCRATCH_ZPWORD1+1
lda c64.SCRATCH_ZPWORD2
sta IRQ_SCRATCH_ZPWORD2
lda c64.SCRATCH_ZPWORD2+1
sta IRQ_SCRATCH_ZPWORD2+1
; stack protector; make sure we don't clobber the top of the evaluation stack
dex
dex
dex
dex
dex
dex
rts
_irq_handler_end
; restore all zp scratch registers and the X register
lda IRQ_SCRATCH_ZPB1
sta c64.SCRATCH_ZPB1
lda IRQ_SCRATCH_ZPREG
sta c64.SCRATCH_ZPREG
lda IRQ_SCRATCH_ZPREGX
sta c64.SCRATCH_ZPREGX
lda IRQ_SCRATCH_ZPWORD1
sta c64.SCRATCH_ZPWORD1
lda IRQ_SCRATCH_ZPWORD1+1
sta c64.SCRATCH_ZPWORD1+1
lda IRQ_SCRATCH_ZPWORD2
sta c64.SCRATCH_ZPWORD2
lda IRQ_SCRATCH_ZPWORD2+1
sta c64.SCRATCH_ZPWORD2+1
ldx IRQ_X_REG
rts
IRQ_X_REG .byte 0
IRQ_SCRATCH_ZPB1 .byte 0
IRQ_SCRATCH_ZPREG .byte 0
IRQ_SCRATCH_ZPREGX .byte 0
IRQ_SCRATCH_ZPWORD1 .word 0
IRQ_SCRATCH_ZPWORD2 .word 0
}}
}
@ -357,7 +402,9 @@ asmsub set_rasterirq(uword rasterpos @ AY) -> clobbers(A) -> () {
rts
_raster_irq_handler
jsr set_irqvec._irq_handler_init
jsr irq.irq
jsr set_irqvec._irq_handler_end
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
jmp c64.IRQDFRT
@ -396,7 +443,9 @@ asmsub set_rasterirq_excl(uword rasterpos @ AY) -> clobbers(A) -> () {
rts
_raster_irq_handler
jsr set_irqvec._irq_handler_init
jsr irq.irq
jsr set_irqvec._irq_handler_end
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
jmp c64.IRQDFEND ; end irq processing - don't call kernel
@ -473,7 +522,7 @@ _loop sta c64.Colors,y
}
asmsub scroll_left_full (ubyte alsocolors @ Pc) -> clobbers(A, Y) -> () {
asmsub scroll_left_full (ubyte alsocolors @ Pc) -> clobbers(A, Y) -> () {
; ---- scroll the whole screen 1 character to the left
; contents of the rightmost column are unchanged, you should clear/refill this yourself
; Carry flag determines if screen color data must be scrolled too
@ -534,7 +583,7 @@ _scroll_screen ; scroll the screen memory
}
asmsub scroll_right_full (ubyte alsocolors @ Pc) -> clobbers(A) -> () {
asmsub scroll_right_full (ubyte alsocolors @ Pc) -> clobbers(A) -> () {
; ---- scroll the whole screen 1 character to the right
; contents of the leftmost column are unchanged, you should clear/refill this yourself
; Carry flag determines if screen color data must be scrolled too
@ -587,7 +636,7 @@ _scroll_screen ; scroll the screen memory
}
asmsub scroll_up_full (ubyte alsocolors @ Pc) -> clobbers(A) -> () {
asmsub scroll_up_full (ubyte alsocolors @ Pc) -> clobbers(A) -> () {
; ---- scroll the whole screen 1 character up
; contents of the bottom row are unchanged, you should refill/clear this yourself
; Carry flag determines if screen color data must be scrolled too
@ -640,7 +689,7 @@ _scroll_screen ; scroll the screen memory
}
asmsub scroll_down_full (ubyte alsocolors @ Pc) -> clobbers(A) -> () {
asmsub scroll_down_full (ubyte alsocolors @ Pc) -> clobbers(A) -> () {
; ---- scroll the whole screen 1 character down
; contents of the top row are unchanged, you should refill/clear this yourself
; Carry flag determines if screen color data must be scrolled too
@ -713,27 +762,6 @@ asmsub print (str text @ AY) -> clobbers(A,Y) -> () {
}
asmsub print_p (str_p text @ AY) -> clobbers(A) -> (ubyte @ Y) {
; ---- print pstring (length as first byte) from A/Y, returns str len in Y
%asm {{
sta c64.SCRATCH_ZPB1
sty c64.SCRATCH_ZPREG
stx c64.SCRATCH_ZPREGX
ldy #0
lda (c64.SCRATCH_ZPB1),y
beq +
tax
- iny
lda (c64.SCRATCH_ZPB1),y
jsr c64.CHROUT
dex
bne -
+ ldx c64.SCRATCH_ZPREGX
rts ; output string length is in Y
}}
}
asmsub print_ub0 (ubyte value @ A) -> clobbers(A,Y) -> () {
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
%asm {{
@ -763,13 +791,12 @@ _print_byte_digits
bne _print_hundreds
cpx #'0'
bne _print_tens
pla
jmp c64.CHROUT
jmp _end
_print_hundreds tya
jsr c64.CHROUT
_print_tens txa
jsr c64.CHROUT
pla
_end pla
jsr c64.CHROUT
ldx c64.SCRATCH_ZPREGX
rts
@ -867,7 +894,7 @@ asmsub print_uw0 (uword value @ AY) -> clobbers(A,Y) -> () {
%asm {{
jsr c64utils.uword2decimal
ldy #0
- lda c64utils.word2decimal_output,y
- lda c64utils.uword2decimal.output,y
jsr c64.CHROUT
iny
cpy #5
@ -882,25 +909,25 @@ asmsub print_uw (uword value @ AY) -> clobbers(A,Y) -> () {
%asm {{
jsr c64utils.uword2decimal
ldy #0
lda c64utils.word2decimal_output
lda c64utils.uword2decimal.output
cmp #'0'
bne _pr_decimal
iny
lda c64utils.word2decimal_output+1
lda c64utils.uword2decimal.output+1
cmp #'0'
bne _pr_decimal
iny
lda c64utils.word2decimal_output+2
lda c64utils.uword2decimal.output+2
cmp #'0'
bne _pr_decimal
iny
lda c64utils.word2decimal_output+3
lda c64utils.uword2decimal.output+3
cmp #'0'
bne _pr_decimal
iny
_pr_decimal
lda c64utils.word2decimal_output,y
lda c64utils.uword2decimal.output,y
jsr c64.CHROUT
iny
cpy #5
@ -910,7 +937,7 @@ _pr_decimal
}
asmsub print_w (word value @ AY) -> clobbers(A,Y) -> () {
; ---- print the (signed) word in A/Y in decimal form, without left padding 0s
; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
%asm {{
cpy #0
bpl +
@ -931,7 +958,7 @@ asmsub print_w (word value @ AY) -> clobbers(A,Y) -> () {
}
asmsub input_chars (uword buffer @ AY) -> clobbers(A) -> (ubyte @ Y) {
; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y.
; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. (string is terminated with a 0 byte as well)
; It assumes the keyboard is selected as I/O channel!
%asm {{
@ -973,6 +1000,25 @@ _screenrows .word $0400 + range(0, 1000, 40)
}}
}
asmsub getchr (ubyte col @Y, ubyte row @A) -> clobbers(Y) -> (ubyte @ A) {
; ---- get the character in the screen matrix at the given location
%asm {{
sty c64.SCRATCH_ZPB1
asl a
tay
lda setchr._screenrows+1,y
sta _mod+2
lda setchr._screenrows,y
clc
adc c64.SCRATCH_ZPB1
sta _mod+1
bcc _mod
inc _mod+2
_mod lda $ffff ; modified
rts
}}
}
asmsub setclr (ubyte col @Y, ubyte row @A) -> clobbers(A) -> () {
; ---- set the color in SCRATCH_ZPB1 on the screen matrix at the given position
%asm {{
@ -995,6 +1041,24 @@ _colorrows .word $d800 + range(0, 1000, 40)
}}
}
asmsub getclr (ubyte col @Y, ubyte row @A) -> clobbers(Y) -> (ubyte @ A) {
; ---- get the color in the screen color matrix at the given location
%asm {{
sty c64.SCRATCH_ZPB1
asl a
tay
lda setclr._colorrows+1,y
sta _mod+2
lda setclr._colorrows,y
clc
adc c64.SCRATCH_ZPB1
sta _mod+1
bcc _mod
inc _mod+2
_mod lda $ffff ; modified
rts
}}
}
sub setcc (ubyte column, ubyte row, ubyte char, ubyte color) {
; ---- set char+color at the given position on the screen
@ -1022,7 +1086,7 @@ _colormod sta $ffff ; modified
}}
}
asmsub PLOT (ubyte col @ Y, ubyte row @ A) -> clobbers(A) -> () {
asmsub plot (ubyte col @ Y, ubyte row @ A) -> clobbers(A) -> () {
; ---- safe wrapper around PLOT kernel routine, to save the X register.
%asm {{
stx c64.SCRATCH_ZPREGX

View File

@ -58,29 +58,29 @@ multiply_words .proc
stx c64.SCRATCH_ZPREGX
mult16 lda #$00
sta multiply_words_result+2 ; clear upper bits of product
sta multiply_words_result+3
sta result+2 ; clear upper bits of product
sta result+3
ldx #16 ; for all 16 bits...
- lsr c64.SCRATCH_ZPWORD1+1 ; divide multiplier by 2
ror c64.SCRATCH_ZPWORD1
bcc +
lda multiply_words_result+2 ; get upper half of product and add multiplicand
lda result+2 ; get upper half of product and add multiplicand
clc
adc c64.SCRATCH_ZPWORD2
sta multiply_words_result+2
lda multiply_words_result+3
sta result+2
lda result+3
adc c64.SCRATCH_ZPWORD2+1
+ ror a ; rotate partial product
sta multiply_words_result+3
ror multiply_words_result+2
ror multiply_words_result+1
ror multiply_words_result
sta result+3
ror result+2
ror result+1
ror result
dex
bne -
ldx c64.SCRATCH_ZPREGX
rts
multiply_words_result .byte 0,0,0,0
result .byte 0,0,0,0
.pend
@ -241,26 +241,24 @@ mul_byte_3 .proc
.pend
mul_word_3 .proc
; W + W*2
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPWORD1
; W*2 + W
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPWORD1+1
asl c64.SCRATCH_ZPWORD1
rol c64.SCRATCH_ZPWORD1+1
sta c64.SCRATCH_ZPREG
lda c64.ESTACK_LO+1,x
asl a
rol c64.SCRATCH_ZPREG
clc
lda c64.ESTACK_LO+1,x
adc c64.SCRATCH_ZPWORD1
adc c64.ESTACK_LO+1,x
sta c64.ESTACK_LO+1,x
lda c64.ESTACK_HI+1,x
adc c64.SCRATCH_ZPWORD1+1
lda c64.SCRATCH_ZPREG
adc c64.ESTACK_HI+1,x
sta c64.ESTACK_HI+1,x
rts
.pend
mul_byte_5 .proc
; X + X*4
; X*4 + X
lda c64.ESTACK_LO+1,x
asl a
asl a
@ -271,58 +269,49 @@ mul_byte_5 .proc
.pend
mul_word_5 .proc
; W + W*4
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPWORD1
; W*4 + W
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPWORD1+1
asl c64.SCRATCH_ZPWORD1
rol c64.SCRATCH_ZPWORD1+1
asl c64.SCRATCH_ZPWORD1
rol c64.SCRATCH_ZPWORD1+1
sta c64.SCRATCH_ZPREG
lda c64.ESTACK_LO+1,x
asl a
rol c64.SCRATCH_ZPREG
asl a
rol c64.SCRATCH_ZPREG
clc
lda c64.ESTACK_LO+1,x
adc c64.SCRATCH_ZPWORD1
adc c64.ESTACK_LO+1,x
sta c64.ESTACK_LO+1,x
lda c64.ESTACK_HI+1,x
adc c64.SCRATCH_ZPWORD1+1
lda c64.SCRATCH_ZPREG
adc c64.ESTACK_HI+1,x
sta c64.ESTACK_HI+1,x
rts
.pend
mul_byte_6 .proc
; X*2 + X*4
; (X*2 + X)*2
lda c64.ESTACK_LO+1,x
asl a
sta c64.SCRATCH_ZPREG
clc
adc c64.ESTACK_LO+1,x
asl a
clc
adc c64.SCRATCH_ZPREG
sta c64.ESTACK_LO+1,x
rts
.pend
mul_word_6 .proc
; W*2 + W*4
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPWORD1
; (W*2 + W)*2
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPWORD1+1
asl c64.SCRATCH_ZPWORD1
rol c64.SCRATCH_ZPWORD1+1
lda c64.SCRATCH_ZPWORD1
sta c64.SCRATCH_ZPWORD2
lda c64.SCRATCH_ZPWORD1+1
sta c64.SCRATCH_ZPWORD2+1
asl c64.SCRATCH_ZPWORD2
rol c64.SCRATCH_ZPWORD2+1
sta c64.SCRATCH_ZPREG
lda c64.ESTACK_LO+1,x
asl a
rol c64.SCRATCH_ZPREG
clc
lda c64.SCRATCH_ZPWORD1
adc c64.SCRATCH_ZPWORD2
adc c64.ESTACK_LO+1,x
sta c64.ESTACK_LO+1,x
lda c64.SCRATCH_ZPWORD1+1
adc c64.SCRATCH_ZPWORD2+1
lda c64.SCRATCH_ZPREG
adc c64.ESTACK_HI+1,x
asl c64.ESTACK_LO+1,x
rol a
sta c64.ESTACK_HI+1,x
rts
.pend
@ -341,29 +330,26 @@ mul_byte_7 .proc
mul_word_7 .proc
; W*8 - W
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPWORD1+1
asl c64.SCRATCH_ZPWORD1
rol c64.SCRATCH_ZPWORD1+1
asl c64.SCRATCH_ZPWORD1
rol c64.SCRATCH_ZPWORD1+1
asl c64.SCRATCH_ZPWORD1
rol c64.SCRATCH_ZPWORD1+1
lda c64.SCRATCH_ZPWORD1
sta c64.SCRATCH_ZPREG
lda c64.ESTACK_LO+1,x
asl a
rol c64.SCRATCH_ZPREG
asl a
rol c64.SCRATCH_ZPREG
asl a
rol c64.SCRATCH_ZPREG
sec
lda c64.SCRATCH_ZPWORD1
sbc c64.ESTACK_LO+1,x
sta c64.ESTACK_LO+1,x
lda c64.SCRATCH_ZPWORD1+1
lda c64.SCRATCH_ZPREG
sbc c64.ESTACK_HI+1,x
sta c64.ESTACK_HI+1,x
rts
.pend
mul_byte_9 .proc
; X + X*8
; X*8 + X
lda c64.ESTACK_LO+1,x
asl a
asl a
@ -375,80 +361,67 @@ mul_byte_9 .proc
.pend
mul_word_9 .proc
; W + W*8
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPWORD1
; W*8 + W
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPWORD1+1
asl c64.SCRATCH_ZPWORD1
rol c64.SCRATCH_ZPWORD1+1
asl c64.SCRATCH_ZPWORD1
rol c64.SCRATCH_ZPWORD1+1
asl c64.SCRATCH_ZPWORD1
rol c64.SCRATCH_ZPWORD1+1
sta c64.SCRATCH_ZPREG
lda c64.ESTACK_LO+1,x
asl a
rol c64.SCRATCH_ZPREG
asl a
rol c64.SCRATCH_ZPREG
asl a
rol c64.SCRATCH_ZPREG
clc
lda c64.ESTACK_LO+1,x
adc c64.SCRATCH_ZPWORD1
adc c64.ESTACK_LO+1,x
sta c64.ESTACK_LO+1,x
lda c64.ESTACK_HI+1,x
adc c64.SCRATCH_ZPWORD1+1
lda c64.SCRATCH_ZPREG
adc c64.ESTACK_HI+1,x
sta c64.ESTACK_HI+1,x
rts
.pend
mul_byte_10 .proc
; X + X + X*8
; (X*4 + X)*2
lda c64.ESTACK_LO+1,x
asl a
asl a
clc
adc c64.ESTACK_LO+1,x
asl a
clc
adc c64.ESTACK_LO+1,x
clc
adc c64.ESTACK_LO+1,x
sta c64.ESTACK_LO+1,x
rts
.pend
mul_word_10 .proc
; W*2 + W*8
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPWORD1
; (W*4 + W)*2
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPWORD1+1
asl c64.SCRATCH_ZPWORD1
rol c64.SCRATCH_ZPWORD1+1
lda c64.SCRATCH_ZPWORD1
sta c64.SCRATCH_ZPWORD2
lda c64.SCRATCH_ZPWORD1+1
sta c64.SCRATCH_ZPWORD2+1
asl c64.SCRATCH_ZPWORD2
rol c64.SCRATCH_ZPWORD2+1
asl c64.SCRATCH_ZPWORD2
rol c64.SCRATCH_ZPWORD2+1
sta c64.SCRATCH_ZPREG
lda c64.ESTACK_LO+1,x
asl a
rol c64.SCRATCH_ZPREG
asl a
rol c64.SCRATCH_ZPREG
clc
lda c64.SCRATCH_ZPWORD1
adc c64.SCRATCH_ZPWORD2
adc c64.ESTACK_LO+1,x
sta c64.ESTACK_LO+1,x
lda c64.SCRATCH_ZPWORD1+1
adc c64.SCRATCH_ZPWORD2+1
lda c64.SCRATCH_ZPREG
adc c64.ESTACK_HI+1,x
asl c64.ESTACK_LO+1,x
rol a
sta c64.ESTACK_HI+1,x
rts
.pend
mul_byte_11 .proc
; X + X + X + X*8
; (X*2 + X)*4 - X
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPREG
asl a
asl a
asl a
clc
adc c64.SCRATCH_ZPREG
clc
adc c64.SCRATCH_ZPREG
clc
adc c64.SCRATCH_ZPREG
adc c64.ESTACK_LO+1,x
asl a
asl a
sec
sbc c64.ESTACK_LO+1,x
sta c64.ESTACK_LO+1,x
rts
.pend
@ -456,58 +429,47 @@ mul_byte_11 .proc
; mul_word_11 is skipped (too much code)
mul_byte_12 .proc
; X*4 + X*8
; (X*2 + X)*4
lda c64.ESTACK_LO+1,x
asl a
asl a
sta c64.SCRATCH_ZPREG
asl a
clc
adc c64.SCRATCH_ZPREG
adc c64.ESTACK_LO+1,x
asl a
asl a
sta c64.ESTACK_LO+1,x
rts
.pend
mul_word_12 .proc
; W*4 + W*8
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPWORD1
; (W*2 + W)*4
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPWORD1+1
asl c64.SCRATCH_ZPWORD1
rol c64.SCRATCH_ZPWORD1+1
asl c64.SCRATCH_ZPWORD1
rol c64.SCRATCH_ZPWORD1+1
lda c64.SCRATCH_ZPWORD1
sta c64.SCRATCH_ZPWORD2
lda c64.SCRATCH_ZPWORD1+1
sta c64.SCRATCH_ZPWORD2+1
asl c64.SCRATCH_ZPWORD2
rol c64.SCRATCH_ZPWORD2+1
sta c64.SCRATCH_ZPREG
lda c64.ESTACK_LO+1,x
asl a
rol c64.SCRATCH_ZPREG
clc
lda c64.SCRATCH_ZPWORD1
adc c64.SCRATCH_ZPWORD2
adc c64.ESTACK_LO+1,x
sta c64.ESTACK_LO+1,x
lda c64.SCRATCH_ZPWORD1+1
adc c64.SCRATCH_ZPWORD2+1
lda c64.SCRATCH_ZPREG
adc c64.ESTACK_HI+1,x
asl c64.ESTACK_LO+1,x
rol a
asl c64.ESTACK_LO+1,x
rol a
sta c64.ESTACK_HI+1,x
rts
.pend
mul_byte_13 .proc
; X*16 - X -X -X
; (X*2 + X)*4 + X
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPREG
asl a
clc
adc c64.ESTACK_LO+1,x
asl a
asl a
asl a
asl a
sec
sbc c64.SCRATCH_ZPREG
sec
sbc c64.SCRATCH_ZPREG
sec
sbc c64.SCRATCH_ZPREG
clc
adc c64.ESTACK_LO+1,x
sta c64.ESTACK_LO+1,x
rts
.pend
@ -515,16 +477,14 @@ mul_byte_13 .proc
; mul_word_13 is skipped (too much code)
mul_byte_14 .proc
; X*16 - X -X
; (X*8 - X)*2
lda c64.ESTACK_LO+1,x
asl a
asl a
asl a
asl a
sec
sbc c64.ESTACK_LO+1,x
sec
sec
sbc c64.ESTACK_LO+1,x
asl a
sta c64.ESTACK_LO+1,x
rts
.pend
@ -534,7 +494,6 @@ mul_byte_14 .proc
mul_byte_15 .proc
; X*16 - X
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPREG
asl a
asl a
asl a
@ -547,80 +506,70 @@ mul_byte_15 .proc
mul_word_15 .proc
; W*16 - W
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPWORD1+1
asl c64.SCRATCH_ZPWORD1
rol c64.SCRATCH_ZPWORD1+1
asl c64.SCRATCH_ZPWORD1
rol c64.SCRATCH_ZPWORD1+1
asl c64.SCRATCH_ZPWORD1
rol c64.SCRATCH_ZPWORD1+1
asl c64.SCRATCH_ZPWORD1
rol c64.SCRATCH_ZPWORD1+1
sta c64.SCRATCH_ZPREG
lda c64.ESTACK_LO+1,x
asl a
rol c64.SCRATCH_ZPREG
asl a
rol c64.SCRATCH_ZPREG
asl a
rol c64.SCRATCH_ZPREG
asl a
rol c64.SCRATCH_ZPREG
sec
lda c64.SCRATCH_ZPWORD1
sbc c64.ESTACK_LO+1,x
sta c64.ESTACK_LO+1,x
lda c64.SCRATCH_ZPWORD1+1
lda c64.SCRATCH_ZPREG
sbc c64.ESTACK_HI+1,x
sta c64.ESTACK_HI+1,x
rts
.pend
mul_byte_20 .proc
; X*4 + X*16
; (X*4 + X)*4
lda c64.ESTACK_LO+1,x
asl a
asl a
sta c64.SCRATCH_ZPREG
asl a
asl a
clc
adc c64.SCRATCH_ZPREG
adc c64.ESTACK_LO+1,x
asl a
asl a
sta c64.ESTACK_LO+1,x
rts
.pend
mul_word_20 .proc
; W*4 + W*16
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPWORD1
; (W*4 + W)*4
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPWORD1+1
asl c64.SCRATCH_ZPWORD1
rol c64.SCRATCH_ZPWORD1+1
asl c64.SCRATCH_ZPWORD1
rol c64.SCRATCH_ZPWORD1+1
lda c64.SCRATCH_ZPWORD1
sta c64.SCRATCH_ZPWORD2
lda c64.SCRATCH_ZPWORD1+1
sta c64.SCRATCH_ZPWORD2+1
asl c64.SCRATCH_ZPWORD2
rol c64.SCRATCH_ZPWORD2+1
asl c64.SCRATCH_ZPWORD2
rol c64.SCRATCH_ZPWORD2+1
sta c64.SCRATCH_ZPREG
lda c64.ESTACK_LO+1,x
asl a
rol c64.SCRATCH_ZPREG
asl a
rol c64.SCRATCH_ZPREG
clc
lda c64.SCRATCH_ZPWORD1
adc c64.SCRATCH_ZPWORD2
adc c64.ESTACK_LO+1,x
sta c64.ESTACK_LO+1,x
lda c64.SCRATCH_ZPWORD1+1
adc c64.SCRATCH_ZPWORD2+1
lda c64.SCRATCH_ZPREG
adc c64.ESTACK_HI+1,x
asl c64.ESTACK_LO+1,x
rol a
asl c64.ESTACK_LO+1,x
rol a
sta c64.ESTACK_HI+1,x
rts
.pend
mul_byte_25 .proc
; X + X*8 + X*16
; (X*2 + X)*8 + X
lda c64.ESTACK_LO+1,x
asl a
asl a
asl a
sta c64.SCRATCH_ZPREG
asl a
clc
adc c64.SCRATCH_ZPREG
adc c64.ESTACK_LO+1,x
asl a
asl a
asl a
clc
adc c64.ESTACK_LO+1,x
sta c64.ESTACK_LO+1,x
@ -629,76 +578,68 @@ mul_byte_25 .proc
mul_word_25 .proc
; W + W*8 + W*16
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPWORD1+1
asl c64.SCRATCH_ZPWORD1
rol c64.SCRATCH_ZPWORD1+1
asl c64.SCRATCH_ZPWORD1
rol c64.SCRATCH_ZPWORD1+1
asl c64.SCRATCH_ZPWORD1
rol c64.SCRATCH_ZPWORD1+1
clc
lda c64.ESTACK_LO+1,x
adc c64.SCRATCH_ZPWORD1
asl a
rol c64.SCRATCH_ZPWORD1+1
asl a
rol c64.SCRATCH_ZPWORD1+1
asl a
rol c64.SCRATCH_ZPWORD1+1
sta c64.SCRATCH_ZPWORD1
clc
adc c64.ESTACK_LO+1,x
sta c64.ESTACK_LO+1,x
lda c64.ESTACK_HI+1,x
adc c64.SCRATCH_ZPWORD1+1
lda c64.SCRATCH_ZPWORD1+1
adc c64.ESTACK_HI+1,x
sta c64.ESTACK_HI+1,x
asl c64.SCRATCH_ZPWORD1
lda c64.SCRATCH_ZPWORD1
asl a
rol c64.SCRATCH_ZPWORD1+1
clc
lda c64.ESTACK_LO+1,x
adc c64.SCRATCH_ZPWORD1
adc c64.ESTACK_LO+1,x
sta c64.ESTACK_LO+1,x
lda c64.ESTACK_HI+1,x
adc c64.SCRATCH_ZPWORD1+1
lda c64.SCRATCH_ZPWORD1+1
adc c64.ESTACK_HI+1,x
sta c64.ESTACK_HI+1,x
rts
.pend
mul_byte_40 .proc
; X*8 + X*32
; (X*4 + X)*8
lda c64.ESTACK_LO+1,x
asl a
asl a
asl a
sta c64.SCRATCH_ZPREG
clc
adc c64.ESTACK_LO+1,x
asl a
asl a
asl a
clc
adc c64.SCRATCH_ZPREG
sta c64.ESTACK_LO+1,x
rts
.pend
mul_word_40 .proc
; W*8 + W*32
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPWORD1
; (W*4 + W)*8
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPWORD1+1
asl c64.SCRATCH_ZPWORD1
rol c64.SCRATCH_ZPWORD1+1
asl c64.SCRATCH_ZPWORD1
rol c64.SCRATCH_ZPWORD1+1
asl c64.SCRATCH_ZPWORD1
rol c64.SCRATCH_ZPWORD1+1
lda c64.SCRATCH_ZPWORD1
sta c64.ESTACK_LO+1,x
lda c64.SCRATCH_ZPWORD1+1
sta c64.ESTACK_HI+1,x
asl c64.SCRATCH_ZPWORD1
rol c64.SCRATCH_ZPWORD1+1
asl c64.SCRATCH_ZPWORD1
rol c64.SCRATCH_ZPWORD1+1
sta c64.SCRATCH_ZPREG
lda c64.ESTACK_LO+1,x
asl a
rol c64.SCRATCH_ZPREG
asl a
rol c64.SCRATCH_ZPREG
clc
lda c64.ESTACK_LO+1,x
adc c64.SCRATCH_ZPWORD1
adc c64.ESTACK_LO+1,x
sta c64.ESTACK_LO+1,x
lda c64.ESTACK_HI+1,x
adc c64.SCRATCH_ZPWORD1+1
lda c64.SCRATCH_ZPREG
adc c64.ESTACK_HI+1,x
asl c64.ESTACK_LO+1,x
rol a
asl c64.ESTACK_LO+1,x
rol a
asl c64.ESTACK_LO+1,x
rol a
sta c64.ESTACK_HI+1,x
rts
.pend

View File

@ -49,19 +49,17 @@ add_a_to_zpword .proc
pop_index_times_5 .proc
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPB1
asl a
asl a
clc
adc c64.SCRATCH_ZPB1 ; A*=5
adc c64.ESTACK_LO,x
rts
.pend
neg_b .proc
lda c64.ESTACK_LO+1,x
eor #255
clc
adc #1
lda #0
sec
sbc c64.ESTACK_LO+1,x
sta c64.ESTACK_LO+1,x
rts
.pend
@ -90,10 +88,9 @@ inv_word .proc
not_byte .proc
lda c64.ESTACK_LO+1,x
beq +
lda #0
beq ++
+ lda #1
+ sta c64.ESTACK_LO+1,x
lda #1
+ eor #1
sta c64.ESTACK_LO+1,x
rts
.pend
@ -101,14 +98,169 @@ not_word .proc
lda c64.ESTACK_LO + 1,x
ora c64.ESTACK_HI + 1,x
beq +
lda #0
beq ++
+ lda #1
+ sta c64.ESTACK_LO + 1,x
lda #1
+ eor #1
sta c64.ESTACK_LO + 1,x
lsr a
sta c64.ESTACK_HI + 1,x
rts
.pend
bitand_b .proc
; -- bitwise and (of 2 bytes)
lda c64.ESTACK_LO+2,x
and c64.ESTACK_LO+1,x
inx
sta c64.ESTACK_LO+1,x
rts
.pend
bitor_b .proc
; -- bitwise or (of 2 bytes)
lda c64.ESTACK_LO+2,x
ora c64.ESTACK_LO+1,x
inx
sta c64.ESTACK_LO+1,x
rts
.pend
bitxor_b .proc
; -- bitwise xor (of 2 bytes)
lda c64.ESTACK_LO+2,x
eor c64.ESTACK_LO+1,x
inx
sta c64.ESTACK_LO+1,x
rts
.pend
bitand_w .proc
; -- bitwise and (of 2 words)
lda c64.ESTACK_LO+2,x
and c64.ESTACK_LO+1,x
sta c64.ESTACK_LO+2,x
lda c64.ESTACK_HI+2,x
and c64.ESTACK_HI+1,x
sta c64.ESTACK_HI+2,x
inx
rts
.pend
bitor_w .proc
; -- bitwise or (of 2 words)
lda c64.ESTACK_LO+2,x
ora c64.ESTACK_LO+1,x
sta c64.ESTACK_LO+2,x
lda c64.ESTACK_HI+2,x
ora c64.ESTACK_HI+1,x
sta c64.ESTACK_HI+2,x
inx
rts
.pend
bitxor_w .proc
; -- bitwise xor (of 2 bytes)
lda c64.ESTACK_LO+2,x
eor c64.ESTACK_LO+1,x
sta c64.ESTACK_LO+2,x
lda c64.ESTACK_HI+2,x
eor c64.ESTACK_HI+1,x
sta c64.ESTACK_HI+2,x
inx
rts
.pend
and_b .proc
; -- logical and (of 2 bytes)
lda c64.ESTACK_LO+2,x
beq +
lda #1
+ sta c64.SCRATCH_ZPB1
lda c64.ESTACK_LO+1,x
beq +
lda #1
+ and c64.SCRATCH_ZPB1
inx
sta c64.ESTACK_LO+1,x
rts
.pend
or_b .proc
; -- logical or (of 2 bytes)
lda c64.ESTACK_LO+2,x
ora c64.ESTACK_LO+1,x
beq +
lda #1
+ inx
sta c64.ESTACK_LO+1,x
rts
.pend
xor_b .proc
; -- logical xor (of 2 bytes)
lda c64.ESTACK_LO+2,x
beq +
lda #1
+ sta c64.SCRATCH_ZPB1
lda c64.ESTACK_LO+1,x
beq +
lda #1
+ eor c64.SCRATCH_ZPB1
inx
sta c64.ESTACK_LO+1,x
rts
.pend
and_w .proc
; -- logical and (word and word -> byte)
lda c64.ESTACK_LO+2,x
ora c64.ESTACK_HI+2,x
beq +
lda #1
+ sta c64.SCRATCH_ZPB1
lda c64.ESTACK_LO+1,x
ora c64.ESTACK_HI+1,x
beq +
lda #1
+ and c64.SCRATCH_ZPB1
inx
sta c64.ESTACK_LO+1,x
sta c64.ESTACK_HI+1,x
rts
.pend
or_w .proc
; -- logical or (word or word -> byte)
lda c64.ESTACK_LO+2,x
ora c64.ESTACK_LO+1,x
ora c64.ESTACK_HI+2,x
ora c64.ESTACK_HI+1,x
beq +
lda #1
+ inx
sta c64.ESTACK_LO+1,x
sta c64.ESTACK_HI+1,x
rts
.pend
xor_w .proc
; -- logical xor (word xor word -> byte)
lda c64.ESTACK_LO+2,x
ora c64.ESTACK_HI+2,x
beq +
lda #1
+ sta c64.SCRATCH_ZPB1
lda c64.ESTACK_LO+1,x
ora c64.ESTACK_HI+1,x
beq +
lda #1
+ eor c64.SCRATCH_ZPB1
inx
sta c64.ESTACK_LO+1,x
sta c64.ESTACK_HI+1,x
rts
.pend
abs_b .proc
; -- push abs(byte) on stack (as byte)
lda c64.ESTACK_LO+1,x
@ -170,9 +322,9 @@ mul_word .proc
stx c64.SCRATCH_ZPREGX
jsr math.multiply_words
ldx c64.SCRATCH_ZPREGX
lda math.multiply_words.multiply_words_result
lda math.multiply_words.result
sta c64.ESTACK_LO+1,x
lda math.multiply_words.multiply_words_result+1
lda math.multiply_words.result+1
sta c64.ESTACK_HI+1,x
rts
.pend
@ -300,25 +452,18 @@ equal_w .proc
notequal_b .proc
; -- are the two bytes on the stack different?
inx
lda c64.ESTACK_LO,x
eor c64.ESTACK_LO+1,x
sta c64.ESTACK_LO+1,x
rts
lda c64.ESTACK_LO+1,x
cmp c64.ESTACK_LO+2,x
beq equal_b._equal_b_false
bne equal_b._equal_b_true
.pend
notequal_w .proc
; -- are the two words on the stack different?
inx
lda c64.ESTACK_LO,x
eor c64.ESTACK_LO+1,x
beq +
sta c64.ESTACK_LO+1,x
rts
+ lda c64.ESTACK_HI,x
eor c64.ESTACK_HI+1,x
sta c64.ESTACK_LO+1,x
rts
lda c64.ESTACK_HI+1,x
cmp c64.ESTACK_HI+2,x
beq notequal_b
bne equal_b._equal_b_true
.pend
less_ub .proc
@ -375,11 +520,10 @@ _equal_b_false lda #0
.pend
lesseq_ub .proc
lda c64.ESTACK_LO+2,x
cmp c64.ESTACK_LO+1,x
bcc equal_b._equal_b_true
beq equal_b._equal_b_true
bcs equal_b._equal_b_false
lda c64.ESTACK_LO+1,x
cmp c64.ESTACK_LO+2,x
bcs equal_b._equal_b_true
bcc equal_b._equal_b_false
.pend
lesseq_b .proc
@ -496,6 +640,65 @@ greatereq_w .proc
bmi equal_b._equal_b_false
.pend
func_read_flags .proc
; -- put the processor status register on the stack
php
pla
sta c64.ESTACK_LO,x
dex
rts
.pend
func_sqrt16 .proc
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPWORD2
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPWORD2+1
stx c64.SCRATCH_ZPREGX
ldy #$00 ; r = 0
ldx #$07
clc ; clear bit 16 of m
_loop
tya
ora _stab-1,x
sta c64.SCRATCH_ZPB1 ; (r asl 8) | (d asl 7)
lda c64.SCRATCH_ZPWORD2+1
bcs _skip0 ; m >= 65536? then t <= m is always true
cmp c64.SCRATCH_ZPB1
bcc _skip1 ; t <= m
_skip0
sbc c64.SCRATCH_ZPB1
sta c64.SCRATCH_ZPWORD2+1 ; m = m - t
tya
ora _stab,x
tay ; r = r or d
_skip1
asl c64.SCRATCH_ZPWORD2
rol c64.SCRATCH_ZPWORD2+1 ; m = m asl 1
dex
bne _loop
; last iteration
bcs _skip2
sty c64.SCRATCH_ZPB1
lda c64.SCRATCH_ZPWORD2
cmp #$80
lda c64.SCRATCH_ZPWORD2+1
sbc c64.SCRATCH_ZPB1
bcc _skip3
_skip2
iny ; r = r or d (d is 1 here)
_skip3
ldx c64.SCRATCH_ZPREGX
tya
sta c64.ESTACK_LO+1,x
lda #0
sta c64.ESTACK_HI+1,x
rts
_stab .byte $01,$02,$04,$08,$10,$20,$40,$80
.pend
func_sin8 .proc
ldy c64.ESTACK_LO+1,x
@ -790,17 +993,15 @@ _loop lda (c64.SCRATCH_ZPWORD1),y
func_sum_ub .proc
jsr pop_array_and_lengthmin1Y
lda #0
sta c64.ESTACK_LO,x
sta c64.ESTACK_HI,x
- lda (c64.SCRATCH_ZPWORD1),y
clc
adc c64.ESTACK_LO,x
sta c64.ESTACK_LO,x
- clc
adc (c64.SCRATCH_ZPWORD1),y
bcc +
inc c64.ESTACK_HI,x
+ dey
cpy #255
bne -
sta c64.ESTACK_LO,x
dex
rts
.pend
@ -961,8 +1162,7 @@ _gtequ dey
_result_minw .word 0
.pend
func_len_str .proc
func_strlen .proc
; -- push length of 0-terminated string on stack
jsr peek_address
ldy #0
@ -975,15 +1175,6 @@ func_len_str .proc
rts
.pend
func_len_strp .proc
; -- push length of pascal-string on stack
jsr peek_address
ldy #0
lda (c64.SCRATCH_ZPWORD1),y ; first byte is length
sta c64.ESTACK_LO+1,x
rts
.pend
func_rnd .proc
; -- put a random ubyte on the estack
jsr math.randbyte
@ -1003,7 +1194,7 @@ func_rndw .proc
.pend
func_memcopy .proc
func_memcopy .proc
; note: clobbers A,Y
inx
stx c64.SCRATCH_ZPREGX
@ -1029,7 +1220,7 @@ func_memcopy .proc
rts
.pend
func_memset .proc
func_memset .proc
; note: clobbers A,Y
inx
stx c64.SCRATCH_ZPREGX
@ -1049,7 +1240,7 @@ func_memset .proc
rts
.pend
func_memsetw .proc
func_memsetw .proc
; note: clobbers A,Y
; -- fill memory from (SCRATCH_ZPWORD1) number of words in SCRATCH_ZPWORD2, with word value in AY.

View File

@ -1 +1 @@
1.1 (beta)
1.7

View File

@ -46,6 +46,7 @@ private fun compileMain(args: Array<String>) {
var moduleFile = ""
var writeVmCode = false
var writeAssembly = true
var optimize = true
for (arg in args) {
if(arg=="-emu")
emulatorToStart = "x64"
@ -55,6 +56,8 @@ private fun compileMain(args: Array<String>) {
writeVmCode = true
else if(arg=="-noasm")
writeAssembly = false
else if(arg=="-noopt")
optimize = false
else if(!arg.startsWith("-"))
moduleFile = arg
else
@ -85,7 +88,7 @@ private fun compileMain(args: Array<String>) {
println("Syntax check...")
val heap = HeapValues()
val time1= measureTimeMillis {
moduleAst.checkIdentifiers(heap)
moduleAst.checkIdentifiers(namespace)
}
//println(" time1: $time1")
val time2 = measureTimeMillis {
@ -101,15 +104,17 @@ private fun compileMain(args: Array<String>) {
}
//println(" time4: $time4")
// optimize the parse tree
println("Optimizing...")
val allScopedSymbolDefinitions = moduleAst.checkIdentifiers(heap) // useful for checking symbol usage later?
while (true) {
// keep optimizing expressions and statements until no more steps remain
val optsDone1 = moduleAst.simplifyExpressions(namespace, heap)
val optsDone2 = moduleAst.optimizeStatements(namespace, heap)
if (optsDone1 + optsDone2 == 0)
break
moduleAst.checkIdentifiers(namespace)
if(optimize) {
// optimize the parse tree
println("Optimizing...")
while (true) {
// keep optimizing expressions and statements until no more steps remain
val optsDone1 = moduleAst.simplifyExpressions(namespace, heap)
val optsDone2 = moduleAst.optimizeStatements(namespace, heap)
if (optsDone1 + optsDone2 == 0)
break
}
}
namespace = moduleAst.definingScope() // create it again, it could have changed in the meantime
@ -121,7 +126,8 @@ private fun compileMain(args: Array<String>) {
// compile the syntax tree into stackvmProg form, and optimize that
val compiler = Compiler(moduleAst, namespace, heap)
val intermediate = compiler.compile(compilerOptions)
intermediate.optimize()
if(optimize)
intermediate.optimize()
if(writeVmCode) {
val stackVmFilename = intermediate.name + ".vm.txt"
@ -134,7 +140,7 @@ private fun compileMain(args: Array<String>) {
if(writeAssembly) {
val zeropage = C64Zeropage(compilerOptions)
intermediate.allocateZeropage(zeropage)
val assembly = AsmGen(compilerOptions, intermediate, heap, zeropage).compileToAssembly()
val assembly = AsmGen(compilerOptions, intermediate, heap, zeropage).compileToAssembly(optimize)
assembly.assemble(compilerOptions)
programname = assembly.name
}
@ -146,6 +152,11 @@ private fun compileMain(args: Array<String>) {
System.err.println(px.message)
System.err.print("\u001b[0m") // reset
exitProcess(1)
} catch (ax: AstException) {
System.err.print("\u001b[91m") // bright red
System.err.println(ax.toString())
System.err.print("\u001b[0m") // reset
exitProcess(1)
} catch (x: Exception) {
print("\u001b[91m") // bright red
println("\n* internal error *")
@ -182,7 +193,7 @@ fun determineCompilationOptions(moduleAst: Module): CompilationOptions {
val floatsEnabled = options.any { it.name == "enable_floats" }
val zpType: ZeropageType =
if (zpoption == null)
if(floatsEnabled) ZeropageType.BASICSAFE else ZeropageType.KERNALSAFE
if(floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE
else
try {
ZeropageType.valueOf(zpoption)
@ -211,6 +222,7 @@ private fun usage() {
System.err.println(" [-writevm] write intermediate vm code to a file as well")
System.err.println(" [-noasm] don't create assembly code")
System.err.println(" [-vm] launch the prog8 virtual machine instead of the compiler")
System.err.println(" [-noopt] don't perform optimizations")
System.err.println(" modulefile main module file to compile")
exitProcess(1)
}

View File

@ -9,7 +9,9 @@ import prog8.compiler.target.c64.Petscii
import prog8.functions.BuiltinFunctions
import prog8.functions.NotConstArgumentException
import prog8.functions.builtinFunctionReturnType
import prog8.parser.CustomLexer
import prog8.parser.prog8Parser
import java.io.CharConversionException
import java.io.File
import java.nio.file.Path
import kotlin.math.abs
@ -25,9 +27,7 @@ enum class DataType {
WORD,
FLOAT,
STR,
STR_P,
STR_S,
STR_PS,
ARRAY_UB,
ARRAY_B,
ARRAY_UW,
@ -37,20 +37,15 @@ enum class DataType {
fun assignableTo(targetType: DataType) =
// what types are assignable to others without loss of precision?
when(this) {
UBYTE -> targetType == BYTE || targetType == UBYTE || targetType == UWORD || targetType==WORD || targetType == FLOAT
BYTE -> targetType == BYTE || targetType == UBYTE || targetType == WORD || targetType == FLOAT
UBYTE -> targetType == UBYTE || targetType == UWORD || targetType==WORD || targetType == FLOAT
BYTE -> targetType == BYTE || targetType == UBYTE || targetType == UWORD || targetType==WORD || targetType == FLOAT
UWORD -> targetType == UWORD || targetType == FLOAT
WORD -> targetType == WORD || targetType == FLOAT
WORD -> targetType == WORD || targetType==UWORD || targetType == FLOAT
FLOAT -> targetType == FLOAT
STR -> targetType == STR || targetType==STR_S || targetType == UWORD
STR_P -> targetType == STR_P || targetType==STR_PS || targetType == UWORD
STR_S -> targetType == STR || targetType==STR_S || targetType == UWORD
STR_PS -> targetType == STR_P || targetType==STR_PS || targetType == UWORD
ARRAY_UB -> targetType == UWORD
ARRAY_B -> targetType == UWORD
ARRAY_UW -> targetType == UWORD
ARRAY_W -> targetType == UWORD
ARRAY_F -> targetType == UWORD
STR -> targetType == STR || targetType==STR_S
STR_S -> targetType == STR || targetType==STR_S
in ArrayDatatypes -> targetType === this
else -> false
}
@ -97,14 +92,15 @@ enum class BranchCondition {
val IterableDatatypes = setOf(
DataType.STR, DataType.STR_S,
DataType.STR_P, DataType.STR_PS, // note: these are a bit weird they store their length as the first byte
DataType.ARRAY_UB, DataType.ARRAY_B,
DataType.ARRAY_UW, DataType.ARRAY_W,
DataType.ARRAY_F)
val StringDatatypes = setOf(DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS)
val NumericDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT)
val ByteDatatypes = setOf(DataType.UBYTE, DataType.BYTE)
val WordDatatypes = setOf(DataType.UWORD, DataType.WORD)
val IntegerDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD)
val NumericDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT)
val StringDatatypes = setOf(DataType.STR, DataType.STR_S)
val ArrayDatatypes = setOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F)
@ -160,7 +156,7 @@ interface IAstProcessor {
fun process(decl: VarDecl): IStatement {
decl.value = decl.value?.process(this)
decl.arrayspec?.process(this)
decl.arraysize?.process(this)
return decl
}
@ -174,9 +170,9 @@ interface IAstProcessor {
return functionCall
}
fun process(functionCall: FunctionCallStatement): IStatement {
functionCall.arglist = functionCall.arglist.map { it.process(this) }.toMutableList()
return functionCall
fun process(functionCallStatement: FunctionCallStatement): IStatement {
functionCallStatement.arglist = functionCallStatement.arglist.map { it.process(this) }.toMutableList()
return functionCallStatement
}
fun process(identifier: IdentifierReference): IExpression {
@ -214,6 +210,7 @@ interface IAstProcessor {
}
fun process(literalValue: LiteralValue): LiteralValue {
literalValue.arrayvalue?.forEach { it.process(this) }
return literalValue
}
@ -292,6 +289,11 @@ interface IAstProcessor {
memwrite.addressExpression = memwrite.addressExpression.process(this)
return memwrite
}
fun process(addressOf: AddressOf): IExpression {
process(addressOf.identifier)
return addressOf
}
}
@ -327,8 +329,11 @@ inline fun <reified T> findParentNode(node: Node): T? {
interface IStatement : Node {
fun process(processor: IAstProcessor) : IStatement
fun makeScopedName(name: String): List<String> {
// this is usually cached in a lazy property on the statement object itself
fun makeScopedName(name: String): String {
// easy way out is to always return the full scoped name.
// it would be nicer to find only the minimal prefixed scoped name, but that's too much hassle for now.
// and like this, we can cache the name even,
// like in a lazy property on the statement object itself (label, subroutine, vardecl)
val scope = mutableListOf<String>()
var statementScope = this.parent
while(statementScope !is ParentSentinel && statementScope !is Module) {
@ -337,8 +342,9 @@ interface IStatement : Node {
}
statementScope = statementScope.parent
}
scope.add(name)
return scope
if(name.isNotEmpty())
scope.add(name)
return scope.joinToString(".")
}
}
@ -506,10 +512,9 @@ private class GlobalNamespace(override val name: String,
class Block(override val name: String,
val address: Int?,
override var statements: MutableList<IStatement>,
val isInLibrary: Boolean,
override val position: Position) : IStatement, INameScope {
override lateinit var parent: Node
val scopedname: String by lazy { makeScopedName(name).joinToString(".") }
override fun linkParents(parent: Node) {
this.parent = parent
@ -549,7 +554,6 @@ data class DirectiveArg(val str: String?, val name: String?, val int: Int?, over
data class Label(val name: String, override val position: Position) : IStatement {
override lateinit var parent: Node
val scopedname: String by lazy { makeScopedName(name).joinToString(".") }
override fun linkParents(parent: Node) {
this.parent = parent
@ -560,6 +564,8 @@ data class Label(val name: String, override val position: Position) : IStatement
override fun toString(): String {
return "Label(name=$name, pos=$position)"
}
val scopedname: String by lazy { makeScopedName(name) }
}
@ -622,30 +628,30 @@ class Break(override val position: Position) : IStatement {
}
class ArraySpec(var x: IExpression, override val position: Position) : Node {
class ArrayIndex(var index: IExpression, override val position: Position) : Node {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
x.linkParents(this)
index.linkParents(this)
}
companion object {
fun forArray(v: LiteralValue, heap: HeapValues): ArraySpec {
fun forArray(v: LiteralValue, heap: HeapValues): ArrayIndex {
val arraySize = v.arrayvalue?.size ?: heap.get(v.heapId!!).arraysize
return ArraySpec(LiteralValue.optimalNumeric(arraySize, v.position), v.position)
return ArrayIndex(LiteralValue.optimalNumeric(arraySize, v.position), v.position)
}
}
fun process(processor: IAstProcessor) {
x = x.process(processor)
index = index.process(processor)
}
override fun toString(): String {
return("ArraySpec($x, pos=$position)")
return("ArrayIndex($index, pos=$position)")
}
fun size() = (x as? LiteralValue)?.asIntegerValue
fun size() = (index as? LiteralValue)?.asIntegerValue
}
@ -658,7 +664,8 @@ enum class VarDeclType {
class VarDecl(val type: VarDeclType,
private val declaredDatatype: DataType,
val zeropage: Boolean,
val arrayspec: ArraySpec?,
var arraysize: ArrayIndex?,
val isUnsizedArray: Boolean,
val name: String,
var value: IExpression?,
override val position: Position) : IStatement {
@ -666,7 +673,7 @@ class VarDecl(val type: VarDeclType,
val datatypeErrors = mutableListOf<SyntaxError>() // don't crash at init time, report them in the AstChecker
val datatype =
if (arrayspec == null) declaredDatatype
if (arraysize == null && !isUnsizedArray) declaredDatatype
else when (declaredDatatype) {
DataType.UBYTE -> DataType.ARRAY_UB
DataType.BYTE -> DataType.ARRAY_B
@ -681,13 +688,13 @@ class VarDecl(val type: VarDeclType,
override fun linkParents(parent: Node) {
this.parent = parent
arrayspec?.linkParents(this)
arraysize?.linkParents(this)
value?.linkParents(this)
}
override fun process(processor: IAstProcessor) = processor.process(this)
val scopedname: String by lazy { makeScopedName(name).joinToString(".") }
val scopedname: String by lazy { makeScopedName(name) }
override fun toString(): String {
return "VarDecl(name=$name, vartype=$type, datatype=$datatype, value=$value, pos=$position)"
@ -702,7 +709,7 @@ class VarDecl(val type: VarDeclType,
DataType.FLOAT -> LiteralValue(DataType.FLOAT, floatvalue=0.0, position=position)
else -> throw FatalAstException("can only set a default value for a numeric type")
}
val decl = VarDecl(type, declaredDatatype, zeropage, arrayspec, name, constValue, position)
val decl = VarDecl(type, declaredDatatype, zeropage, arraysize, isUnsizedArray, name, constValue, position)
if(parent!=null)
decl.linkParents(parent)
return decl
@ -789,7 +796,7 @@ data class AssignTarget(val register: Register?,
fun shortString(withTypePrefix: Boolean=false): String {
if(register!=null)
return (if(withTypePrefix) "0register::" else "") + register.toString()
return (if(withTypePrefix) "0register::" else "") + register.name
if(identifier!=null)
return (if(withTypePrefix) "3identifier::" else "") + identifier.nameInSource.last()
if(arrayindexed!=null)
@ -799,6 +806,9 @@ data class AssignTarget(val register: Register?,
return (if(withTypePrefix) "1address::" else "") +address.asIntegerValue.toString()
return if(withTypePrefix) "???::???" else "???"
}
fun isMemoryMapped(namespace: INameScope): Boolean =
memoryAddress!=null || (identifier?.targetVarDecl(namespace)?.type==VarDeclType.MEMORY)
}
@ -917,8 +927,8 @@ class BinaryExpression(var left: IExpression, var operator: String, var right: I
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
DataType.BYTE -> when(rightDt) {
DataType.BYTE, DataType.UBYTE -> DataType.BYTE
DataType.WORD, DataType.UWORD -> DataType.WORD
in ByteDatatypes -> DataType.BYTE
in WordDatatypes -> DataType.WORD
DataType.FLOAT -> DataType.FLOAT
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
@ -929,7 +939,7 @@ class BinaryExpression(var left: IExpression, var operator: String, var right: I
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
DataType.WORD -> when(rightDt) {
DataType.BYTE, DataType.UBYTE, DataType.WORD, DataType.UWORD -> DataType.WORD
in IntegerDatatypes -> DataType.WORD
DataType.FLOAT -> DataType.FLOAT
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
@ -945,7 +955,7 @@ class BinaryExpression(var left: IExpression, var operator: String, var right: I
}
class ArrayIndexedExpression(val identifier: IdentifierReference,
var arrayspec: ArraySpec,
var arrayspec: ArrayIndex,
override val position: Position) : IExpression {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
@ -977,7 +987,7 @@ class ArrayIndexedExpression(val identifier: IdentifierReference,
}
override fun toString(): String {
return "ArrayIndexed(ident=$identifier, arrayspec=$arrayspec; pos=$position)"
return "ArrayIndexed(ident=$identifier, arraysize=$arrayspec; pos=$position)"
}
}
@ -1006,6 +1016,23 @@ class TypecastExpression(var expression: IExpression, var type: DataType, overri
}
data class AddressOf(val identifier: IdentifierReference, override val position: Position) : IExpression {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
identifier.parent=this
}
var scopedname: String? = null // will be set in a later state by the compiler
override fun isIterable(namespace: INameScope, heap: HeapValues) = false
override fun constValue(namespace: INameScope, heap: HeapValues): LiteralValue? = null
override fun referencesIdentifier(name: String) = false
override fun resultingDatatype(namespace: INameScope, heap: HeapValues) = DataType.UWORD
override fun process(processor: IAstProcessor) = processor.process(this)
}
class DirectMemoryRead(var addressExpression: IExpression, override val position: Position) : IExpression {
override lateinit var parent: Node
@ -1072,8 +1099,8 @@ class LiteralValue(val type: DataType,
fun fromNumber(value: Number, type: DataType, position: Position) : LiteralValue {
return when(type) {
DataType.UBYTE, DataType.BYTE -> LiteralValue(type, bytevalue = value.toShort(), position = position)
DataType.UWORD, DataType.WORD -> LiteralValue(type, wordvalue = value.toInt(), position = position)
in ByteDatatypes -> LiteralValue(type, bytevalue = value.toShort(), position = position)
in WordDatatypes -> LiteralValue(type, wordvalue = value.toInt(), position = position)
DataType.FLOAT -> LiteralValue(type, floatvalue = value.toDouble(), position = position)
else -> throw FatalAstException("non numeric datatype")
}
@ -1109,8 +1136,8 @@ class LiteralValue(val type: DataType,
init {
when(type){
DataType.UBYTE, DataType.BYTE -> if(bytevalue==null) throw FatalAstException("literal value missing bytevalue")
DataType.UWORD, DataType.WORD -> if(wordvalue==null) throw FatalAstException("literal value missing wordvalue")
in ByteDatatypes -> if(bytevalue==null) throw FatalAstException("literal value missing bytevalue")
in WordDatatypes -> if(wordvalue==null) throw FatalAstException("literal value missing wordvalue")
DataType.FLOAT -> if(floatvalue==null) throw FatalAstException("literal value missing floatvalue")
in StringDatatypes ->
if(initialstrvalue==null && heapId==null) throw FatalAstException("literal value missing strvalue/heapId")
@ -1158,14 +1185,15 @@ class LiteralValue(val type: DataType,
DataType.UWORD -> "uword:$wordvalue"
DataType.WORD -> "word:$wordvalue"
DataType.FLOAT -> "float:$floatvalue"
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS-> {
in StringDatatypes -> {
if(heapId!=null) "str:#$heapId"
else "str:$initialstrvalue"
}
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F -> {
if(heapId!=null) "arrayspec:#$heapId"
else "arrayspec:$arrayvalue"
in ArrayDatatypes -> {
if(heapId!=null) "array:#$heapId"
else "array:$arrayvalue"
}
else -> throw FatalAstException("weird datatype")
}
return "LiteralValue($vstr)"
}
@ -1271,7 +1299,7 @@ class LiteralValue(val type: DataType,
return LiteralValue(targettype, wordvalue = value, position = position)
}
}
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
in StringDatatypes -> {
if(targettype in StringDatatypes)
return this
}
@ -1313,9 +1341,7 @@ class RangeExpr(var from: IExpression,
fromDt==DataType.UBYTE && toDt==DataType.UBYTE -> DataType.UBYTE
fromDt==DataType.UWORD && toDt==DataType.UWORD -> DataType.UWORD
fromDt==DataType.STR && toDt==DataType.STR -> DataType.STR
fromDt==DataType.STR_P && toDt==DataType.STR_P -> DataType.STR_P
fromDt==DataType.STR_S && toDt==DataType.STR_S -> DataType.STR_S
fromDt==DataType.STR_PS && toDt==DataType.STR_PS -> DataType.STR_PS
fromDt==DataType.WORD || toDt==DataType.WORD -> DataType.WORD
fromDt==DataType.BYTE || toDt==DataType.BYTE -> DataType.BYTE
else -> DataType.UBYTE
@ -1394,6 +1420,9 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
else
namespace.lookup(nameInSource, this)
fun targetVarDecl(namespace: INameScope): VarDecl? = targetStatement(namespace) as? VarDecl
fun targetSubroutine(namespace: INameScope): Subroutine? = targetStatement(namespace) as? Subroutine
override fun linkParents(parent: Node) {
this.parent = parent
}
@ -1587,7 +1616,7 @@ class AnonymousScope(override var statements: MutableList<IStatement>,
override lateinit var parent: Node
init {
name = "<<<anonymous-$sequenceNumber>>>"
name = "<anon-$sequenceNumber>" // make sure it's an invalid soruce code identifier so user source code can never produce it
sequenceNumber++
}
@ -1628,7 +1657,7 @@ class Subroutine(override val name: String,
override var statements: MutableList<IStatement>,
override val position: Position) : IStatement, INameScope {
override lateinit var parent: Node
val scopedname: String by lazy { makeScopedName(name).joinToString(".") }
val scopedname: String by lazy { makeScopedName(name) }
override fun linkParents(parent: Node) {
this.parent = parent
@ -1647,6 +1676,40 @@ class Subroutine(override val name: String,
.filter { it is InlineAssembly }
.map { (it as InlineAssembly).assembly }
.count { " rti" in it || "\trti" in it || " rts" in it || "\trts" in it || " jmp" in it || "\tjmp" in it }
val canBeAsmSubroutine =false // TODO disabled for now, see below about problem with converting to asm subroutine
// !isAsmSubroutine
// && ((parameters.size == 1 && parameters[0].type in setOf(DataType.BYTE, DataType.UBYTE, DataType.WORD, DataType.UWORD))
// || (parameters.size == 2 && parameters.map { it.type }.all { it == DataType.BYTE || it == DataType.UBYTE }))
fun intoAsmSubroutine(): Subroutine {
// TODO turn subroutine into asm calling convention. Requires rethinking of how parameters are handled (conflicts with local vardefs now, see AstIdentifierChecker...)
return this // TODO
// println("TO ASM $this") // TODO
// val paramregs = if (parameters.size == 1 && parameters[0].type in setOf(DataType.BYTE, DataType.UBYTE))
// listOf(RegisterOrStatusflag(RegisterOrPair.Y, null, null))
// else if (parameters.size == 1 && parameters[0].type in setOf(DataType.WORD, DataType.UWORD))
// listOf(RegisterOrStatusflag(RegisterOrPair.AY, null, null))
// else if (parameters.size == 2 && parameters.map { it.type }.all { it == DataType.BYTE || it == DataType.UBYTE })
// listOf(RegisterOrStatusflag(RegisterOrPair.A, null, null), RegisterOrStatusflag(RegisterOrPair.Y, null, null))
// else throw FatalAstException("cannot convert subroutine to asm parameters")
//
// val asmsub=Subroutine(
// name,
// parameters,
// returntypes,
// paramregs,
// emptyList(),
// emptySet(),
// null,
// true,
// statements,
// position
// )
// asmsub.linkParents(parent)
// return asmsub
}
}
@ -1753,34 +1816,38 @@ class RepeatLoop(var body: AnonymousScope,
/***************** Antlr Extension methods to create AST ****************/
fun prog8Parser.ModuleContext.toAst(name: String, isLibrary: Boolean, importedFrom: Path) : Module =
Module(name, modulestatement().asSequence().map { it.toAst() }.toMutableList(), toPosition(), isLibrary, importedFrom)
private fun ParserRuleContext.toPosition() : Position {
val file =
if(start.inputStream.sourceName == IntStream.UNKNOWN_SOURCE_NAME)
"<internal>"
else
File(start.inputStream.sourceName).name
// note: be ware of TAB characters in the source text, they count as 1 column...
return Position(file, start.line, start.charPositionInLine, stop.charPositionInLine+stop.text.length)
fun prog8Parser.ModuleContext.toAst(name: String, isLibrary: Boolean, importedFrom: Path) : Module {
val nameWithoutSuffix = if(name.endsWith(".p8")) name.substringBeforeLast('.') else name
return Module(nameWithoutSuffix, modulestatement().asSequence().map { it.toAst(isLibrary) }.toMutableList(), toPosition(), isLibrary, importedFrom)
}
private fun prog8Parser.ModulestatementContext.toAst() : IStatement {
private fun ParserRuleContext.toPosition() : Position {
val customTokensource = this.start.tokenSource as? CustomLexer
val filename =
when {
customTokensource!=null -> customTokensource.modulePath.fileName.toString()
start.tokenSource.sourceName == IntStream.UNKNOWN_SOURCE_NAME -> "@internal@"
else -> File(start.inputStream.sourceName).name
}
// note: be ware of TAB characters in the source text, they count as 1 column...
return Position(filename, start.line, start.charPositionInLine, stop.charPositionInLine+stop.text.length)
}
private fun prog8Parser.ModulestatementContext.toAst(isInLibrary: Boolean) : IStatement {
val directive = directive()?.toAst()
if(directive!=null) return directive
val block = block()?.toAst()
val block = block()?.toAst(isInLibrary)
if(block!=null) return block
throw FatalAstException(text)
}
private fun prog8Parser.BlockContext.toAst() : IStatement =
Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), statement_block().toAst(), toPosition())
private fun prog8Parser.BlockContext.toAst(isInLibrary: Boolean) : IStatement =
Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), statement_block().toAst(), isInLibrary, toPosition())
private fun prog8Parser.Statement_blockContext.toAst(): MutableList<IStatement> =
@ -1792,40 +1859,47 @@ private fun prog8Parser.StatementContext.toAst() : IStatement {
return VarDecl(VarDeclType.VAR,
it.datatype().toAst(),
it.ZEROPAGE()!=null,
it.arrayspec()?.toAst(),
it.arrayindex()?.toAst(),
it.ARRAYSIG()!=null,
it.identifier().text,
null,
it.toPosition())
}
varinitializer()?.let {
val vd = it.vardecl()
return VarDecl(VarDeclType.VAR,
it.vardecl().datatype().toAst(),
it.vardecl().ZEROPAGE()!=null,
it.vardecl().arrayspec()?.toAst(),
it.vardecl().identifier().text,
vd.datatype().toAst(),
vd.ZEROPAGE()!=null,
vd.arrayindex()?.toAst(),
vd.ARRAYSIG()!=null,
vd.identifier().text,
it.expression().toAst(),
it.toPosition())
}
constdecl()?.let {
val cvarinit = it.varinitializer()
val vd = cvarinit.vardecl()
return VarDecl(VarDeclType.CONST,
cvarinit.vardecl().datatype().toAst(),
cvarinit.vardecl().ZEROPAGE()!=null,
cvarinit.vardecl().arrayspec()?.toAst(),
cvarinit.vardecl().identifier().text,
vd.datatype().toAst(),
vd.ZEROPAGE()!=null,
vd.arrayindex()?.toAst(),
vd.ARRAYSIG()!=null,
vd.identifier().text,
cvarinit.expression().toAst(),
cvarinit.toPosition())
}
memoryvardecl()?.let {
val mvarinit = it.varinitializer()
val vd = mvarinit.vardecl()
return VarDecl(VarDeclType.MEMORY,
mvarinit.vardecl().datatype().toAst(),
mvarinit.vardecl().ZEROPAGE()!=null,
mvarinit.vardecl().arrayspec()?.toAst(),
mvarinit.vardecl().identifier().text,
vd.datatype().toAst(),
vd.ZEROPAGE()!=null,
vd.arrayindex()?.toAst(),
vd.ARRAYSIG()!=null,
vd.identifier().text,
mvarinit.expression().toAst(),
mvarinit.toPosition())
}
@ -2021,8 +2095,8 @@ private fun prog8Parser.DatatypeContext.toAst() = DataType.valueOf(text.toUpperC
private fun prog8Parser.RegisterorpairContext.toAst() = RegisterOrPair.valueOf(text.toUpperCase())
private fun prog8Parser.ArrayspecContext.toAst() : ArraySpec =
ArraySpec(expression().toAst(), toPosition())
private fun prog8Parser.ArrayindexContext.toAst() : ArrayIndex =
ArrayIndex(expression().toAst(), toPosition())
private fun prog8Parser.DirectiveContext.toAst() : Directive =
@ -2039,7 +2113,11 @@ private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
var datatype = DataType.UBYTE
when (radix) {
10 -> {
integer = text.toInt()
integer = try {
text.toInt()
} catch(x: NumberFormatException) {
throw AstException("${toPosition()} invalid decimal literal ${x.message}")
}
datatype = when(integer) {
in 0..255 -> DataType.UBYTE
in -128..127 -> DataType.BYTE
@ -2051,12 +2129,20 @@ private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
2 -> {
if(text.length>8)
datatype = DataType.UWORD
integer = text.toInt(2)
try {
integer = text.toInt(2)
} catch(x: NumberFormatException) {
throw AstException("${toPosition()} invalid binary literal ${x.message}")
}
}
16 -> {
if(text.length>2)
datatype = DataType.UWORD
integer = text.toInt(16)
try {
integer = text.toInt(16)
} catch(x: NumberFormatException) {
throw AstException("${toPosition()} invalid hexadecimal literal ${x.message}")
}
}
else -> throw FatalAstException("invalid radix")
}
@ -2094,10 +2180,16 @@ private fun prog8Parser.ExpressionContext.toAst() : IExpression {
}
litval.floatliteral()!=null -> LiteralValue(DataType.FLOAT, floatvalue = litval.floatliteral().toAst(), position = litval.toPosition())
litval.stringliteral()!=null -> LiteralValue(DataType.STR, strvalue = unescape(litval.stringliteral().text, litval.toPosition()), position = litval.toPosition())
litval.charliteral()!=null -> LiteralValue(DataType.UBYTE, bytevalue = Petscii.encodePetscii(unescape(litval.charliteral().text, litval.toPosition()), true)[0], position = litval.toPosition())
litval.charliteral()!=null -> {
try {
LiteralValue(DataType.UBYTE, bytevalue = Petscii.encodePetscii(unescape(litval.charliteral().text, litval.toPosition()), true)[0], position = litval.toPosition())
} catch (ce: CharConversionException) {
throw SyntaxError(ce.message ?: ce.toString(), litval.toPosition())
}
}
litval.arrayliteral()!=null -> {
val array = litval.arrayliteral()?.toAst()
// the actual type of the arrayspec can not yet be determined here (missing namespace & heap)
// the actual type of the arraysize can not yet be determined here (missing namespace & heap)
// the ConstantFolder takes care of that and converts the type if needed.
LiteralValue(DataType.ARRAY_UB, arrayvalue = array, position = litval.toPosition())
}
@ -2138,13 +2230,16 @@ private fun prog8Parser.ExpressionContext.toAst() : IExpression {
if(directmemory()!=null)
return DirectMemoryRead(directmemory().expression().toAst(), toPosition())
if(addressof()!=null)
return AddressOf(addressof().scoped_identifier().toAst(), toPosition())
throw FatalAstException(text)
}
private fun prog8Parser.ArrayindexedContext.toAst(): ArrayIndexedExpression {
return ArrayIndexedExpression(scoped_identifier().toAst(),
arrayspec().toAst(),
arrayindex().toAst(),
toPosition())
}
@ -2236,31 +2331,6 @@ private fun prog8Parser.RepeatloopContext.toAst(): RepeatLoop {
}
internal fun registerSet(asmReturnvaluesRegisters: Iterable<RegisterOrStatusflag>): Set<Register> {
val resultRegisters = mutableSetOf<Register>()
for(x in asmReturnvaluesRegisters) {
when(x.registerOrPair) {
RegisterOrPair.A -> resultRegisters.add(Register.A)
RegisterOrPair.X -> resultRegisters.add(Register.X)
RegisterOrPair.Y -> resultRegisters.add(Register.Y)
RegisterOrPair.AX -> {
resultRegisters.add(Register.A)
resultRegisters.add(Register.X)
}
RegisterOrPair.AY -> {
resultRegisters.add(Register.A)
resultRegisters.add(Register.Y)
}
RegisterOrPair.XY -> {
resultRegisters.add(Register.X)
resultRegisters.add(Register.Y)
}
}
}
return resultRegisters
}
internal fun escape(str: String) = str.replace("\t", "\\t").replace("\n", "\\n").replace("\r", "\\r")
internal fun unescape(str: String, position: Position): String {
@ -2278,7 +2348,7 @@ internal fun unescape(str: String, position: Position): String {
'u' -> {
"${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}".toInt(16).toChar()
}
else -> throw AstException("$position invalid escape char in string: \\$ec")
else -> throw SyntaxError("invalid escape char in string: \\$ec", position)
})
} else {
result.add(c)

View File

@ -7,6 +7,7 @@ import prog8.compiler.target.c64.FLOAT_MAX_POSITIVE
import prog8.functions.BuiltinFunctions
import prog8.optimizing.same
import prog8.parser.ParsingFailedError
import java.io.File
/**
* General checks on the Ast
@ -58,7 +59,7 @@ private class AstChecker(private val namespace: INameScope,
private val heapStringSentinel: Int
init {
val stringSentinel = heap.allEntries().firstOrNull {it.value.str==""}
heapStringSentinel = stringSentinel?.key ?: heap.add(DataType.STR, "")
heapStringSentinel = stringSentinel?.key ?: heap.addString(DataType.STR, "")
}
fun result(): List<AstException> {
@ -139,9 +140,6 @@ private class AstChecker(private val namespace: INameScope,
if(forLoop.body.isEmpty())
printWarning("for loop body is empty", forLoop.position)
if(forLoop.iterable is LiteralValue)
checkResult.add(SyntaxError("currently not possible to loop over a literal value directly, define it as a variable instead", forLoop.position)) // todo loop over literals (by creating a generated variable)
if(!forLoop.iterable.isIterable(namespace, heap)) {
checkResult.add(ExpressionError("can only loop over an iterable type", forLoop.position))
} else {
@ -153,7 +151,7 @@ private class AstChecker(private val namespace: INameScope,
checkResult.add(ExpressionError("register can only loop over bytes", forLoop.position))
} else {
// loop variable
val loopvar = forLoop.loopVar!!.targetStatement(namespace) as? VarDecl
val loopvar = forLoop.loopVar!!.targetVarDecl(namespace)
if(loopvar==null || loopvar.type==VarDeclType.CONST) {
checkResult.add(SyntaxError("for loop requires a variable to loop with", forLoop.position))
@ -336,7 +334,7 @@ private class AstChecker(private val namespace: INameScope,
if(subroutine.asmClobbers.intersect(regCounts.keys).isNotEmpty())
err("a return register is also in the clobber list")
} else {
// TODO: non-asm subroutines can only take numeric arguments for now. (not strings and arrays)
// TODO: non-asm subroutines can only take numeric arguments for now. (not strings and arrays) Maybe this can be improved now that we have '&' ?
// the way string params are treated is almost okay (their address is passed) but the receiving subroutine treats it as an integer rather than referring back to the original string.
// the way array params are treated is buggy; it thinks the subroutine needs a byte parameter in place of a byte[] ...
// This is not easy to fix because strings and arrays are treated a bit simplistic (a "virtual" pointer to the value on the heap)
@ -358,23 +356,17 @@ private class AstChecker(private val namespace: INameScope,
// 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(namespace)
if (stmt is Subroutine && stmt.returntypes.size > 1) {
if (stmt is Subroutine) {
if (stmt.isAsmSubroutine) {
if (stmt.returntypes.size != assignment.targets.size)
checkResult.add(ExpressionError("number of return values doesn't match number of assignment targets", assignment.value.position))
else {
if (assignment.targets.all { it.register != null }) {
val returnRegisters = registerSet(stmt.asmReturnvaluesRegisters)
val targetRegisters = assignment.targets.filter { it.register != null }.map { it.register }.toSet()
if (returnRegisters != targetRegisters)
checkResult.add(ExpressionError("asmsub return registers $returnRegisters don't match assignment target registers", assignment.position))
}
for (thing in stmt.returntypes.zip(assignment.targets)) {
if (thing.second.determineDatatype(namespace, heap, assignment) != thing.first)
checkResult.add(ExpressionError("return type mismatch for target ${thing.second.shortString()}", assignment.value.position))
}
}
} else
} else if(assignment.targets.size>1)
checkResult.add(ExpressionError("only asmsub subroutines can return multiple values", assignment.value.position))
}
}
@ -410,23 +402,10 @@ private class AstChecker(private val namespace: INameScope,
checkResult.add(ExpressionError("cannot assign new value to a constant", assignment.position))
return assignment
}
if(assignment.value.resultingDatatype(namespace, heap) in ArrayDatatypes) {
if(targetSymbol.datatype==DataType.UWORD)
return assignment // array can be assigned to UWORD (it's address should be taken as the value then)
}
}
}
}
// it is only possible to assign an array to something that is an UWORD or UWORD array (in which case the address of the array value is used as the value)
if(assignment.value.resultingDatatype(namespace, heap) in ArrayDatatypes) {
// the UWORD case has been handled above already, check for UWORD array
val arrayVar = target.arrayindexed?.identifier?.targetStatement(namespace)
if(arrayVar is VarDecl && arrayVar.datatype==DataType.ARRAY_UW)
return assignment
checkResult.add(SyntaxError("it's not possible to assign an array to something other than an UWORD, use it as a variable decl initializer instead", assignment.position))
}
if(assignment.aug_op!=null) {
// check augmented assignment (and convert it into a normal assignment!)
// A /= 3 -> check as if it was A = A / 3
@ -452,10 +431,10 @@ private class AstChecker(private val namespace: INameScope,
if(constVal!=null) {
val arrayspec = if(target.identifier!=null) {
val targetVar = namespace.lookup(target.identifier.nameInSource, assignment) as? VarDecl
targetVar?.arrayspec
targetVar?.arraysize
} else null
checkValueTypeAndRange(targetDatatype,
arrayspec ?: ArraySpec(LiteralValue.optimalInteger(-1, assignment.position), assignment.position),
arrayspec ?: ArrayIndex(LiteralValue.optimalInteger(-1, assignment.position), assignment.position),
constVal, heap)
} else {
val sourceDatatype: DataType? = assignment.value.resultingDatatype(namespace, heap)
@ -477,6 +456,18 @@ private class AstChecker(private val namespace: INameScope,
return assignment
}
override fun process(addressOf: AddressOf): IExpression {
val variable=addressOf.identifier.targetVarDecl(namespace)
if(variable==null)
checkResult.add(ExpressionError("pointer-of operand must be the name of a heap variable", addressOf.position))
else {
if(variable.datatype !in ArrayDatatypes && variable.datatype !in StringDatatypes)
checkResult.add(ExpressionError("pointer-of operand must be the name of a string or array heap variable", addressOf.position))
}
if(addressOf.scopedname==null)
throw FatalAstException("the scopedname of AddressOf should have been set by now $addressOf")
return super.process(addressOf)
}
/**
* Check the variable declarations (values within range etc)
@ -487,7 +478,7 @@ private class AstChecker(private val namespace: INameScope,
}
// the initializer value can't refer to the variable itself (recursive definition)
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arrayspec?.x?.referencesIdentifier(decl.name) == true) {
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.index?.referencesIdentifier(decl.name) == true) {
err("recursive var declaration")
}
@ -502,6 +493,22 @@ private class AstChecker(private val namespace: INameScope,
checkResult.add(SyntaxError("floating point used, but that is not enabled via options", decl.position))
}
// ARRAY without size specifier MUST have an iterable initializer value
if(decl.isUnsizedArray) {
if(decl.type==VarDeclType.MEMORY)
checkResult.add(SyntaxError("memory mapped array must have a size specification", decl.position))
if(decl.value==null) {
checkResult.add(SyntaxError("array variable is missing a size specification or an initialization value", decl.position))
return decl
}
if(decl.value is LiteralValue && !(decl.value as LiteralValue).isArray) {
checkResult.add(SyntaxError("unsized array declaration cannot use a single literal initialization value", decl.position))
return decl
}
if(decl.value is RangeExpr)
throw FatalAstException("range expressions in vardecls should have been converted into array values during constFolding $decl")
}
when(decl.type) {
VarDeclType.VAR, VarDeclType.CONST -> {
if (decl.value == null) {
@ -523,13 +530,16 @@ private class AstChecker(private val namespace: INameScope,
return super.process(decl)
}
when {
decl.value is RangeExpr -> checkValueTypeAndRange(decl.datatype, decl.arrayspec, decl.value as RangeExpr)
decl.value is RangeExpr -> {
if(!decl.isUnsizedArray)
checkValueTypeAndRange(decl.datatype, decl.arraysize!!, decl.value as RangeExpr)
}
decl.value is LiteralValue -> {
val arraySpec = decl.arrayspec ?: (
val arraySpec = decl.arraysize ?: (
if((decl.value as LiteralValue).isArray)
ArraySpec.forArray(decl.value as LiteralValue, heap)
ArrayIndex.forArray(decl.value as LiteralValue, heap)
else
ArraySpec(LiteralValue.optimalInteger(-2, decl.position), decl.position)
ArrayIndex(LiteralValue.optimalInteger(-2, decl.position), decl.position)
)
checkValueTypeAndRange(decl.datatype, arraySpec, decl.value as LiteralValue, heap)
}
@ -540,8 +550,8 @@ private class AstChecker(private val namespace: INameScope,
}
}
VarDeclType.MEMORY -> {
if(decl.arrayspec!=null) {
val arraySize = decl.arrayspec.size() ?: 1
if(decl.arraysize!=null) {
val arraySize = decl.arraysize!!.size() ?: 1
when(decl.datatype) {
DataType.ARRAY_B, DataType.ARRAY_UB ->
if(arraySize > 256)
@ -592,9 +602,10 @@ private class AstChecker(private val namespace: INameScope,
if(directive.parent !is Module) err("this directive may only occur at module level")
if(directive.args.size!=1 ||
directive.args[0].name != "basicsafe" &&
directive.args[0].name != "floatsafe" &&
directive.args[0].name != "kernalsafe" &&
directive.args[0].name != "full")
err("invalid zp type, expected basicsafe, kernalsafe, or full")
err("invalid zp type, expected basicsafe, floatsafe, kernalsafe, or full")
}
"%zpreserved" -> {
if(directive.parent !is Module) err("this directive may only occur at module level")
@ -622,6 +633,7 @@ private class AstChecker(private val namespace: INameScope,
if(directive.parent !is INameScope || directive.parent is Module) err("this directive may only occur in a block")
if(directive.args.size!=2 || directive.args[0].str==null || directive.args[1].str==null)
err("invalid asminclude directive, expected arguments: \"filename\", \"scopelabel\"")
checkFileExists(directive, directive.args[0].str!!)
}
"%asmbinary" -> {
if(directive.parent !is INameScope || directive.parent is Module) err("this directive may only occur in a block")
@ -631,6 +643,7 @@ private class AstChecker(private val namespace: INameScope,
if(directive.args.size>=2 && directive.args[1].int==null) err(errormsg)
if(directive.args.size==3 && directive.args[2].int==null) err(errormsg)
if(directive.args.size>3) err(errormsg)
checkFileExists(directive, directive.args[0].str!!)
}
"%option" -> {
if(directive.parent !is Block && directive.parent !is Module) err("this directive may only occur in a block or at module level")
@ -644,15 +657,23 @@ private class AstChecker(private val namespace: INameScope,
return super.process(directive)
}
private fun checkFileExists(directive: Directive, filename: String) {
var definingModule = directive.parent
while (definingModule !is Module)
definingModule = definingModule.parent
if (!(filename.startsWith("library:") || definingModule.importedFrom.resolveSibling(filename).toFile().isFile || File(filename).isFile))
checkResult.add(NameError("included file not found: $filename", directive.position))
}
override fun process(literalValue: LiteralValue): LiteralValue {
if(!compilerOptions.floats && literalValue.type==DataType.FLOAT) {
checkResult.add(SyntaxError("floating point used, but that is not enabled via options", literalValue.position))
}
val arrayspec =
if(literalValue.isArray)
ArraySpec.forArray(literalValue, heap)
ArrayIndex.forArray(literalValue, heap)
else
ArraySpec(LiteralValue.optimalInteger(-3, literalValue.position), literalValue.position)
ArrayIndex(LiteralValue.optimalInteger(-3, literalValue.position), literalValue.position)
checkValueTypeAndRange(literalValue.type, arrayspec, literalValue, heap)
val lv = super.process(literalValue)
@ -681,6 +702,9 @@ private class AstChecker(private val namespace: INameScope,
}
override fun process(expr: BinaryExpression): IExpression {
val leftDt = expr.left.resultingDatatype(namespace, heap)
val rightDt = expr.right.resultingDatatype(namespace, heap)
when(expr.operator){
"/", "%" -> {
val constvalRight = expr.right.constValue(namespace, heap)
@ -688,16 +712,30 @@ private class AstChecker(private val namespace: INameScope,
if(divisor==0.0)
checkResult.add(ExpressionError("division by zero", expr.right.position))
if(expr.operator=="%") {
val rightDt = constvalRight?.resultingDatatype(namespace, heap)
val leftDt = expr.left.resultingDatatype(namespace, heap)
if ((rightDt != DataType.UBYTE && rightDt != DataType.UWORD) || (leftDt!=DataType.UBYTE && leftDt!=DataType.UWORD))
checkResult.add(ExpressionError("remainder can only be used on unsigned integer operands", expr.right.position))
}
}
"**" -> {
if(leftDt in IntegerDatatypes)
checkResult.add(ExpressionError("power operator requires floating point", expr.position))
}
"and", "or", "xor" -> {
// only integer numeric operands accepted, and if literal constants, only boolean values accepted (0 or 1)
if(leftDt !in IntegerDatatypes || rightDt !in IntegerDatatypes)
checkResult.add(ExpressionError("logical operator can only be used on boolean operands", expr.right.position))
val constLeft = expr.left.constValue(namespace, heap)
val constRight = expr.right.constValue(namespace, heap)
if(constLeft!=null && constLeft.asIntegerValue !in 0..1 || constRight!=null && constRight.asIntegerValue !in 0..1)
checkResult.add(ExpressionError("const literal argument of logical operator must be boolean (0 or 1)", expr.position))
}
"&", "|", "^" -> {
// only integer numeric operands accepted
if(leftDt !in IntegerDatatypes || rightDt !in IntegerDatatypes)
checkResult.add(ExpressionError("bitwise operator can only be used on integer operands", expr.right.position))
}
}
val leftDt = expr.left.resultingDatatype(namespace, heap)!!
val rightDt = expr.right.resultingDatatype(namespace, heap)!!
if(leftDt !in NumericDatatypes)
checkResult.add(ExpressionError("left operand is not numeric", expr.left.position))
if(rightDt!in NumericDatatypes)
@ -708,12 +746,6 @@ private class AstChecker(private val namespace: INameScope,
override fun process(typecast: TypecastExpression): IExpression {
if(typecast.type in IterableDatatypes)
checkResult.add(ExpressionError("cannot type cast to string or array type", typecast.position))
val funcTarget = (typecast.expression as? IFunctionCall)?.target?.targetStatement(namespace)
if(funcTarget is Subroutine &&
funcTarget.asmReturnvaluesRegisters.isNotEmpty() &&
funcTarget.asmReturnvaluesRegisters.all { it.stack!=true }) {
checkResult.add(ExpressionError("cannot type cast a call to an asmsub that returns value in register - use a variable to store it first", typecast.position))
}
return super.process(typecast)
}
@ -769,13 +801,13 @@ private class AstChecker(private val namespace: INameScope,
return super.process(functionCall)
}
override fun process(functionCall: FunctionCallStatement): IStatement {
val targetStatement = checkFunctionOrLabelExists(functionCall.target, functionCall)
override fun process(functionCallStatement: FunctionCallStatement): IStatement {
val targetStatement = checkFunctionOrLabelExists(functionCallStatement.target, functionCallStatement)
if(targetStatement!=null)
checkFunctionCall(targetStatement, functionCall.arglist, functionCall.position)
checkFunctionCall(targetStatement, functionCallStatement.arglist, functionCallStatement.position)
if(targetStatement is Subroutine && targetStatement.returntypes.isNotEmpty())
printWarning("result value of subroutine call is discarded", functionCall.position)
return super.process(functionCall)
printWarning("result value of subroutine call is discarded", functionCallStatement.position)
return super.process(functionCallStatement)
}
private fun checkFunctionCall(target: IStatement, args: List<IExpression>, position: Position) {
@ -791,7 +823,7 @@ private class AstChecker(private val namespace: INameScope,
for (arg in args.withIndex().zip(func.parameters)) {
val argDt=arg.first.value.resultingDatatype(namespace, heap)
if(argDt!=null && !argDt.assignableTo(arg.second.possibleDatatypes)) {
checkResult.add(ExpressionError("builtin function argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.possibleDatatypes}", position))
checkResult.add(ExpressionError("builtin function '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.possibleDatatypes}", position))
}
}
if(target.name=="swap") {
@ -814,14 +846,30 @@ private class AstChecker(private val namespace: INameScope,
else {
for (arg in args.withIndex().zip(target.parameters)) {
val argDt = arg.first.value.resultingDatatype(namespace, heap)
if(argDt!=null && !argDt.assignableTo(arg.second.type))
checkResult.add(ExpressionError("subroutine argument ${arg.first.index+1} has invalid type $argDt, expected ${arg.second.type}", position))
if(argDt!=null && !argDt.assignableTo(arg.second.type)) {
// for asm subroutines having STR param it's okay to provide a UWORD too (pointer value)
if(!(target.isAsmSubroutine && arg.second.type in StringDatatypes && argDt==DataType.UWORD))
checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.type}", position))
}
if(target.isAsmSubroutine) {
if (target.asmParameterRegisters[arg.first.index].registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.XY, RegisterOrPair.X)) {
if (arg.first.value !is LiteralValue && arg.first.value !is IdentifierReference)
printWarning("calling a subroutine that expects X as a parameter is problematic, more so when providing complex arguments. If you see a compiler error/crash about this later, try to simplify this call", position)
}
// check if the argument types match the register(pairs)
val asmParamReg = target.asmParameterRegisters[arg.first.index]
if(asmParamReg.statusflag!=null) {
if(argDt !in ByteDatatypes)
checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index+1} must be byte type for statusflag", position))
} else if(asmParamReg.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) {
if(argDt !in ByteDatatypes)
checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index+1} must be byte type for single register", position))
} else if(asmParamReg.registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
if(argDt !in WordDatatypes+ IterableDatatypes)
checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index+1} must be word type for register pair", position))
}
}
}
}
@ -862,20 +910,17 @@ private class AstChecker(private val namespace: INameScope,
if(target is VarDecl) {
if(target.datatype !in IterableDatatypes)
checkResult.add(SyntaxError("indexing requires an iterable variable", arrayIndexedExpression.position))
val arraysize = target.arrayspec?.size()
val arraysize = target.arraysize?.size()
if(arraysize!=null) {
// check out of bounds
val index = (arrayIndexedExpression.arrayspec.x as? LiteralValue)?.asIntegerValue
val index = (arrayIndexedExpression.arrayspec.index as? LiteralValue)?.asIntegerValue
if(index!=null && (index<0 || index>=arraysize))
checkResult.add(ExpressionError("array index out of bounds", arrayIndexedExpression.arrayspec.position))
} else if(target.datatype in StringDatatypes) {
// check supported string tyep
if(target.datatype == DataType.STR_P || target.datatype==DataType.STR_PS)
checkResult.add(ExpressionError("indexing pascal-strings is not supported, use regular str type instead", arrayIndexedExpression.arrayspec.position))
// check string lengths
val heapId = (target.value as LiteralValue).heapId!!
val stringLen = heap.get(heapId).str!!.length
val index = (arrayIndexedExpression.arrayspec.x as? LiteralValue)?.asIntegerValue
val index = (arrayIndexedExpression.arrayspec.index as? LiteralValue)?.asIntegerValue
if(index!=null && (index<0 || index>=stringLen))
checkResult.add(ExpressionError("index out of bounds", arrayIndexedExpression.arrayspec.position))
}
@ -883,7 +928,7 @@ private class AstChecker(private val namespace: INameScope,
checkResult.add(SyntaxError("indexing requires a variable to act upon", arrayIndexedExpression.position))
// check index value 0..255
val dtx = arrayIndexedExpression.arrayspec.x.resultingDatatype(namespace, heap)
val dtx = arrayIndexedExpression.arrayspec.index.resultingDatatype(namespace, heap)
if(dtx!=DataType.UBYTE && dtx!=DataType.BYTE)
checkResult.add(SyntaxError("array indexing is limited to byte size 0..255", arrayIndexedExpression.position))
@ -894,11 +939,11 @@ private class AstChecker(private val namespace: INameScope,
val targetStatement = target.targetStatement(namespace)
if(targetStatement is Label || targetStatement is Subroutine || targetStatement is BuiltinFunctionStatementPlaceholder)
return targetStatement
checkResult.add(SyntaxError("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position))
checkResult.add(NameError("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position))
return null
}
private fun checkValueTypeAndRange(targetDt: DataType, arrayspec: ArraySpec?, range: RangeExpr) : Boolean {
private fun checkValueTypeAndRange(targetDt: DataType, arrayspec: ArrayIndex, range: RangeExpr) : Boolean {
val from = range.from.constValue(namespace, heap)
val to = range.to.constValue(namespace, heap)
if(from==null || to==null) {
@ -926,7 +971,7 @@ private class AstChecker(private val namespace: INameScope,
}
in ArrayDatatypes -> {
// range and length check bytes
val expectedSize = arrayspec!!.size()
val expectedSize = arrayspec.size()
val rangeSize=range.size(heap)
if(rangeSize!=null && rangeSize != expectedSize) {
checkResult.add(ExpressionError("range size doesn't match array size, expected $expectedSize found $rangeSize", range.position))
@ -938,7 +983,7 @@ private class AstChecker(private val namespace: INameScope,
}
}
private fun checkValueTypeAndRange(targetDt: DataType, arrayspec: ArraySpec, value: LiteralValue, heap: HeapValues) : Boolean {
private fun checkValueTypeAndRange(targetDt: DataType, arrayspec: ArrayIndex, value: LiteralValue, heap: HeapValues) : Boolean {
fun err(msg: String) : Boolean {
checkResult.add(ExpressionError(msg, value.position))
return false
@ -946,8 +991,8 @@ private class AstChecker(private val namespace: INameScope,
when (targetDt) {
DataType.FLOAT -> {
val number = when(value.type) {
DataType.UBYTE, DataType.BYTE -> value.bytevalue!!.toDouble()
DataType.UWORD, DataType.WORD -> value.wordvalue!!.toDouble()
in ByteDatatypes -> value.bytevalue!!.toDouble()
in WordDatatypes -> value.wordvalue!!.toDouble()
DataType.FLOAT -> value.floatvalue!!
else -> return err("numeric value expected")
}
@ -971,8 +1016,6 @@ private class AstChecker(private val namespace: INameScope,
return err("value '$number' out of range for byte")
}
DataType.UWORD -> {
if(value.isString || value.isArray) // string or array are assignable to uword; their memory address is used.
return true
val number = value.asIntegerValue ?: return if (value.floatvalue!=null)
err("unsigned word value expected instead of float; possible loss of precision")
else
@ -988,7 +1031,7 @@ private class AstChecker(private val namespace: INameScope,
if (number < -32768 || number > 32767)
return err("value '$number' out of range for word")
}
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
DataType.STR, DataType.STR_S -> {
if(!value.isString)
return err("string value expected")
val str = value.strvalue(heap)
@ -996,7 +1039,7 @@ private class AstChecker(private val namespace: INameScope,
return err("string length must be 0-255")
}
DataType.ARRAY_UB, DataType.ARRAY_B -> {
// value may be either a single byte, or a byte arrayspec (of all constant values)
// value may be either a single byte, or a byte arraysize (of all constant values), or a range
if(value.type==targetDt) {
if(!checkArrayValues(value, targetDt))
return false
@ -1005,7 +1048,7 @@ private class AstChecker(private val namespace: INameScope,
if(arraySpecSize!=null && arraySpecSize>0) {
if(arraySpecSize<1 || arraySpecSize>256)
return err("byte array length must be 1-256")
val constX = arrayspec.x.constValue(namespace, heap)
val constX = arrayspec.index.constValue(namespace, heap)
if(constX?.asIntegerValue==null)
return err("array size specifier must be constant integer value")
val expectedSize = constX.asIntegerValue
@ -1018,7 +1061,7 @@ private class AstChecker(private val namespace: INameScope,
return err("invalid byte array initialization value ${value.type}, expected $targetDt")
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
// value may be either a single word, or a word arrayspec
// value may be either a single word, or a word arraysize, or a range
if(value.type==targetDt) {
if(!checkArrayValues(value, targetDt))
return false
@ -1027,7 +1070,7 @@ private class AstChecker(private val namespace: INameScope,
if(arraySpecSize!=null && arraySpecSize>0) {
if(arraySpecSize<1 || arraySpecSize>128)
return err("word array length must be 1-128")
val constX = arrayspec.x.constValue(namespace, heap)
val constX = arrayspec.index.constValue(namespace, heap)
if(constX?.asIntegerValue==null)
return err("array size specifier must be constant integer value")
val expectedSize = constX.asIntegerValue
@ -1040,7 +1083,7 @@ private class AstChecker(private val namespace: INameScope,
return err("invalid word array initialization value ${value.type}, expected $targetDt")
}
DataType.ARRAY_F -> {
// value may be either a single float, or a float arrayspec
// value may be either a single float, or a float arraysize
if(value.type==targetDt) {
if(!checkArrayValues(value, targetDt))
return false
@ -1049,7 +1092,7 @@ private class AstChecker(private val namespace: INameScope,
if(arraySpecSize!=null && arraySpecSize>0) {
if(arraySpecSize < 1 || arraySpecSize>51)
return err("float array length must be 1-51")
val constX = arrayspec.x.constValue(namespace, heap)
val constX = arrayspec.index.constValue(namespace, heap)
if(constX?.asIntegerValue==null)
return err("array size specifier must be constant integer value")
val expectedSize = constX.asIntegerValue
@ -1078,16 +1121,16 @@ private class AstChecker(private val namespace: INameScope,
val correct: Boolean
when(type) {
DataType.ARRAY_UB -> {
correct=array.array!=null && array.array.all { it in 0..255 }
correct=array.array!=null && array.array.all { it.integer!=null && it.integer in 0..255 }
}
DataType.ARRAY_B -> {
correct=array.array!=null && array.array.all { it in -128..127 }
correct=array.array!=null && array.array.all { it.integer!=null && it.integer in -128..127 }
}
DataType.ARRAY_UW -> {
correct=array.array!=null && array.array.all { it in 0..65535 }
correct=array.array!=null && array.array.all { (it.integer!=null && it.integer in 0..65535) || it.addressOf!=null}
}
DataType.ARRAY_W -> {
correct=array.array!=null && array.array.all { it in -32768..32767 }
correct=array.array!=null && array.array.all { it.integer!=null && it.integer in -32768..32767 }
}
DataType.ARRAY_F -> correct = array.doubleArray!=null
else -> throw AstException("invalid array type $type")
@ -1110,12 +1153,10 @@ private class AstChecker(private val namespace: INameScope,
DataType.BYTE -> sourceDatatype==DataType.BYTE
DataType.UBYTE -> sourceDatatype==DataType.UBYTE
DataType.WORD -> sourceDatatype==DataType.BYTE || sourceDatatype==DataType.UBYTE || sourceDatatype==DataType.WORD
DataType.UWORD -> sourceDatatype in setOf(DataType.UBYTE, DataType.UWORD, DataType.STR, DataType.STR_S) || sourceDatatype in ArrayDatatypes
DataType.UWORD -> sourceDatatype==DataType.UBYTE || sourceDatatype==DataType.UWORD
DataType.FLOAT -> sourceDatatype in NumericDatatypes
DataType.STR -> sourceDatatype==DataType.STR
DataType.STR_S -> sourceDatatype==DataType.STR_S
DataType.STR_P -> sourceDatatype==DataType.STR_P
DataType.STR_PS -> sourceDatatype==DataType.STR_PS
else -> checkResult.add(SyntaxError("cannot assign new value to variable of type $targetDatatype", position))
}
@ -1129,9 +1170,9 @@ private class AstChecker(private val namespace: INameScope,
checkResult.add(ExpressionError("cannot assign word to byte, use msb() or lsb()?", position))
}
else if(sourceDatatype==DataType.FLOAT && targetDatatype in IntegerDatatypes)
checkResult.add(ExpressionError("cannot assign float to ${targetDatatype.toString().toLowerCase()}; possible loss of precision. Suggestion: round the value or revert to integer arithmetic", position))
checkResult.add(ExpressionError("cannot assign float to ${targetDatatype.name.toLowerCase()}; possible loss of precision. Suggestion: round the value or revert to integer arithmetic", position))
else
checkResult.add(ExpressionError("cannot assign ${sourceDatatype.toString().toLowerCase()} to ${targetDatatype.toString().toLowerCase()}", position))
checkResult.add(ExpressionError("cannot assign ${sourceDatatype.name.toLowerCase()} to ${targetDatatype.name.toLowerCase()}", position))
return false
}

View File

@ -1,49 +1,53 @@
package prog8.ast
import prog8.compiler.HeapValues
import prog8.functions.BuiltinFunctions
/**
* Checks the validity of all identifiers (no conflicts)
* Also builds a list of all (scoped) symbol definitions
* Also makes sure that subroutine's parameters also become local variable decls in the subroutine's scope.
* Finally, it also makes sure the datatype of all Var decls and sub Return values is set correctly.
*/
fun Module.checkIdentifiers(heap: HeapValues): MutableMap<String, IStatement> {
val checker = AstIdentifiersChecker(heap)
fun Module.checkIdentifiers(namespace: INameScope) {
val checker = AstIdentifiersChecker(namespace)
this.process(checker)
// add any anonymous variables for heap values that are used, and replace literalvalue by identifierref
for (variable in checker.anonymousVariablesFromHeap) {
// add any anonymous variables for heap values that are used,
// and replace an iterable literalvalue by identifierref to new local variable
for (variable in checker.anonymousVariablesFromHeap.values) {
val scope = variable.first.definingScope()
scope.statements.add(variable.second)
val parent = variable.first.parent
when {
parent is Assignment && parent.value === variable.first -> {
val idref = IdentifierReference(listOf("auto_heap_value_${variable.first.heapId}"), variable.first.position)
val idref = IdentifierReference(listOf("$autoHeapValuePrefix${variable.first.heapId}"), variable.first.position)
idref.linkParents(parent)
parent.value = idref
}
parent is IFunctionCall -> {
val parameterPos = parent.arglist.indexOf(variable.first)
val idref = IdentifierReference(listOf("auto_heap_value_${variable.first.heapId}"), variable.first.position)
val idref = IdentifierReference(listOf("$autoHeapValuePrefix${variable.first.heapId}"), variable.first.position)
idref.linkParents(parent)
parent.arglist[parameterPos] = idref
}
parent is ForLoop -> {
val idref = IdentifierReference(listOf("$autoHeapValuePrefix${variable.first.heapId}"), variable.first.position)
idref.linkParents(parent)
parent.iterable = idref
}
else -> TODO("replace literalvalue by identifierref: $variable (in $parent)")
}
variable.second.linkParents(scope as Node)
}
printErrors(checker.result(), name)
return checker.symbols
}
private class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor {
private class AstIdentifiersChecker(private val namespace: INameScope) : IAstProcessor {
private val checkResult: MutableList<AstException> = mutableListOf()
var symbols: MutableMap<String, IStatement> = mutableMapOf()
var blocks: MutableMap<String, Block> = mutableMapOf()
private set
fun result(): List<AstException> {
@ -51,17 +55,16 @@ private class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor {
}
private fun nameError(name: String, position: Position, existing: IStatement) {
checkResult.add(NameError("name conflict '$name', first defined in ${existing.position.file} line ${existing.position.line}", position))
checkResult.add(NameError("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position))
}
override fun process(block: Block): IStatement {
val scopedName = block.scopedname
val existing = symbols[scopedName]
if(existing!=null) {
val existing = blocks[block.name]
if(existing!=null)
nameError(block.name, block.position, existing)
} else {
symbols[scopedName] = block
}
else
blocks[block.name] = block
return super.process(block)
}
@ -84,13 +87,10 @@ private class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor {
// the builtin functions can't be redefined
checkResult.add(NameError("builtin function cannot be redefined", decl.position))
val scopedName = decl.scopedname
val existing = symbols[scopedName]
if(existing!=null) {
val existing = namespace.lookup(listOf(decl.name), decl)
if (existing != null && existing !== decl)
nameError(decl.name, decl.position, existing)
} else {
symbols[scopedName] = decl
}
return super.process(decl)
}
@ -102,13 +102,9 @@ private class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor {
if (subroutine.parameters.any { it.name in BuiltinFunctions })
checkResult.add(NameError("builtin function name cannot be used as parameter", subroutine.position))
val scopedName = subroutine.scopedname
val existing = symbols[scopedName]
if (existing != null) {
val existing = namespace.lookup(listOf(subroutine.name), subroutine)
if (existing != null && existing !== subroutine)
nameError(subroutine.name, subroutine.position, existing)
} else {
symbols[scopedName] = subroutine
}
// check that there are no local variables that redefine the subroutine's parameters
val allDefinedNames = subroutine.allLabelsAndVariables()
@ -124,12 +120,13 @@ private class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor {
// NOTE:
// - numeric types BYTE and WORD and FLOAT are passed by value;
// - strings, arrays, matrices are passed by reference (their 16-bit address is passed as an uword parameter)
if(subroutine.asmAddress==null) {
// - do NOT do this is the statement can be transformed into an asm subroutine later!
if(subroutine.asmAddress==null && !subroutine.canBeAsmSubroutine) {
if(subroutine.asmParameterRegisters.isEmpty()) {
subroutine.parameters
.filter { it.name !in allDefinedNames }
.forEach {
val vardecl = VarDecl(VarDeclType.VAR, it.type, false, null, it.name, null, subroutine.position)
val vardecl = VarDecl(VarDeclType.VAR, it.type, false, null, false, it.name, null, subroutine.position)
vardecl.linkParents(subroutine)
subroutine.statements.add(0, vardecl)
}
@ -144,13 +141,9 @@ private class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor {
// the builtin functions can't be redefined
checkResult.add(NameError("builtin function cannot be redefined", label.position))
} else {
val scopedName = label.scopedname
val existing = symbols[scopedName]
if (existing != null) {
val existing = namespace.lookup(listOf(label.name), label)
if (existing != null && existing !== label)
nameError(label.name, label.position, existing)
} else {
symbols[scopedName] = label
}
}
return super.process(label)
}
@ -171,7 +164,7 @@ private class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor {
val existing = if(forLoop.body.isEmpty()) null else forLoop.body.lookup(forLoop.loopVar.nameInSource, forLoop.body.statements.first())
if(existing==null) {
// create the local scoped for loop variable itself
val vardecl = VarDecl(VarDeclType.VAR, forLoop.decltype, true, null, varName, null, forLoop.loopVar.position)
val vardecl = VarDecl(VarDeclType.VAR, forLoop.decltype, true, null, false, varName, null, forLoop.loopVar.position)
vardecl.linkParents(forLoop.body)
forLoop.body.statements.add(0, vardecl)
forLoop.loopVar.parent = forLoop.body // loopvar 'is defined in the body'
@ -183,7 +176,7 @@ private class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor {
val existing = if(forLoop.body.isEmpty()) null else forLoop.body.lookup(listOf(ForLoop.iteratorLoopcounterVarname), forLoop.body.statements.first())
if(existing==null) {
// create loop iteration counter variable (without value, to avoid an assignment)
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, true, null, ForLoop.iteratorLoopcounterVarname, null, forLoop.loopVar.position)
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, true, null, false, ForLoop.iteratorLoopcounterVarname, null, forLoop.loopVar.position)
vardecl.linkParents(forLoop.body)
forLoop.body.statements.add(0, vardecl)
forLoop.loopVar.parent = forLoop.body // loopvar 'is defined in the body'
@ -224,15 +217,26 @@ private class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor {
}
internal val anonymousVariablesFromHeap = mutableSetOf<Pair<LiteralValue, VarDecl>>()
internal val anonymousVariablesFromHeap = mutableMapOf<String, Pair<LiteralValue, VarDecl>>()
override fun process(literalValue: LiteralValue): LiteralValue {
if(literalValue.heapId!=null && literalValue.parent !is VarDecl) {
// a literal value that's not declared as a variable, which refers to something on the heap.
// we need to introduce an auto-generated variable for this to be able to refer to the value!
val variable = VarDecl(VarDeclType.VAR, literalValue.type, false, null, "auto_heap_value_${literalValue.heapId}", literalValue, literalValue.position)
anonymousVariablesFromHeap.add(Pair(literalValue, variable))
val variable = VarDecl(VarDeclType.VAR, literalValue.type, false, null, false, "$autoHeapValuePrefix${literalValue.heapId}", literalValue, literalValue.position)
anonymousVariablesFromHeap[variable.name] = Pair(literalValue, variable)
}
return super.process(literalValue)
}
override fun process(addressOf: AddressOf): IExpression {
// register the scoped name of the referenced identifier
val variable= addressOf.identifier.targetVarDecl(namespace) ?: return addressOf
addressOf.scopedname = variable.scopedname
return super.process(addressOf)
}
}
internal const val autoHeapValuePrefix = "auto_heap_value_"

View File

@ -92,9 +92,9 @@ private class AstRecursionChecker(private val namespace: INameScope) : IAstProce
return listOf(AstException("Program contains recursive subroutine calls, this is not supported. Recursive chain:\n (a subroutine call in) $chain"))
}
override fun process(functionCall: FunctionCallStatement): IStatement {
val scope = functionCall.definingScope()
val targetStatement = functionCall.target.targetStatement(namespace)
override fun process(functionCallStatement: FunctionCallStatement): IStatement {
val scope = functionCallStatement.definingScope()
val targetStatement = functionCallStatement.target.targetStatement(namespace)
if(targetStatement!=null) {
val targetScope = when (targetStatement) {
is Subroutine -> targetStatement
@ -102,7 +102,7 @@ private class AstRecursionChecker(private val namespace: INameScope) : IAstProce
}
callGraph.add(scope, targetScope)
}
return super.process(functionCall)
return super.process(functionCallStatement)
}
override fun process(functionCall: FunctionCall): IExpression {

View File

@ -3,7 +3,7 @@ package prog8.ast
import prog8.compiler.HeapValues
fun Module.reorderStatements(namespace: INameScope, heap: HeapValues) {
val initvalueCreator = VarInitValueCreator()
val initvalueCreator = VarInitValueAndAddressOfCreator(namespace)
this.process(initvalueCreator)
val checker = StatementReorderer(namespace, heap)
@ -33,11 +33,21 @@ private class StatementReorderer(private val namespace: INameScope, private val
val (blocks, other) = module.statements.partition { it is Block }
module.statements = other.asSequence().plus(blocks.sortedBy { (it as Block).address ?: Int.MAX_VALUE }).toMutableList()
// make sure user-defined blocks come BEFORE library blocks, and move the "main" block to the top of everything
val nonLibraryBlocks = module.statements.withIndex()
.filter { it.value is Block && !(it.value as Block).isInLibrary }
.map { it.index to it.value }
.reversed()
for(nonLibBlock in nonLibraryBlocks)
module.statements.removeAt(nonLibBlock.first)
for(nonLibBlock in nonLibraryBlocks)
module.statements.add(0, nonLibBlock.second)
val mainBlock = module.statements.single { it is Block && it.name=="main" }
if((mainBlock as Block).address==null) {
module.statements.remove(mainBlock)
module.statements.add(0, mainBlock)
}
val varDecls = module.statements.filterIsInstance<VarDecl>()
module.statements.removeAll(varDecls)
module.statements.addAll(0, varDecls)
@ -46,8 +56,6 @@ private class StatementReorderer(private val namespace: INameScope, private val
module.statements.removeAll(directives)
module.statements.addAll(0, directives)
// TODO make sure user-defined blocks come BEFORE library blocks
sortConstantAssignments(module.statements)
}
@ -147,12 +155,23 @@ private class StatementReorderer(private val namespace: INameScope, private val
return scope
}
override fun process(decl: VarDecl): IStatement {
if(decl.arraysize==null) {
val array = decl.value as? LiteralValue
if(array!=null && array.isArray) {
val size = heap.get(array.heapId!!).arraysize
decl.arraysize = ArrayIndex(LiteralValue.optimalInteger(size, decl.position), decl.position)
}
}
return super.process(decl)
}
private fun sortConstantAssignments(statements: MutableList<IStatement>) {
// sort assignments by datatype and value, so multiple initializations with the same value can be optimized (to load the value just once)
val result = mutableListOf<IStatement>()
val stmtIter = statements.iterator()
for(stmt in stmtIter) {
if(stmt is Assignment) {
if(stmt is Assignment && !stmt.targets.any { it.isMemoryMapped(namespace) }) {
val constval = stmt.value.constValue(namespace, heap)
if(constval!=null) {
val (sorted, trailing) = sortConstantAssignmentSequence(stmt, stmtIter)
@ -195,7 +214,7 @@ private class StatementReorderer(private val namespace: INameScope, private val
}
private class VarInitValueCreator: IAstProcessor {
private class VarInitValueAndAddressOfCreator(private val namespace: INameScope): IAstProcessor {
// Replace the var decl with an assignment and add a new vardecl with the default constant value.
// This makes sure the variables get reset to the intended value on a next run of the program.
// Variable decls without a value don't get this treatment, which means they retain the last
@ -203,7 +222,10 @@ private class VarInitValueCreator: IAstProcessor {
// This is done in a separate step because it interferes with the namespace lookup of symbols
// in other ast processors.
private val vardeclsToAdd = mutableMapOf<INameScope, MutableList<VarDecl>>()
// Also takes care to insert AddressOf (&) expression where required (string params to a UWORD function param etc).
private val vardeclsToAdd = mutableMapOf<INameScope, MutableMap<String, VarDecl>>()
override fun process(module: Module) {
super.process(module)
@ -211,8 +233,8 @@ private class VarInitValueCreator: IAstProcessor {
// add any new vardecls to the various scopes
for(decl in vardeclsToAdd)
for(d in decl.value) {
d.linkParents(decl.key as Node)
decl.key.statements.add(0, d)
d.value.linkParents(decl.key as Node)
decl.key.statements.add(0, d.value)
}
}
@ -223,9 +245,7 @@ private class VarInitValueCreator: IAstProcessor {
if(decl.datatype in NumericDatatypes) {
val scope = decl.definingScope()
if(scope !in vardeclsToAdd)
vardeclsToAdd[scope] = mutableListOf()
vardeclsToAdd[scope]!!.add(decl.asDefaultValueDecl(null))
addVarDecl(scope, decl.asDefaultValueDecl(null))
val declvalue = decl.value!!
val value =
if(declvalue is LiteralValue) {
@ -244,4 +264,63 @@ private class VarInitValueCreator: IAstProcessor {
return decl
}
override fun process(functionCall: FunctionCall): IExpression {
val targetStatement = functionCall.target.targetSubroutine(namespace)
if(targetStatement!=null) {
var node: Node = functionCall
while(node !is IStatement)
node=node.parent
addAddressOfExprIfNeeded(targetStatement, functionCall.arglist, node)
}
return functionCall
}
override fun process(functionCallStatement: FunctionCallStatement): IStatement {
val targetStatement = functionCallStatement.target.targetSubroutine(namespace)
if(targetStatement!=null)
addAddressOfExprIfNeeded(targetStatement, functionCallStatement.arglist, functionCallStatement)
return functionCallStatement
}
private fun addAddressOfExprIfNeeded(subroutine: Subroutine, arglist: MutableList<IExpression>, parent: IStatement) {
// functions that accept UWORD and are given an array type, or string, will receive the AddressOf (memory location) of that value instead.
for(argparam in subroutine.parameters.withIndex().zip(arglist)) {
if(argparam.first.value.type==DataType.UWORD || argparam.first.value.type in StringDatatypes) {
if(argparam.second is AddressOf)
continue
val idref = argparam.second as? IdentifierReference
val strvalue = argparam.second as? LiteralValue
if(idref!=null) {
val variable = idref.targetVarDecl(namespace)
if(variable!=null && (variable.datatype in StringDatatypes || variable.datatype in ArrayDatatypes)) {
val pointerExpr = AddressOf(idref, idref.position)
pointerExpr.scopedname = parent.makeScopedName(idref.nameInSource.single())
pointerExpr.linkParents(arglist[argparam.first.index].parent)
arglist[argparam.first.index] = pointerExpr
}
}
else if(strvalue!=null) {
if(strvalue.isString) {
// replace the argument with &autovar
val autoVarName = "$autoHeapValuePrefix${strvalue.heapId}"
val autoHeapvarRef = IdentifierReference(listOf(autoVarName), strvalue.position)
val pointerExpr = AddressOf(autoHeapvarRef, strvalue.position)
pointerExpr.scopedname = parent.makeScopedName(autoVarName)
pointerExpr.linkParents(arglist[argparam.first.index].parent)
arglist[argparam.first.index] = pointerExpr
// add a vardecl so that the autovar can be resolved in later lookups
val variable = VarDecl(VarDeclType.VAR, strvalue.type, false, null, false, autoVarName, strvalue, strvalue.position)
addVarDecl(strvalue.definingScope(), variable)
}
}
}
}
}
private fun addVarDecl(scope: INameScope, variable: VarDecl) {
if(scope !in vardeclsToAdd)
vardeclsToAdd[scope] = mutableMapOf()
vardeclsToAdd.getValue(scope)[variable.name]=variable
}
}

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@ import prog8.ast.*
class ZeropageDepletedError(message: String) : Exception(message)
abstract class Zeropage(private val options: CompilationOptions) {
abstract class Zeropage(protected val options: CompilationOptions) {
private val allocations = mutableMapOf<Int, Pair<String, DataType>>()
val free = mutableListOf<Int>() // subclasses must set this to the appropriate free locations.
@ -20,8 +20,8 @@ abstract class Zeropage(private val options: CompilationOptions) {
val size =
when (datatype) {
DataType.UBYTE, DataType.BYTE -> 1
DataType.UWORD, DataType.WORD -> 2
in ByteDatatypes -> 1
in WordDatatypes -> 2
DataType.FLOAT -> {
if (options.floats) {
if(position!=null)
@ -61,4 +61,12 @@ abstract class Zeropage(private val options: CompilationOptions) {
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
}

View File

@ -8,25 +8,34 @@ open class Instruction(val opcode: Opcode,
val callLabel: String? = null,
val callLabel2: String? = null)
{
lateinit var next: Instruction
var nextAlt: Instruction? = null
var branchAddress: Int? = null
override fun toString(): String {
val argStr = arg?.toString() ?: ""
val result =
when {
opcode==Opcode.LINE -> "_line $callLabel"
opcode==Opcode.INLINE_ASSEMBLY -> "inline_assembly"
opcode==Opcode.INLINE_ASSEMBLY -> {
// inline assembly is not written out (it can't be processed as intermediate language)
// instead, it is converted into a system call that can be intercepted by the vm
if(callLabel!=null)
"syscall SYSASM.$callLabel\n return"
else
"inline_assembly"
}
opcode==Opcode.INCLUDE_FILE -> {
"include_file \"$callLabel\" $arg $arg2"
}
opcode==Opcode.SYSCALL -> {
val syscall = Syscall.values().find { it.callNr==arg!!.numericValue() }
"syscall $syscall"
}
opcode in opcodesWithVarArgument -> {
// opcodes that manipulate a variable
"${opcode.toString().toLowerCase()} ${callLabel?:""} ${callLabel2?:""}".trimEnd()
"${opcode.name.toLowerCase()} ${callLabel?:""} ${callLabel2?:""}".trimEnd()
}
callLabel==null -> "${opcode.toString().toLowerCase()} $argStr"
else -> "${opcode.toString().toLowerCase()} $callLabel $argStr"
callLabel==null -> "${opcode.name.toLowerCase()} $argStr"
else -> "${opcode.name.toLowerCase()} $callLabel $argStr"
}
.trimEnd()

View File

@ -11,13 +11,12 @@ import java.nio.file.Path
class IntermediateProgram(val name: String, var loadAddress: Int, val heap: HeapValues, val importedFrom: Path) {
class ProgramBlock(val scopedname: String,
val shortname: String,
class ProgramBlock(val name: String,
var address: Int?,
val instructions: MutableList<Instruction> = mutableListOf(),
val variables: MutableMap<String, Value> = mutableMapOf(),
val variables: MutableMap<String, Value> = mutableMapOf(), // names are fully scoped
val memoryPointers: MutableMap<String, Pair<Int, DataType>> = mutableMapOf(),
val labels: MutableMap<String, Instruction> = mutableMapOf(),
val labels: MutableMap<String, Instruction> = mutableMapOf(), // names are fully scoped
val force_output: Boolean)
{
val numVariables: Int
@ -70,7 +69,7 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
optimizeMultipleSequentialLineInstrs()
optimizeCallReturnIntoJump()
optimizeConditionalBranches()
// todo: add more optimizations to stackvm code
// todo: add more optimizations to intermediate code!
optimizeRemoveNops() // must be done as the last step
optimizeMultipleSequentialLineInstrs() // once more
@ -389,20 +388,20 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
when(decl.type) {
VarDeclType.VAR -> {
val value = when(decl.datatype) {
DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT -> Value(decl.datatype, (decl.value as LiteralValue).asNumericValue!!)
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
in NumericDatatypes -> Value(decl.datatype, (decl.value as LiteralValue).asNumericValue!!)
in StringDatatypes -> {
val litval = (decl.value as LiteralValue)
if(litval.heapId==null)
throw CompilerException("string should already be in the heap")
Value(decl.datatype, litval.heapId)
}
DataType.ARRAY_B, DataType.ARRAY_W,
DataType.ARRAY_UB, DataType.ARRAY_UW, DataType.ARRAY_F -> {
in ArrayDatatypes -> {
val litval = (decl.value as LiteralValue)
if(litval.heapId==null)
throw CompilerException("array should already be in the heap")
Value(decl.datatype, litval.heapId)
}
else -> throw CompilerException("weird datatype")
}
currentBlock.variables[scopedname] = value
if(decl.zeropage)
@ -447,8 +446,8 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
currentBlock.memoryPointers[name] = Pair(address, datatype)
}
fun newBlock(scopedname: String, shortname: String, address: Int?, options: Set<String>) {
currentBlock = ProgramBlock(scopedname, shortname, address, force_output="force_output" in options)
fun newBlock(name: String, address: Int?, options: Set<String>) {
currentBlock = ProgramBlock(name, address, force_output="force_output" in options)
blocks.add(currentBlock)
}
@ -460,29 +459,44 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
out.println("%end_memory")
out.println("%heap")
heap.allEntries().forEach {
out.print("${it.key} ${it.value.type.name.toLowerCase()} ")
when {
it.value.str!=null ->
out.println("${it.key} ${it.value.type.toString().toLowerCase()} \"${escape(it.value.str!!)}\"")
it.value.array!=null ->
out.println("${it.key} ${it.value.type.toString().toLowerCase()} ${it.value.array!!.toList()}")
out.println("\"${escape(it.value.str!!)}\"")
it.value.array!=null -> {
// this array can contain both normal integers, and pointer values
val arrayvalues = it.value.array!!.map { av ->
when {
av.integer!=null -> av.integer.toString()
av.addressOf!=null -> {
if(av.addressOf.scopedname==null)
throw CompilerException("AddressOf scopedname should have been set")
else
"&${av.addressOf.scopedname}"
}
else -> throw CompilerException("weird array value")
}
}
out.println(arrayvalues)
}
it.value.doubleArray!=null ->
out.println("${it.key} ${it.value.type.toString().toLowerCase()} ${it.value.doubleArray!!.toList()}")
out.println(it.value.doubleArray!!.toList())
else -> throw CompilerException("invalid heap entry $it")
}
}
out.println("%end_heap")
for(blk in blocks) {
out.println("\n%block ${blk.scopedname} ${blk.address?.toString(16) ?: ""}")
out.println("\n%block ${blk.name} ${blk.address?.toString(16) ?: ""}")
out.println("%variables")
for(variable in blk.variables) {
val valuestr = variable.value.toString()
out.println("${variable.key} ${variable.value.type.toString().toLowerCase()} $valuestr")
out.println("${variable.key} ${variable.value.type.name.toLowerCase()} $valuestr")
}
out.println("%end_variables")
out.println("%memorypointers")
for(iconst in blk.memoryPointers) {
out.println("${iconst.key} ${iconst.value.second.toString().toLowerCase()} uw:${iconst.value.first.toString(16)}")
out.println("${iconst.key} ${iconst.value.second.name.toLowerCase()} uw:${iconst.value.first.toString(16)}")
}
out.println("%end_memorypointers")
out.println("%instructions")

View File

@ -58,10 +58,6 @@ enum class Opcode {
DIV_F,
REMAINDER_UB, // signed remainder is undefined/unimplemented
REMAINDER_UW, // signed remainder is undefined/unimplemented
POW_UB,
POW_B,
POW_UW,
POW_W,
POW_F,
NEG_B,
NEG_W,
@ -244,7 +240,6 @@ enum class Opcode {
JZW, // branch if value is zero (word)
JNZW, // branch if value is not zero (word)
// subroutines
CALL,
RETURN,
@ -257,6 +252,7 @@ enum class Opcode {
CLC, // clear carry status flag NOTE: is mostly fake, carry flag is not affected by any numeric operations
SEI, // set irq-disable status flag
CLI, // clear irq-disable status flag
CARRY_TO_A, // load var/register A with carry status bit
RSAVE, // save all internal registers and status flags
RSAVEX, // save just X (the evaluation stack pointer)
RRESTORE, // restore all internal registers and status flags
@ -266,7 +262,8 @@ enum class Opcode {
BREAKPOINT, // breakpoint
TERMINATE, // end the program
LINE, // track source file line number
INLINE_ASSEMBLY // container to hold inline raw assembly code
INLINE_ASSEMBLY, // container to hold inline raw assembly code
INCLUDE_FILE // directive to include a file at this position in the memory of the program
}
val opcodesWithVarArgument = setOf(

View File

@ -79,8 +79,8 @@ class Value(val type: DataType, numericvalueOrHeapId: Number) {
fun numericValue(): Number {
return when(type) {
DataType.UBYTE, DataType.BYTE -> byteval!!
DataType.UWORD, DataType.WORD -> wordval!!
in ByteDatatypes -> byteval!!
in WordDatatypes -> wordval!!
DataType.FLOAT -> floatval!!
else -> throw ValueException("invalid datatype for numeric value: $type")
}
@ -88,8 +88,8 @@ class Value(val type: DataType, numericvalueOrHeapId: Number) {
fun integerValue(): Int {
return when(type) {
DataType.UBYTE, DataType.BYTE -> byteval!!.toInt()
DataType.UWORD, DataType.WORD -> wordval!!
in ByteDatatypes -> byteval!!.toInt()
in WordDatatypes -> wordval!!
DataType.FLOAT -> throw ValueException("float to integer loss of precision")
else -> throw ValueException("invalid datatype for integer value: $type")
}
@ -393,8 +393,8 @@ class Value(val type: DataType, numericvalueOrHeapId: Number) {
fun msb(): Value {
return when(type) {
DataType.UBYTE, DataType.BYTE -> Value(DataType.UBYTE, 0)
DataType.UWORD, DataType.WORD -> Value(DataType.UBYTE, wordval!! ushr 8 and 255)
in ByteDatatypes -> Value(DataType.UBYTE, 0)
in WordDatatypes -> Value(DataType.UBYTE, wordval!! ushr 8 and 255)
else -> throw ValueException("msb can only work on (u)byte/(u)word")
}
}
@ -428,7 +428,7 @@ class Value(val type: DataType, numericvalueOrHeapId: Number) {
}
DataType.UWORD -> {
when (targetType) {
DataType.BYTE, DataType.UBYTE -> Value(DataType.UBYTE, integerValue() and 255)
in ByteDatatypes -> Value(DataType.UBYTE, integerValue() and 255)
DataType.UWORD -> this
DataType.WORD -> {
if(integerValue()<=32767)
@ -442,7 +442,7 @@ class Value(val type: DataType, numericvalueOrHeapId: Number) {
}
DataType.WORD -> {
when (targetType) {
DataType.BYTE, DataType.UBYTE -> Value(DataType.UBYTE, integerValue() and 255)
in ByteDatatypes -> Value(DataType.UBYTE, integerValue() and 255)
DataType.UWORD -> Value(DataType.UWORD, integerValue() and 65535)
DataType.WORD -> this
DataType.FLOAT -> Value(DataType.FLOAT, numericValue())

View File

@ -16,9 +16,6 @@ import kotlin.math.abs
class AssemblyError(msg: String) : RuntimeException(msg)
// TODO: code generation for POW instruction
class AsmGen(val options: CompilationOptions, val program: IntermediateProgram, val heap: HeapValues, val zeropage: Zeropage) {
private val globalFloatConsts = mutableMapOf<Double, String>()
private val assemblyLines = mutableListOf<String>()
@ -26,10 +23,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
private var breakpointCounter = 0
init {
// Because 64tass understands scoped names via .proc / .block,
// we'll strip the block prefix from all scoped names in the program.
// Also, convert invalid label names (such as "<<<anonymous-1>>>") to something that's allowed.
// Also have to do that for the variablesMarkedForZeropage!
// Convert invalid label names (such as "<anon-1>") to something that's allowed.
val newblocks = mutableListOf<IntermediateProgram.ProgramBlock>()
for(block in program.blocks) {
val newvars = block.variables.map { symname(it.key, block) to it.value }.toMap().toMutableMap()
@ -45,14 +39,13 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
callLabel2 = if (it.callLabel2 != null) symname(it.callLabel2, block) else null)
}
}.toMutableList()
val newConstants = block.memoryPointers.map { symname(it.key, block) to it.value }.toMap().toMutableMap()
val newMempointers = block.memoryPointers.map { symname(it.key, block) to it.value }.toMap().toMutableMap()
val newblock = IntermediateProgram.ProgramBlock(
block.scopedname,
block.shortname,
block.name,
block.address,
newinstructions,
newvars,
newConstants,
newMempointers,
newlabels,
force_output = block.force_output)
newblock.variablesMarkedForZeropage.clear()
@ -76,7 +69,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
}
}
fun compileToAssembly(): AssemblyProgram {
fun compileToAssembly(optimize: Boolean): AssemblyProgram {
println("Generating assembly code from intermediate code... ")
assemblyLines.clear()
@ -84,9 +77,11 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
for(b in program.blocks)
block2asm(b)
var optimizationsDone=1
while(optimizationsDone>0) {
optimizationsDone = optimizeAssembly(assemblyLines)
if(optimize) {
var optimizationsDone = 1
while (optimizationsDone > 0) {
optimizationsDone = optimizeAssembly(assemblyLines)
}
}
File("${program.name}.asm").printWriter().use {
@ -107,29 +102,20 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
}
// convert a fully scoped name (defined in the given block) to a valid assembly symbol name
private fun symname(scoped: String, block: IntermediateProgram.ProgramBlock?): String {
if(' ' in scoped)
return scoped
val blockLocal: Boolean
var name = when {
block==null -> {
blockLocal=true
scoped
}
scoped.startsWith("${block.shortname}.") -> {
blockLocal = true
scoped.substring(block.shortname.length+1)
}
scoped.startsWith("block.") -> {
blockLocal = false
scoped
}
else -> {
blockLocal = false
scoped
}
var name = if (block!=null && scoped.startsWith("${block.name}.")) {
blockLocal = true
scoped.substring(block.name.length+1)
}
name = name.replace("<<<", "prog8_").replace(">>>", "")
else {
blockLocal = false
scoped
}
name = name.replace("<", "prog8_").replace(">", "") // take care of the autogenerated invalid (anon) label names
if(name=="-")
return "-"
if(blockLocal)
@ -186,15 +172,32 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
}
}
if(zeropage.exitProgramStrategy!=Zeropage.ExitProgramStrategy.CLEAN_EXIT) {
// disable shift-commodore charset switching and run/stop key
out(" lda #$80")
out(" lda #$80")
out(" sta 657\t; disable charset switching")
out(" lda #239")
out(" sta 808\t; disable run/stop key")
}
out(" ldx #\$ff\t; init estack pointer")
out(" ; initialize the variables in each block")
for(block in program.blocks) {
val initVarsLabel = block.instructions.firstOrNull { it is LabelInstr && it.name==initvarsSubName } as? LabelInstr
if(initVarsLabel!=null)
out(" jsr ${block.scopedname}.${initVarsLabel.name}")
out(" jsr ${block.name}.${initVarsLabel.name}")
}
out(" clc")
out(" jmp main.start\t; jump to program entrypoint")
when(zeropage.exitProgramStrategy) {
Zeropage.ExitProgramStrategy.CLEAN_EXIT -> {
out(" jmp main.start\t; jump to program entrypoint")
}
Zeropage.ExitProgramStrategy.SYSTEM_RESET -> {
out(" jsr main.start\t; call program entrypoint")
out(" jmp (c64.RESET_VEC)\t; cold reset")
}
}
out("")
// the global list of all floating point constants for the whole program
@ -206,9 +209,9 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
private fun block2asm(blk: IntermediateProgram.ProgramBlock) {
block = blk
out("\n; ---- block: '${block.shortname}' ----")
out("\n; ---- block: '${block.name}' ----")
if(!blk.force_output)
out("${block.shortname}\t.proc\n")
out("${block.name}\t.proc\n")
if(block.address!=null) {
out(".cerror * > ${block.address?.toHex()}, 'block address overlaps by ', *-${block.address?.toHex()},' bytes'")
out("* = ${block.address?.toHex()}")
@ -216,14 +219,14 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
// deal with zeropage variables
for(variable in blk.variables) {
val sym = symname(blk.scopedname+"."+variable.key, null)
val sym = symname(blk.name+"."+variable.key, null)
val zpVar = program.allocatedZeropageVariables[sym]
if(zpVar==null) {
// This var is not on the ZP yet. Attempt to move it there (if it's not a float, those take up too much space)
if(variable.value.type in zeropage.allowedDatatypes && variable.value.type != DataType.FLOAT) {
try {
val address = zeropage.allocate(sym, variable.value.type, null)
out("${variable.key} = $address\t; zp ${variable.value.type}")
out("${variable.key} = $address\t; auto zp ${variable.value.type}")
// make sure we add the var to the set of zpvars for this block
blk.variablesMarkedForZeropage.add(variable.key)
program.allocatedZeropageVariables[sym] = Pair(address, variable.value.type)
@ -277,18 +280,15 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
DataType.UWORD -> out("${v.first}\t.word 0")
DataType.WORD -> out("${v.first}\t.sint 0")
DataType.FLOAT -> out("${v.first}\t.byte 0,0,0,0,0 ; float")
DataType.STR,
DataType.STR_P,
DataType.STR_S,
DataType.STR_PS -> {
DataType.STR, DataType.STR_S -> {
val rawStr = heap.get(v.second.heapId).str!!
val bytes = encodeStr(rawStr, v.second.type).map { "$" + it.toString(16).padStart(2, '0') }
out("${v.first}\t; ${v.second.type} \"${escape(rawStr)}\"")
out("${v.first}\t; ${v.second.type} \"${escape(rawStr).replace("\u0000", "<NULL>")}\"")
for (chunk in bytes.chunked(16))
out(" .byte " + chunk.joinToString())
}
DataType.ARRAY_UB -> {
// unsigned integer byte arrayspec
// unsigned integer byte arraysize
val data = makeArrayFillDataUnsigned(v.second)
if (data.size <= 16)
out("${v.first}\t.byte ${data.joinToString()}")
@ -299,7 +299,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
}
}
DataType.ARRAY_B -> {
// signed integer byte arrayspec
// signed integer byte arraysize
val data = makeArrayFillDataSigned(v.second)
if (data.size <= 16)
out("${v.first}\t.char ${data.joinToString()}")
@ -310,7 +310,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
}
}
DataType.ARRAY_UW -> {
// unsigned word arrayspec
// unsigned word arraysize
val data = makeArrayFillDataUnsigned(v.second)
if (data.size <= 16)
out("${v.first}\t.word ${data.joinToString()}")
@ -321,7 +321,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
}
}
DataType.ARRAY_W -> {
// signed word arrayspec
// signed word arraysize
val data = makeArrayFillDataSigned(v.second)
if (data.size <= 16)
out("${v.first}\t.sint ${data.joinToString()}")
@ -332,7 +332,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
}
}
DataType.ARRAY_F -> {
// float arrayspec
// float arraysize
val array = heap.get(v.second.heapId).doubleArray!!
val floatFills = array.map { makeFloatFill(Mflpt5.fromNumber(it)) }
out(v.first)
@ -344,24 +344,14 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
}
private fun encodeStr(str: String, dt: DataType): List<Short> {
when(dt) {
return when(dt) {
DataType.STR -> {
val bytes = Petscii.encodePetscii(str, true)
return bytes.plus(0)
}
DataType.STR_P -> {
val result = listOf(str.length.toShort())
val bytes = Petscii.encodePetscii(str, true)
return result.plus(bytes)
bytes.plus(0)
}
DataType.STR_S -> {
val bytes = Petscii.encodeScreencode(str, true)
return bytes.plus(0)
}
DataType.STR_PS -> {
val result = listOf(str.length.toShort())
val bytes = Petscii.encodeScreencode(str, true)
return result.plus(bytes)
bytes.plus(0)
}
else -> throw AssemblyError("invalid str type")
}
@ -369,23 +359,33 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
private fun makeArrayFillDataUnsigned(value: Value): List<String> {
val array = heap.get(value.heapId).array!!
return if (value.type == DataType.ARRAY_UB || value.type == DataType.ARRAY_UW)
array.map { "$"+it.toString(16).padStart(2, '0') }
else
throw AssemblyError("invalid arrayspec type")
return when {
value.type==DataType.ARRAY_UB ->
// byte array can never contain pointer-to types, so treat values as all integers
array.map { "$"+it.integer!!.toString(16).padStart(2, '0') }
value.type==DataType.ARRAY_UW -> array.map {
when {
it.integer!=null -> "$"+it.integer.toString(16).padStart(2, '0')
it.addressOf!=null -> symname(it.addressOf.scopedname!!, block)
else -> throw AssemblyError("weird type in array")
}
}
else -> throw AssemblyError("invalid arraysize type")
}
}
private fun makeArrayFillDataSigned(value: Value): List<String> {
val array = heap.get(value.heapId).array!!
// note: array of signed value can never contain pointer-to type, so simply process values as being all integers
return if (value.type == DataType.ARRAY_B || value.type == DataType.ARRAY_W) {
array.map {
if(it>=0)
"$"+it.toString(16).padStart(2, '0')
if(it.integer!!>=0)
"$"+it.integer.toString(16).padStart(2, '0')
else
"-$"+abs(it).toString(16).padStart(2, '0')
"-$"+abs(it.integer).toString(16).padStart(2, '0')
}
}
else throw AssemblyError("invalid arrayspec type")
else throw AssemblyError("invalid arraysize type")
}
private fun instr2asm(ins: List<Instruction>): Int {
@ -424,12 +424,9 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
private fun simpleInstr2Asm(ins: Instruction): String? {
// a label 'instruction' is simply translated into a asm label
if(ins is LabelInstr) {
if(ins.name.startsWith("block."))
return ""
val labelresult =
if(ins.name.startsWith("${block.shortname}."))
ins.name.substring(block.shortname.length+1)
if(ins.name.startsWith("${block.name}."))
ins.name.substring(block.name.length+1)
else
ins.name
return if(ins.asmProc) labelresult+"\t\t.proc" else labelresult
@ -446,6 +443,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
Opcode.CLC -> " clc"
Opcode.SEI -> " sei"
Opcode.CLI -> " cli"
Opcode.CARRY_TO_A -> " lda #0 | adc #0"
Opcode.JUMP -> {
if(ins.callLabel!=null)
" jmp ${ins.callLabel}"
@ -473,7 +471,12 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
Opcode.DISCARD_BYTE -> " inx"
Opcode.DISCARD_WORD -> " inx"
Opcode.DISCARD_FLOAT -> " inx | inx | inx"
Opcode.INLINE_ASSEMBLY -> "@inline@" + (ins.callLabel ?: "") // All of the inline assembly is stored in the calllabel property. the '@inline@' is a special marker to process it.
Opcode.INLINE_ASSEMBLY -> "@inline@" + (ins.callLabel2 ?: "") // All of the inline assembly is stored in the calllabel2 property. the '@inline@' is a special marker to process it.
Opcode.INCLUDE_FILE -> {
val offset = if(ins.arg==null) "" else ", ${ins.arg.integerValue()}"
val length = if(ins.arg2==null) "" else ", ${ins.arg2.integerValue()}"
" .binary \"${ins.callLabel}\" $offset $length"
}
Opcode.SYSCALL -> {
if (ins.arg!!.numericValue() in syscallsForStackVm.map { it.callNr })
throw CompilerException("cannot translate vm syscalls to real assembly calls - use *real* subroutine calls instead. Syscall ${ins.arg.numericValue()}")
@ -497,9 +500,9 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
Syscall.FUNC_ALL_F,
Syscall.FUNC_MAX_F,
Syscall.FUNC_MIN_F,
Syscall.FUNC_AVG_F,
Syscall.FUNC_SUM_F -> " jsr c64flt.${call.toString().toLowerCase()}"
else -> " jsr prog8_lib.${call.toString().toLowerCase()}"
Syscall.FUNC_SUM_F -> " jsr c64flt.${call.name.toLowerCase()}"
null -> ""
else -> " jsr prog8_lib.${call.name.toLowerCase()}"
}
}
Opcode.BREAKPOINT -> {
@ -740,6 +743,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
Opcode.ABS_B -> " jsr prog8_lib.abs_b"
Opcode.ABS_W -> " jsr prog8_lib.abs_w"
Opcode.ABS_F -> " jsr c64flt.abs_f"
Opcode.POW_F -> " jsr c64flt.pow_f"
Opcode.INV_BYTE -> {
"""
lda ${(ESTACK_LO + 1).toHex()},x
@ -870,30 +874,20 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
Opcode.IDIV_W -> " jsr prog8_lib.idiv_w"
Opcode.IDIV_UW -> " jsr prog8_lib.idiv_uw"
Opcode.AND_BYTE -> {
"""
lda ${(ESTACK_LO + 2).toHex()},x
and ${(ESTACK_LO + 1).toHex()},x
inx
sta ${(ESTACK_LO + 1).toHex()},x
"""
}
Opcode.OR_BYTE -> {
"""
lda ${(ESTACK_LO + 2).toHex()},x
ora ${(ESTACK_LO + 1).toHex()},x
inx
sta ${(ESTACK_LO + 1).toHex()},x
"""
}
Opcode.XOR_BYTE -> {
"""
lda ${(ESTACK_LO + 2).toHex()},x
eor ${(ESTACK_LO + 1).toHex()},x
inx
sta ${(ESTACK_LO + 1).toHex()},x
"""
}
Opcode.AND_BYTE -> " jsr prog8_lib.and_b"
Opcode.OR_BYTE -> " jsr prog8_lib.or_b"
Opcode.XOR_BYTE -> " jsr prog8_lib.xor_b"
Opcode.AND_WORD -> " jsr prog8_lib.and_w"
Opcode.OR_WORD -> " jsr prog8_lib.or_w"
Opcode.XOR_WORD -> " jsr prog8_lib.xor_w"
Opcode.BITAND_BYTE -> " jsr prog8_lib.bitand_b"
Opcode.BITOR_BYTE -> " jsr prog8_lib.bitor_b"
Opcode.BITXOR_BYTE -> " jsr prog8_lib.bitxor_b"
Opcode.BITAND_WORD -> " jsr prog8_lib.bitand_w"
Opcode.BITOR_WORD -> " jsr prog8_lib.bitor_w"
Opcode.BITXOR_WORD -> " jsr prog8_lib.bitxor_w"
Opcode.REMAINDER_UB -> " jsr prog8_lib.remainder_ub"
Opcode.REMAINDER_UW -> " jsr prog8_lib.remainder_uw"
@ -943,23 +937,21 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
if(mulIns.opcode == Opcode.MUL_B || mulIns.opcode==Opcode.MUL_UB) {
if(amount in setOf(0,1,2,4,8,16,32,64,128,256))
throw AssemblyError("multiplication by power of 2 should have been converted into a left shift instruction already")
printWarning("multiplication by power of 2 should have been optimized into a left shift instruction: $mulIns $amount")
if(amount in setOf(3,5,6,7,9,10,11,12,13,14,15,20,25,40))
return " jsr math.mul_byte_$amount"
if(mulIns.opcode == Opcode.MUL_B && amount in setOf(-3,-5,-6,-7,-9,-10,-11,-12,-13,-14,-15,-20,-25,-40))
return " jsr prog8_lib.neg_b | jsr math.mul_byte_${-amount}"
}
else if(mulIns.opcode == Opcode.MUL_UW) {
if(amount in setOf(0,1,2,4,8,16,32,64,128,256))
throw AssemblyError("multiplication by power of 2 should have been converted into a left shift instruction already")
printWarning("multiplication by power of 2 should have been optimized into a left shift instruction: $mulIns $amount")
if(amount in setOf(3,5,6,7,9,10,12,15,20,25,40))
return " jsr math.mul_word_$amount"
}
else if(mulIns.opcode == Opcode.MUL_W) {
if(amount in setOf(0,1,2,4,8,16,32,64,128,256))
throw AssemblyError("multiplication by power of 2 should have been converted into a left shift instruction already")
printWarning("multiplication by power of 2 should have been optimized into a left shift instruction: $mulIns $amount")
if(amount in setOf(3,5,6,7,9,10,12,15,20,25,40))
return " jsr math.mul_word_$amount"
if(amount in setOf(-3,-5,-6,-7,-9,-10,-12,-15,-20,-25,-40))
@ -1261,9 +1253,6 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
Opcode.ROR2_WORD -> {
AsmFragment(" lsr $variable+1 | ror $variable | bcc + | lda $variable+1 | ora #\$80 | sta $variable+1 |+", 30)
}
// Opcode.SYSCALL -> {
// TODO("optimize SYSCALL $ins in-place on variable $variable")
// }
else -> null
}
}
@ -3310,44 +3299,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
null
},
// 16 bit addition avoiding excessive stack usage
// @todo optimize 8 and 16 bit adds and subs even more with longer asmpatterns (avoid stack use altogether on most common operations)
AsmPattern(listOf(Opcode.PUSH_VAR_WORD, Opcode.ADD_UW),
listOf(Opcode.PUSH_VAR_WORD, Opcode.ADD_W)) { segment ->
"""
clc
lda ${segment[0].callLabel}
adc ${(ESTACK_LO+1).toHex()},x
sta ${(ESTACK_LO+1).toHex()},x
lda ${segment[0].callLabel}+1
adc ${(ESTACK_HI+1).toHex()},x
sta ${(ESTACK_HI+1).toHex()},x
"""
},
AsmPattern(listOf(Opcode.PUSH_MEM_UW, Opcode.ADD_UW),
listOf(Opcode.PUSH_MEM_W, Opcode.ADD_W)) { segment ->
"""
clc
lda ${hexVal(segment[0])}
adc ${(ESTACK_LO + 1).toHex()},x
sta ${(ESTACK_LO + 1).toHex()},x
lda ${hexValPlusOne(segment[0])}
adc ${(ESTACK_HI + 1).toHex()},x
sta ${(ESTACK_HI + 1).toHex()},x
"""
},
AsmPattern(listOf(Opcode.PUSH_WORD, Opcode.ADD_UW),
listOf(Opcode.PUSH_WORD, Opcode.ADD_W)) { segment ->
"""
clc
lda #<${hexVal(segment[0])}
adc ${(ESTACK_LO+1).toHex()},x
sta ${(ESTACK_LO+1).toHex()},x
lda #>${hexVal(segment[0])}
adc ${(ESTACK_HI+1).toHex()},x
sta ${(ESTACK_HI+1).toHex()},x
"""
},
// @todo optimize 8 and 16 bit adds and subs (avoid stack use altogether on most common operations)
AsmPattern(listOf(Opcode.PUSH_VAR_BYTE, Opcode.CMP_B), listOf(Opcode.PUSH_VAR_BYTE, Opcode.CMP_UB)) { segment ->
// this pattern is encountered as part of the loop bound condition in for loops (var + cmp + jz/jnz)

View File

@ -29,27 +29,56 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
const val SCRATCH_B1 = 0x02
const val SCRATCH_REG = 0x03 // temp storage for a register
const val SCRATCH_REG_X = 0xfa // temp storage for register X (the evaluation stack pointer)
const val SCRATCH_W1 = 0xfb // $fb/$fc
const val SCRATCH_W2 = 0xfd // $fd/$fe
const val SCRATCH_W1 = 0xfb // $fb+$fc
const val SCRATCH_W2 = 0xfd // $fd+$fe
}
override val exitProgramStrategy: ExitProgramStrategy = when(options.zeropage) {
ZeropageType.BASICSAFE -> ExitProgramStrategy.CLEAN_EXIT
ZeropageType.FLOATSAFE, ZeropageType.KERNALSAFE, ZeropageType.FULL -> ExitProgramStrategy.SYSTEM_RESET
}
init {
if(options.zeropage== ZeropageType.FULL) {
if(options.floats && options.zeropage!=ZeropageType.FLOATSAFE && options.zeropage!=ZeropageType.BASICSAFE)
throw CompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe'")
if(options.zeropage == ZeropageType.FULL) {
free.addAll(0x04 .. 0xf9)
free.add(0xff)
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_REG_X, SCRATCH_W1, SCRATCH_W1+1, SCRATCH_W2, SCRATCH_W2+1))
free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ
} else {
if(options.zeropage== ZeropageType.KERNALSAFE) {
// add the Zp addresses that are just used by BASIC routines to the free list
free.addAll(listOf(0x09, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
if(options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
free.addAll(listOf(0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
0x22, 0x23, 0x24, 0x25,
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53, 0x6f, 0x70))
0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53,
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c,
0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
// 0x90-0xfa is 'kernel work storage area'
))
}
// add the other free Zp addresses
// these are valid for the C-64 (when no RS232 I/O is performed):
free.addAll(listOf(0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0d, 0x0e,
if(options.zeropage == ZeropageType.FLOATSAFE) {
// remove the zero page locations used for floating point operations from the free list
free.removeAll(listOf(
0x12, 0x26, 0x27, 0x28, 0x29, 0x2a,
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xf
))
}
// add the other free Zp addresses,
// these are valid for the C-64 (when no RS232 I/O is performed) but to keep BASIC running fully:
free.addAll(listOf(0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0d, 0x0e,
0x94, 0x95, 0xa7, 0xa8, 0xa9, 0xaa,
0xb5, 0xb6, 0xf7, 0xf8, 0xf9))
}

View File

@ -1055,11 +1055,12 @@ class Petscii {
val lookup = if(lowercase) encodingPetsciiLowercase else encodingPetsciiUppercase
return text.map {
val petscii = lookup[it]
if(petscii==null) {
val case = if(lowercase) "lower" else "upper"
petscii?.toShort() ?: if(it=='\u0000')
0.toShort()
else {
val case = if (lowercase) "lower" else "upper"
throw CharConversionException("no ${case}case Petscii character for '$it'")
}
petscii.toShort()
}
}
@ -1072,11 +1073,12 @@ class Petscii {
val lookup = if(lowercase) encodingScreencodeLowercase else encodingScreencodeUppercase
return text.map{
val screencode = lookup[it]
if(screencode==null) {
val case = if(lowercase) "lower" else "upper"
screencode?.toShort() ?: if(it=='\u0000')
0.toShort()
else {
val case = if (lowercase) "lower" else "upper"
throw CharConversionException("no ${case}Screencode character for '$it'")
}
screencode.toShort()
}
}

View File

@ -26,10 +26,11 @@ val BuiltinFunctions = mapOf(
"lsl" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null),
"lsr" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null),
// these few have a return value depending on the argument(s):
"max" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, n, h -> collectionArgOutputNumber(a, p, n, h) { it.max()!! }}, // type depends on args
"min" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, n, h -> collectionArgOutputNumber(a, p, n, h) { it.min()!! }}, // type depends on args
"sum" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, n, h -> collectionArgOutputNumber(a, p, n, h) { it.sum() }}, // type depends on args
"max" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, n, h -> collectionArgOutputNumber(a, p, n, h) { it.max()!! }}, // type depends on args
"min" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, n, h -> collectionArgOutputNumber(a, p, n, h) { it.min()!! }}, // type depends on args
"sum" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, n, h -> collectionArgOutputNumber(a, p, n, h) { it.sum() }}, // type depends on args
"abs" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
"len" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
// normal functions follow:
"sin" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::sin) },
"sin8" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
@ -45,7 +46,7 @@ val BuiltinFunctions = mapOf(
"atan" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::atan) },
"ln" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::log) },
"log2" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, ::log2) },
// TODO: sqrt() should have integer versions too
"sqrt16" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, n, h -> oneIntArgOutputInt(a, p, n, h) { Math.sqrt(it.toDouble()).toInt() } },
"sqrt" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::sqrt) },
"rad" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::toRadians) },
"deg" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::toDegrees) },
@ -53,7 +54,6 @@ val BuiltinFunctions = mapOf(
"round" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArgOutputWord(a, p, n, h, Math::round) },
"floor" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArgOutputWord(a, p, n, h, Math::floor) },
"ceil" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArgOutputWord(a, p, n, h, Math::ceil) },
"len" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", IterableDatatypes)), DataType.UWORD, ::builtinLen),
"any" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, n, h -> collectionArgOutputBoolean(a, p, n, h) { it.any { v -> v != 0.0} }},
"all" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, n, h -> collectionArgOutputBoolean(a, p, n, h) { it.all { v -> v != 0.0} }},
"lsb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, n, h -> oneIntArgOutputInt(a, p, n, h) { x: Int -> x and 255 }},
@ -70,19 +70,21 @@ val BuiltinFunctions = mapOf(
"clear_carry" to FunctionSignature(false, emptyList(), null),
"set_irqd" to FunctionSignature(false, emptyList(), null),
"clear_irqd" to FunctionSignature(false, emptyList(), null),
"read_flags" to FunctionSignature(false, emptyList(), DataType.UBYTE),
"swap" to FunctionSignature(false, listOf(BuiltinFunctionParam("first", NumericDatatypes), BuiltinFunctionParam("second", NumericDatatypes)), null),
"memcopy" to FunctionSignature(false, listOf(
BuiltinFunctionParam("from", IterableDatatypes + setOf(DataType.UWORD)),
BuiltinFunctionParam("to", IterableDatatypes + setOf(DataType.UWORD)),
BuiltinFunctionParam("numbytes", IntegerDatatypes)), null),
BuiltinFunctionParam("numbytes", setOf(DataType.UBYTE))), null),
"memset" to FunctionSignature(false, listOf(
BuiltinFunctionParam("address", IterableDatatypes + setOf(DataType.UWORD)),
BuiltinFunctionParam("numbytes", setOf(DataType.UWORD)),
BuiltinFunctionParam("bytevalue", setOf(DataType.UBYTE, DataType.BYTE))), null),
BuiltinFunctionParam("bytevalue", ByteDatatypes)), null),
"memsetw" to FunctionSignature(false, listOf(
BuiltinFunctionParam("address", IterableDatatypes + setOf(DataType.UWORD)),
BuiltinFunctionParam("numwords", setOf(DataType.UWORD)),
BuiltinFunctionParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null),
"strlen" to FunctionSignature(true, listOf(BuiltinFunctionParam("string", StringDatatypes)), DataType.UBYTE, ::builtinStrlen),
"vm_write_memchr" to FunctionSignature(false, listOf(BuiltinFunctionParam("address", setOf(DataType.UWORD))), null),
"vm_write_memstr" to FunctionSignature(false, listOf(BuiltinFunctionParam("address", setOf(DataType.UWORD))), null),
"vm_write_num" to FunctionSignature(false, listOf(BuiltinFunctionParam("number", NumericDatatypes)), null),
@ -111,12 +113,12 @@ val BuiltinFunctions = mapOf(
fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespace: INameScope, heap: HeapValues): DataType? {
fun datatypeFromListArg(arglist: IExpression): DataType {
fun datatypeFromIterableArg(arglist: IExpression): DataType {
if(arglist is LiteralValue) {
if(arglist.type==DataType.ARRAY_UB || arglist.type==DataType.ARRAY_UW || arglist.type==DataType.ARRAY_F) {
val dt = arglist.arrayvalue!!.map {it.resultingDatatype(namespace, heap)}
if(dt.any { it!=DataType.UBYTE && it!=DataType.UWORD && it!=DataType.FLOAT}) {
throw FatalAstException("fuction $function only accepts arrayspec of numeric values")
throw FatalAstException("fuction $function only accepts arraysize of numeric values")
}
if(dt.any { it==DataType.FLOAT }) return DataType.FLOAT
if(dt.any { it==DataType.UWORD }) return DataType.UWORD
@ -126,17 +128,17 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespa
if(arglist is IdentifierReference) {
val dt = arglist.resultingDatatype(namespace, heap)
return when(dt) {
DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT,
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> dt
in NumericDatatypes -> dt!!
in StringDatatypes -> dt!!
DataType.ARRAY_UB -> DataType.UBYTE
DataType.ARRAY_B -> DataType.BYTE
DataType.ARRAY_UW -> DataType.UWORD
DataType.ARRAY_W -> DataType.WORD
DataType.ARRAY_F -> DataType.FLOAT
null -> throw FatalAstException("function requires one argument which is an arrayspec $function")
else -> throw FatalAstException("function '$function' requires one argument which is an iterable")
}
}
throw FatalAstException("function requires one argument which is an arrayspec $function")
throw FatalAstException("function '$function' requires one argument which is an iterable")
}
val func = BuiltinFunctions.getValue(function)
@ -145,20 +147,30 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespa
// function has return values, but the return type depends on the arguments
return when (function) {
"max", "min", "abs" -> {
val dt = datatypeFromListArg(args.single())
"abs" -> {
val dt = args.single().resultingDatatype(namespace, heap)
when(dt) {
DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT -> dt
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> DataType.UBYTE
in ByteDatatypes -> DataType.UBYTE
in WordDatatypes -> DataType.UWORD
DataType.FLOAT -> DataType.FLOAT
else -> throw FatalAstException("weird datatype passed to abs $dt")
}
}
"max", "min" -> {
val dt = datatypeFromIterableArg(args.single())
when(dt) {
in NumericDatatypes -> dt
in StringDatatypes -> DataType.UBYTE
DataType.ARRAY_UB -> DataType.UBYTE
DataType.ARRAY_B -> DataType.BYTE
DataType.ARRAY_UW -> DataType.UWORD
DataType.ARRAY_W -> DataType.WORD
DataType.ARRAY_F -> DataType.FLOAT
else -> null
}
}
"sum" -> {
val dt=datatypeFromListArg(args.single())
val dt=datatypeFromIterableArg(args.single())
when(dt) {
DataType.UBYTE, DataType.UWORD -> DataType.UWORD
DataType.BYTE, DataType.WORD -> DataType.WORD
@ -166,9 +178,15 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespa
DataType.ARRAY_UB, DataType.ARRAY_UW -> DataType.UWORD
DataType.ARRAY_B, DataType.ARRAY_W -> DataType.WORD
DataType.ARRAY_F -> DataType.FLOAT
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> DataType.UWORD
in StringDatatypes -> DataType.UWORD
else -> null
}
}
"len" -> {
// a length can be >255 so in that case, the result is an UWORD instead of an UBYTE
// but to avoid a lot of code duplication we simply assume UWORD in all cases for now
return DataType.UWORD
}
else -> return null
}
}
@ -227,7 +245,12 @@ private fun collectionArgOutputNumber(args: List<IExpression>, position: Positio
if(iterable.heapId==null)
throw FatalAstException("iterable value should be on the heap")
val array = heap.get(iterable.heapId).array ?: throw SyntaxError("function expects an iterable type", position)
function(array.map { it.toDouble() })
function(array.map {
if(it.integer!=null)
it.integer.toDouble()
else
throw FatalAstException("cannot perform function over array that contains other values besides constant integers")
})
}
}
}
@ -248,7 +271,12 @@ private fun collectionArgOutputBoolean(args: List<IExpression>, position: Positi
function(constants.map { it!!.toDouble() })
} else {
val array = heap.get(iterable.heapId!!).array ?: throw SyntaxError("function requires array argument", position)
function(array.map { it.toDouble() })
function(array.map {
if(it.integer!=null)
it.integer.toDouble()
else
throw FatalAstException("cannot perform function over array that contains other values besides constant integers")
})
}
return LiteralValue.fromBoolean(result, position)
}
@ -271,7 +299,6 @@ private fun builtinAvg(args: List<IExpression>, position: Position, namespace:IN
if(args.size!=1)
throw SyntaxError("avg requires array argument", position)
val iterable = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
val result = if(iterable.arrayvalue!=null) {
val constants = iterable.arrayvalue.map { it.constValue(namespace, heap)?.asNumericValue }
if (null in constants)
@ -279,20 +306,47 @@ private fun builtinAvg(args: List<IExpression>, position: Position, namespace:IN
(constants.map { it!!.toDouble() }).average()
}
else {
val array = heap.get(iterable.heapId!!).array ?: throw SyntaxError("avg requires array argument", position)
array.average()
val integerarray = heap.get(iterable.heapId!!).array
if(integerarray!=null) {
if (integerarray.all { it.integer != null }) {
integerarray.map { it.integer!! }.average()
} else {
throw ExpressionError("cannot avg() over array that does not only contain constant numerical values", position)
}
} else {
val doublearray = heap.get(iterable.heapId).doubleArray
doublearray?.average() ?: throw SyntaxError("avg requires array argument", position)
}
}
return numericLiteral(result, args[0].position)
}
private fun builtinStrlen(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
if (args.size != 1)
throw SyntaxError("strlen requires one argument", position)
val argument = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
if(argument.type !in StringDatatypes)
throw SyntaxError("strlen must have string argument", position)
val string = argument.strvalue(heap)
val zeroIdx = string.indexOf('\u0000')
return if(zeroIdx>=0)
LiteralValue.optimalInteger(zeroIdx, position=position)
else
LiteralValue.optimalInteger(string.length, position=position)
}
private fun builtinLen(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
// note: in some cases the length is > 255 and so we have to return a UWORD type instead of a byte.
// note: in some cases the length is > 255 and then we have to return a UWORD type instead of a UBYTE.
if(args.size!=1)
throw SyntaxError("len requires one argument", position)
var argument = args[0].constValue(namespace, heap)
if(argument==null) {
val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(namespace)
val arraySize = directMemVar?.arraysize?.size()
if(arraySize != null)
return LiteralValue.optimalInteger(arraySize, position)
if(args[0] !is IdentifierReference)
throw SyntaxError("len over weird argument ${args[0]}", position)
throw SyntaxError("len argument should be an identifier, but is ${args[0]}", position)
val target = (args[0] as IdentifierReference).targetStatement(namespace)
val argValue = (target as? VarDecl)?.value
argument = argValue?.constValue(namespace, heap)
@ -303,23 +357,22 @@ private fun builtinLen(args: List<IExpression>, position: Position, namespace:IN
val arraySize = argument.arrayvalue?.size ?: heap.get(argument.heapId!!).arraysize
if(arraySize>256)
throw CompilerException("array length exceeds byte limit ${argument.position}")
LiteralValue(DataType.UWORD, wordvalue=arraySize, position=args[0].position)
LiteralValue.optimalInteger(arraySize, args[0].position)
}
DataType.ARRAY_F -> {
val arraySize = argument.arrayvalue?.size ?: heap.get(argument.heapId!!).arraysize
if(arraySize>256)
throw CompilerException("array length exceeds byte limit ${argument.position}")
LiteralValue(DataType.UWORD, wordvalue=arraySize, position=args[0].position)
LiteralValue.optimalInteger(arraySize, args[0].position)
}
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
in StringDatatypes -> {
val str = argument.strvalue(heap)
if(str.length>255)
throw CompilerException("string length exceeds byte limit ${argument.position}")
LiteralValue(DataType.UWORD, wordvalue=str.length, position=args[0].position)
LiteralValue.optimalInteger(str.length, args[0].position)
}
DataType.UBYTE, DataType.BYTE,
DataType.UWORD, DataType.WORD,
DataType.FLOAT -> throw SyntaxError("len of weird argument ${args[0]}", position)
in NumericDatatypes -> throw SyntaxError("len of weird argument ${args[0]}", position)
else -> throw CompilerException("weird datatype")
}
}

View File

@ -3,6 +3,7 @@ package prog8.optimizing
import prog8.ast.*
import prog8.compiler.CompilerException
import prog8.compiler.HeapValues
import prog8.compiler.IntegerOrAddressOf
import prog8.compiler.target.c64.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.FLOAT_MAX_POSITIVE
import kotlin.math.floor
@ -24,7 +25,7 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
override fun process(decl: VarDecl): IStatement {
// the initializer value can't refer to the variable itself (recursive definition)
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arrayspec?.x?.referencesIdentifier(decl.name) == true) {
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.index?.referencesIdentifier(decl.name) == true) {
errors.add(ExpressionError("recursive var declaration", decl.position))
return decl
}
@ -33,7 +34,7 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
if(decl.type==VarDeclType.CONST || decl.type==VarDeclType.VAR) {
val litval = decl.value as? LiteralValue
if(litval!=null && litval.isArray)
if(litval!=null && litval.isArray && litval.heapId!=null)
fixupArrayTypeOnHeap(decl, litval)
when(decl.datatype) {
DataType.FLOAT -> {
@ -44,11 +45,34 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
}
}
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
val rangeExpr = decl.value as? RangeExpr
if(rangeExpr!=null) {
// convert the initializer range expression to an actual array (will be put on heap later)
val declArraySize = decl.arraysize?.size()
if(declArraySize!=null && declArraySize!=rangeExpr.size(heap))
errors.add(ExpressionError("range expression size doesn't match declared array size", decl.value?.position!!))
val constRange = rangeExpr.toConstantIntegerRange(heap)
if(constRange!=null) {
val eltType = rangeExpr.resultingDatatype(namespace, heap)!!
if(eltType in ByteDatatypes) {
decl.value = LiteralValue(decl.datatype,
arrayvalue = constRange.map { LiteralValue(eltType, bytevalue=it.toShort(), position = decl.value!!.position ) }
.toTypedArray(), position=decl.value!!.position)
} else {
decl.value = LiteralValue(decl.datatype,
arrayvalue = constRange.map { LiteralValue(eltType, wordvalue= it, position = decl.value!!.position ) }
.toTypedArray(), position=decl.value!!.position)
}
decl.value!!.linkParents(decl)
}
}
if(litval?.type==DataType.FLOAT)
errors.add(ExpressionError("arrayspec requires only integers here", litval.position))
val size = decl.arrayspec!!.size()
if ((litval==null || !litval.isArray) && size != null) {
// arrayspec initializer is empty or a single int, and we know the size; create the arrayspec.
errors.add(ExpressionError("arraysize requires only integers here", litval.position))
if(decl.arraysize==null)
return decl
val size = decl.arraysize!!.size()
if ((litval==null || !litval.isArray) && size != null && rangeExpr==null) {
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
val fillvalue = if (litval == null) 0 else litval.asIntegerValue ?: 0
when(decl.datatype){
DataType.ARRAY_UB -> {
@ -69,22 +93,22 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
}
else -> {}
}
val fillArray = IntArray(size) { fillvalue }
val heapId = heap.add(decl.datatype, fillArray)
val heapId = heap.addIntegerArray(decl.datatype, Array(size) { IntegerOrAddressOf(fillvalue, null) })
decl.value = LiteralValue(decl.datatype, heapId = heapId, position = litval?.position ?: decl.position)
}
}
DataType.ARRAY_F -> {
val size = decl.arrayspec!!.size()
if(decl.arraysize==null)
return decl
val size = decl.arraysize!!.size()
if ((litval==null || !litval.isArray) && size != null) {
// arrayspec initializer is empty or a single int, and we know the size; create the arrayspec.
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
val fillvalue = if (litval == null) 0.0 else litval.asNumericValue?.toDouble() ?: 0.0
if(fillvalue< FLOAT_MAX_NEGATIVE || fillvalue> FLOAT_MAX_POSITIVE)
errors.add(ExpressionError("float value overflow", litval?.position ?: decl.position))
else {
val fillArray = DoubleArray(size) { fillvalue }
val heapId = heap.add(decl.datatype, fillArray)
decl.value = LiteralValue(decl.datatype, heapId = heapId, position = litval?.position ?: decl.position)
val heapId = heap.addDoublesArray(DoubleArray(size) { fillvalue })
decl.value = LiteralValue(DataType.ARRAY_F, heapId = heapId, position = litval?.position ?: decl.position)
}
}
}
@ -97,9 +121,10 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
private fun fixupArrayTypeOnHeap(decl: VarDecl, litval: LiteralValue) {
// fix the type of the array value that's on the heap, to match the vardecl.
// notice that checking the bounds of the actual values is not done here, but in the AstChecker later.
if(decl.datatype==litval.type)
return // already correct datatype
val heapId = litval.heapId!!
val heapId = litval.heapId ?: throw FatalAstException("expected array to be on heap $litval")
val array=heap.get(heapId)
when(decl.datatype) {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
@ -111,12 +136,12 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
DataType.ARRAY_F -> {
if(array.array!=null) {
// convert a non-float array to floats
val doubleArray = array.array.map { it.toDouble() }.toDoubleArray()
val doubleArray = array.array.map { it.integer!!.toDouble() }.toDoubleArray()
heap.update(heapId, HeapValues.HeapValue(DataType.ARRAY_F, null, null, doubleArray))
decl.value = LiteralValue(decl.datatype, heapId = heapId, position = litval.position)
}
}
else -> throw AstException("invalid array vardecl type")
else -> throw FatalAstException("invalid array vardecl type ${decl.datatype}")
}
}
@ -141,6 +166,7 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
override fun process(functionCall: FunctionCall): IExpression {
return try {
super.process(functionCall)
typeCastConstArguments(functionCall)
functionCall.constValue(namespace, heap) ?: functionCall
} catch (ax: AstException) {
addError(ax)
@ -148,6 +174,46 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
}
}
override fun process(functionCallStatement: FunctionCallStatement): IStatement {
super.process(functionCallStatement)
typeCastConstArguments(functionCallStatement)
return functionCallStatement
}
private fun typeCastConstArguments(functionCall: IFunctionCall) {
val subroutine = functionCall.target.targetSubroutine(namespace)
if(subroutine!=null) {
// if types differ, try to typecast constant arguments to the function call to the desired data type of the parameter
for(arg in functionCall.arglist.withIndex().zip(subroutine.parameters)) {
val expectedDt = arg.second.type
val argConst = arg.first.value.constValue(namespace, heap)
if(argConst!=null && argConst.type!=expectedDt) {
val convertedValue = argConst.intoDatatype(expectedDt)
if(convertedValue!=null) {
functionCall.arglist[arg.first.index] = convertedValue
optimizationsDone++
}
}
}
}
}
override fun process(memread: DirectMemoryRead): IExpression {
// @( &thing ) --> thing
val addrOf = memread.addressExpression as? AddressOf
if(addrOf!=null)
return super.process(addrOf.identifier)
return super.process(memread)
}
override fun process(memwrite: DirectMemoryWrite): IExpression {
// @( &thing ) --> thing
val addrOf = memwrite.addressExpression as? AddressOf
if(addrOf!=null)
return super.process(addrOf.identifier)
return super.process(memwrite)
}
/**
* Try to process a unary prefix expression.
* Compile-time constant sub expressions will be evaluated on the spot.
@ -467,7 +533,7 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
val rangeTo = iterableRange.to as? LiteralValue
if(rangeFrom==null || rangeTo==null) return resultStmt
val loopvar = resultStmt.loopVar!!.targetStatement(namespace) as? VarDecl
val loopvar = resultStmt.loopVar!!.targetVarDecl(namespace)
if(loopvar!=null) {
val stepLiteral = iterableRange.step as? LiteralValue
when(loopvar.datatype) {
@ -507,7 +573,7 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
if(literalValue.strvalue(heap).length !in 1..255)
addError(ExpressionError("string literal length must be between 1 and 255", literalValue.position))
else {
val heapId = heap.add(literalValue.type, literalValue.strvalue(heap)) // TODO: we don't know the actual string type yet, STR != STR_P etc...
val heapId = heap.addString(literalValue.type, literalValue.strvalue(heap)) // TODO: we don't know the actual string type yet, STR != STR_S etc...
val newValue = LiteralValue(literalValue.type, heapId = heapId, position = literalValue.position)
return super.process(newValue)
}
@ -520,13 +586,25 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
private fun moveArrayToHeap(arraylit: LiteralValue): LiteralValue {
val array: Array<IExpression> = arraylit.arrayvalue!!.map { it.process(this) }.toTypedArray()
val allElementsAreConstant = array.fold(true) { c, expr-> c and (expr is LiteralValue)}
if(!allElementsAreConstant) {
addError(ExpressionError("array literal can contain only constant values", arraylit.position))
val allElementsAreConstantOrAddressOf = array.fold(true) { c, expr-> c and (expr is LiteralValue || expr is AddressOf)}
if(!allElementsAreConstantOrAddressOf) {
addError(ExpressionError("array literal can only consist of constant primitive numerical values or memory pointers", arraylit.position))
return arraylit
} else if(array.any {it is AddressOf}) {
val arrayDt = DataType.ARRAY_UW
val intArrayWithAddressOfs = array.map {
when (it) {
is AddressOf -> IntegerOrAddressOf(null, it)
is LiteralValue -> IntegerOrAddressOf(it.asIntegerValue, null)
else -> throw CompilerException("invalid datatype in array")
}
}
val heapId = heap.addIntegerArray(arrayDt, intArrayWithAddressOfs.toTypedArray())
return LiteralValue(arrayDt, heapId = heapId, position = arraylit.position)
} else {
// array is only constant numerical values
val valuesInArray = array.map { it.constValue(namespace, heap)!!.asNumericValue!! }
val integerArray = valuesInArray.map{it.toInt()}.toIntArray()
val integerArray = valuesInArray.map{ it.toInt() }
val doubleArray = valuesInArray.map{it.toDouble()}.toDoubleArray()
val typesInArray: Set<DataType> = array.mapNotNull { it.resultingDatatype(namespace, heap) }.toSet()
@ -560,9 +638,9 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
DataType.ARRAY_UB,
DataType.ARRAY_B,
DataType.ARRAY_UW,
DataType.ARRAY_W -> heap.add(arrayDt, integerArray)
DataType.ARRAY_F -> heap.add(arrayDt, doubleArray)
else -> throw CompilerException("invalid arrayspec type")
DataType.ARRAY_W -> heap.addIntegerArray(arrayDt, integerArray.map { IntegerOrAddressOf(it, null) }.toTypedArray())
DataType.ARRAY_F -> heap.addDoublesArray(doubleArray)
else -> throw CompilerException("invalid arraysize type")
}
return LiteralValue(arrayDt, heapId = heapId, position = arraylit.position)
}
@ -635,5 +713,3 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
return assignment
}
}

View File

@ -18,6 +18,22 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
return super.process(assignment)
}
override fun process(memread: DirectMemoryRead): IExpression {
// @( &thing ) --> thing
val addrOf = memread.addressExpression as? AddressOf
if(addrOf!=null)
return super.process(addrOf.identifier)
return super.process(memread)
}
override fun process(memwrite: DirectMemoryWrite): IExpression {
// @( &thing ) --> thing
val addrOf = memwrite.addressExpression as? AddressOf
if(addrOf!=null)
return super.process(addrOf.identifier)
return super.process(memwrite)
}
override fun process(expr: PrefixExpression): IExpression {
if (expr.operator == "+") {
// +X --> X
@ -75,7 +91,7 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
val leftDt = expr.left.resultingDatatype(namespace, heap)
val rightDt = expr.right.resultingDatatype(namespace, heap)
if (leftDt != null && rightDt != null && leftDt != rightDt) {
// try to convert a datatype into the other
// try to convert a datatype into the other (where ddd
if (adjustDatatypes(expr, leftVal, leftDt, rightVal, rightDt)) {
optimizationsDone++
return expr
@ -347,26 +363,37 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
}
if(leftConstVal==null && rightConstVal!=null) {
val (adjusted, newValue) = adjust(rightConstVal, leftDt)
if(adjusted) {
expr.right = newValue
optimizationsDone++
return true
if(isBiggerType(leftDt, rightDt)) {
val (adjusted, newValue) = adjust(rightConstVal, leftDt)
if (adjusted) {
expr.right = newValue
optimizationsDone++
return true
}
}
return false
} else if(leftConstVal!=null && rightConstVal==null) {
val (adjusted, newValue) = adjust(leftConstVal, rightDt)
if(adjusted) {
expr.left = newValue
optimizationsDone++
return true
if(isBiggerType(rightDt, leftDt)) {
val (adjusted, newValue) = adjust(leftConstVal, rightDt)
if (adjusted) {
expr.left = newValue
optimizationsDone++
return true
}
}
return false
} else {
return false
return false // two const values, don't adjust (should have been const-folded away)
}
}
private fun isBiggerType(type: DataType, other: DataType) =
when(type) {
in ByteDatatypes -> false
in WordDatatypes -> other in ByteDatatypes
else -> true
}
private data class ReorderedAssociativeBinaryExpr(val expr: BinaryExpression, val leftVal: LiteralValue?, val rightVal: LiteralValue?)

View File

@ -8,16 +8,19 @@ import kotlin.math.floor
/*
todo: subroutines with 1 or 2 byte args or 1 word arg can be converted to asm sub calling convention (args in registers)
todo: implement usage counters for blocks, variables, subroutines, heap variables. Then:
todo remove unused blocks
todo remove unused variables
todo remove unused subroutines
todo remove unused strings and arrays from the heap
todo inline subroutines that are called exactly once (regardless of their size)
todo inline subroutines that are only called a few times (3?) and that are "sufficiently small" (0-3 statements)
todo inline subroutines that are only called a few times (max 3?)
todo inline subroutines that are "sufficiently small" (0-3 statements)
todo analyse for unreachable code and remove that (f.i. code after goto or return that has no label so can never be jumped to)
*/
class StatementOptimizer(private val namespace: INameScope, private val heap: HeapValues) : IAstProcessor {
@ -52,6 +55,18 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
linesToRemove.reversed().forEach{subroutine.statements.removeAt(it)}
}
if(subroutine.canBeAsmSubroutine) {
optimizationsDone++
return subroutine.intoAsmSubroutine() // TODO this doesn't work yet due to parameter vardecl issue
// TODO fix parameter passing so this also works:
// asmsub aa(byte arg @ Y) -> clobbers() -> () {
// byte local = arg ; @todo fix 'undefined symbol arg' by some sort of alias name for the parameter
// A=44
// }
}
return subroutine
}
@ -61,7 +76,7 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
var previousAssignmentLine: Int? = null
for (i in 0 until statements.size) {
val stmt = statements[i] as? Assignment
if (stmt != null) {
if (stmt != null && stmt.value is LiteralValue) {
if (previousAssignmentLine == null) {
previousAssignmentLine = i
continue
@ -80,28 +95,18 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
return linesToRemove
}
private fun returnregisters(subroutine: Subroutine): List<RegisterOrStatusflag> {
return when {
subroutine.returntypes.isEmpty() -> listOf()
subroutine.returntypes.size==1 && subroutine.returntypes[0] in setOf(DataType.BYTE, DataType.UBYTE) -> listOf(RegisterOrStatusflag(RegisterOrPair.A, null, null))
subroutine.returntypes.size==1 && subroutine.returntypes[0] in setOf(DataType.WORD, DataType.UWORD) -> listOf(RegisterOrStatusflag(RegisterOrPair.AY, null, null))
subroutine.returntypes.size==2 && subroutine.returntypes.all { it in setOf(DataType.BYTE, DataType.UBYTE)} -> listOf(RegisterOrStatusflag(RegisterOrPair.A, null, null), RegisterOrStatusflag(RegisterOrPair.Y, null, null))
else -> throw FatalAstException("can't convert return values to registers")
}
}
private fun isNotMemory(target: AssignTarget): Boolean {
if(target.register!=null)
return true
if(target.memoryAddress!=null)
return false
if(target.arrayindexed!=null) {
val targetStmt = target.arrayindexed.identifier.targetStatement(namespace) as? VarDecl
val targetStmt = target.arrayindexed.identifier.targetVarDecl(namespace)
if(targetStmt!=null)
return targetStmt.type!=VarDeclType.MEMORY
}
if(target.identifier!=null) {
val targetStmt = target.identifier.targetStatement(namespace) as? VarDecl
val targetStmt = target.identifier.targetVarDecl(namespace)
if(targetStmt!=null)
return targetStmt.type!=VarDeclType.MEMORY
}
@ -109,39 +114,39 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
}
override fun process(functionCall: FunctionCallStatement): IStatement {
if(functionCall.target.nameInSource.size==1 && functionCall.target.nameInSource[0] in BuiltinFunctions) {
val functionName = functionCall.target.nameInSource[0]
override fun process(functionCallStatement: FunctionCallStatement): IStatement {
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in BuiltinFunctions) {
val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in pureBuiltinFunctions) {
printWarning("statement has no effect (function return value is discarded)", functionCall.position)
statementsToRemove.add(functionCall)
return functionCall
printWarning("statement has no effect (function return value is discarded)", functionCallStatement.position)
statementsToRemove.add(functionCallStatement)
return functionCallStatement
}
}
if(functionCall.target.nameInSource==listOf("c64scr", "print") ||
functionCall.target.nameInSource==listOf("c64scr", "print_p")) {
if(functionCallStatement.target.nameInSource==listOf("c64scr", "print") ||
functionCallStatement.target.nameInSource==listOf("c64scr", "print_p")) {
// printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters
if(functionCall.arglist.single() is LiteralValue)
if(functionCallStatement.arglist.single() is LiteralValue)
throw AstException("string argument should be on heap already")
val stringVar = functionCall.arglist.single() as? IdentifierReference
val stringVar = functionCallStatement.arglist.single() as? IdentifierReference
if(stringVar!=null) {
val heapId = stringVar.heapId(namespace)
val string = heap.get(heapId).str!!
if(string.length==1) {
val petscii = Petscii.encodePetscii(string, true)[0]
functionCall.arglist.clear()
functionCall.arglist.add(LiteralValue.optimalInteger(petscii, functionCall.position))
functionCall.target = IdentifierReference(listOf("c64", "CHROUT"), functionCall.target.position)
functionCallStatement.arglist.clear()
functionCallStatement.arglist.add(LiteralValue.optimalInteger(petscii, functionCallStatement.position))
functionCallStatement.target = IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position)
optimizationsDone++
return functionCall
return functionCallStatement
} else if(string.length==2) {
val petscii = Petscii.encodePetscii(string, true)
val scope = AnonymousScope(mutableListOf(), functionCall.position)
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCall.target.position),
mutableListOf(LiteralValue.optimalInteger(petscii[0], functionCall.position)), functionCall.position))
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCall.target.position),
mutableListOf(LiteralValue.optimalInteger(petscii[1], functionCall.position)), functionCall.position))
val scope = AnonymousScope(mutableListOf(), functionCallStatement.position)
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position),
mutableListOf(LiteralValue.optimalInteger(petscii[0], functionCallStatement.position)), functionCallStatement.position))
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position),
mutableListOf(LiteralValue.optimalInteger(petscii[1], functionCallStatement.position)), functionCallStatement.position))
optimizationsDone++
return scope
}
@ -151,27 +156,27 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
// if it calls a subroutine,
// and the first instruction in the subroutine is a jump, call that jump target instead
// if the first instruction in the subroutine is a return statement, replace with a nop instruction
val subroutine = functionCall.target.targetStatement(namespace) as? Subroutine
val subroutine = functionCallStatement.target.targetSubroutine(namespace)
if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Jump && first.identifier!=null) {
optimizationsDone++
return FunctionCallStatement(first.identifier, functionCall.arglist, functionCall.position)
return FunctionCallStatement(first.identifier, functionCallStatement.arglist, functionCallStatement.position)
}
if(first is ReturnFromIrq || first is Return) {
optimizationsDone++
return NopStatement(functionCall.position)
return NopStatement(functionCallStatement.position)
}
}
return super.process(functionCall)
return super.process(functionCallStatement)
}
override fun process(functionCall: FunctionCall): IExpression {
// if it calls a subroutine,
// and the first instruction in the subroutine is a jump, call that jump target instead
// if the first instruction in the subroutine is a return statement with constant value, replace with the constant value
val subroutine = functionCall.target.targetStatement(namespace) as? Subroutine
val subroutine = functionCall.target.targetSubroutine(namespace)
if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Jump && first.identifier!=null) {
@ -336,7 +341,7 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
}
override fun process(jump: Jump): IStatement {
val subroutine = jump.identifier?.targetStatement(namespace) as? Subroutine
val subroutine = jump.identifier?.targetSubroutine(namespace)
if(subroutine!=null) {
// if the first instruction in the subroutine is another jump, shortcut this one
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
@ -375,7 +380,7 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
if (same(target, bexpr.left)) {
// remove assignments that have no effect X=X , X+=0, X-=0, X*=1, X/=1, X//=1, A |= 0, A ^= 0, A<<=0, etc etc
// A = A <operator> B
val vardeclDt = (target.identifier?.targetStatement(namespace) as? VarDecl)?.type
val vardeclDt = (target.identifier?.targetVarDecl(namespace))?.type
when (bexpr.operator) {
"+" -> {
@ -517,8 +522,8 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
}
if(target1.arrayindexed!=null && target2.arrayindexed!=null) {
if(target1.arrayindexed.identifier.nameInSource == target2.arrayindexed.identifier.nameInSource) {
val x1 = target1.arrayindexed.arrayspec.x.constValue(namespace, heap)
val x2 = target2.arrayindexed.arrayspec.x.constValue(namespace, heap)
val x1 = target1.arrayindexed.arrayspec.index.constValue(namespace, heap)
val x2 = target2.arrayindexed.arrayspec.index.constValue(namespace, heap)
return x1!=null && x2!=null && x1==x2
}
}
@ -543,7 +548,7 @@ fun same(left: IExpression, right: IExpression): Boolean {
&& same(right.right, left.right))
is ArrayIndexedExpression -> {
return (right is ArrayIndexedExpression && right.identifier.nameInSource == left.identifier.nameInSource
&& same(right.arrayspec.x, left.arrayspec.x))
&& same(right.arrayspec.index, left.arrayspec.index))
}
is LiteralValue -> return (right is LiteralValue && right==left)
}

View File

@ -26,8 +26,12 @@ private class LexerErrorListener: BaseErrorListener() {
}
fun importModule(stream: CharStream, moduleName: String, isLibrary: Boolean): Module {
val lexer = prog8Lexer(stream)
internal class CustomLexer(val modulePath: Path, input: CharStream?) : prog8Lexer(input)
fun importModule(stream: CharStream, modulePath: Path, isLibrary: Boolean): Module {
val moduleName = modulePath.fileName
val lexer = CustomLexer(modulePath, stream)
val lexerErrors = LexerErrorListener()
lexer.addErrorListener(lexerErrors)
val tokens = CommentHandlingTokenStream(lexer)
@ -35,13 +39,13 @@ fun importModule(stream: CharStream, moduleName: String, isLibrary: Boolean): Mo
val parseTree = parser.module()
val numberOfErrors = parser.numberOfSyntaxErrors + lexerErrors.numberOfErrors
if(numberOfErrors > 0)
throw ParsingFailedError("There are $numberOfErrors errors in '$moduleName.p8'.")
throw ParsingFailedError("There are $numberOfErrors errors in '$moduleName'.")
// You can do something with the parsed comments:
// tokens.commentTokens().forEach { println(it) }
// convert to Ast
val moduleAst = parseTree.toAst(moduleName, isLibrary, Paths.get(stream.sourceName))
val moduleAst = parseTree.toAst(moduleName.toString(), isLibrary, modulePath)
importedModules[moduleAst.name] = moduleAst
// process imports
@ -63,7 +67,7 @@ fun importModule(stream: CharStream, moduleName: String, isLibrary: Boolean): Mo
.asSequence()
.mapIndexed { i, it -> Pair(i, it) }
.filter { (it.second as? Directive)?.directive == "%import" }
.map { Pair(it.first, executeImportDirective(it.second as Directive, Paths.get("$moduleName.p8"))) }
.map { Pair(it.first, executeImportDirective(it.second as Directive, modulePath)) }
.toList()
imports.reversed().forEach {
@ -95,9 +99,8 @@ fun importModule(filePath: Path) : Module {
if(!Files.isReadable(filePath))
throw ParsingFailedError("No such file: $filePath")
val moduleName = filePath.fileName.toString().substringBeforeLast('.')
val input = CharStreams.fromPath(filePath)
return importModule(input, moduleName, filePath.parent==null)
return importModule(input, filePath, filePath.parent==null)
}
@ -136,7 +139,7 @@ private fun executeImportDirective(import: Directive, importedFrom: Path): Modul
// load the module from the embedded resource
resource.use {
println("importing '$moduleName' (embedded library)")
importModule(CharStreams.fromStream(it), moduleName, true)
importModule(CharStreams.fromStream(it), Paths.get("@embedded@/$moduleName"), true)
}
} else {
val modulePath = discoverImportedModuleFile(moduleName, importedFrom, import.position)

View File

@ -1,9 +1,8 @@
package prog8.stackvm
import prog8.ast.DataType
import prog8.ast.Position
import prog8.ast.unescape
import prog8.ast.*
import prog8.compiler.HeapValues
import prog8.compiler.IntegerOrAddressOf
import prog8.compiler.intermediate.*
import java.io.File
import java.util.*
@ -13,7 +12,7 @@ class Program (val name: String,
val program: MutableList<Instruction>,
val variables: Map<String, Value>,
val memoryPointers: Map<String, Pair<Int, DataType>>,
val labels: Map<String, Instruction>,
val labels: Map<String, Int>,
val memory: Map<Int, List<Value>>,
val heap: HeapValues)
{
@ -22,7 +21,6 @@ class Program (val name: String,
program.add(LabelInstr("____program_end", false))
program.add(Instruction(Opcode.TERMINATE))
program.add(Instruction(Opcode.NOP))
connect()
}
companion object {
@ -33,7 +31,7 @@ class Program (val name: String,
val program = mutableListOf<Instruction>()
val variables = mutableMapOf<String, Value>()
val memoryPointers = mutableMapOf<String, Pair<Int, DataType>>()
val labels = mutableMapOf<String, Instruction>()
val labels = mutableMapOf<String, Int>()
while(lines.hasNext()) {
val (lineNr, line) = lines.next()
@ -55,7 +53,7 @@ class Program (val name: String,
program: MutableList<Instruction>,
variables: MutableMap<String, Value>,
memoryPointers: MutableMap<String, Pair<Int, DataType>>,
labels: MutableMap<String, Instruction>)
labels: MutableMap<String, Int>)
{
while(true) {
val (_, line) = lines.next()
@ -69,8 +67,10 @@ class Program (val name: String,
loadMemoryPointers(lines, memoryPointers, heap)
else if(line=="%instructions") {
val (blockInstructions, blockLabels) = loadInstructions(lines, heap)
val baseIndex = program.size
program.addAll(blockInstructions)
labels.putAll(blockLabels)
val labelsWithIndex = blockLabels.mapValues { baseIndex+blockInstructions.indexOf(it.value) }
labels.putAll(labelsWithIndex)
}
}
}
@ -88,22 +88,32 @@ class Program (val name: String,
}
heapvalues.sortedBy { it.first }.forEach {
when(it.second) {
DataType.STR,
DataType.STR_P,
DataType.STR_S,
DataType.STR_PS -> heap.add(it.second, unescape(it.third.substring(1, it.third.length-1), Position("<stackvmsource>", 0, 0, 0)))
DataType.STR, DataType.STR_S -> heap.addString(it.second, unescape(it.third.substring(1, it.third.length-1), Position("<stackvmsource>", 0, 0, 0)))
DataType.ARRAY_UB, DataType.ARRAY_B,
DataType.ARRAY_UW, DataType.ARRAY_W -> {
val numbers = it.third.substring(1, it.third.length-1).split(',')
val intarray = numbers.map{number->number.trim().toInt()}.toIntArray()
heap.add(it.second, intarray)
val intarray = numbers.map{number->
val num=number.trim()
if(num.startsWith("&")) {
// it's AddressOf
val scopedname = num.substring(1)
val iref = IdentifierReference(scopedname.split('.'), Position("<intermediate>", 0,0,0))
val addrOf = AddressOf(iref, Position("<intermediate>", 0,0,0))
addrOf.scopedname=scopedname
IntegerOrAddressOf(null, addrOf)
} else {
IntegerOrAddressOf(num.toInt(), null)
}
}.toTypedArray()
heap.addIntegerArray(it.second, intarray)
}
DataType.ARRAY_F -> {
val numbers = it.third.substring(1, it.third.length-1).split(',')
val doublearray = numbers.map{number->number.trim().toDouble()}.toDoubleArray()
heap.add(it.second, doublearray)
heap.addDoublesArray(doublearray)
}
DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT -> throw VmExecutionException("invalid heap value type ${it.second}")
in NumericDatatypes -> throw VmExecutionException("invalid heap value type ${it.second}")
else -> throw VmExecutionException("weird datatype")
}
}
}
@ -146,8 +156,26 @@ class Program (val name: String,
Instruction(opcode, callLabel = withoutQuotes)
}
Opcode.SYSCALL -> {
val call = Syscall.valueOf(args!!)
Instruction(opcode, Value(DataType.UBYTE, call.callNr))
if(args!! in syscallNames) {
val call = Syscall.valueOf(args)
Instruction(opcode, Value(DataType.UBYTE, call.callNr))
} else {
val args2 = args.replace('.', '_')
if(args2 in syscallNames) {
val call = Syscall.valueOf(args2)
Instruction(opcode, Value(DataType.UBYTE, call.callNr))
} else {
// the syscall is not yet implemented. emit a stub.
Instruction(Opcode.SYSCALL, Value(DataType.UBYTE, Syscall.SYSCALLSTUB.callNr), callLabel = args2)
}
}
}
Opcode.INCLUDE_FILE -> {
val argparts = args!!.split(' ')
val filename = argparts[0]
val offset = if(argparts.size>=2 && argparts[1]!="null") getArgValue(argparts[1], heap) else null
val length = if(argparts.size>=3 && argparts[2]!="null") getArgValue(argparts[2], heap) else null
Instruction(opcode, offset, length, filename)
}
else -> {
Instruction(opcode, getArgValue(args, heap))
@ -200,7 +228,7 @@ class Program (val name: String,
DataType.UWORD -> Value(DataType.UWORD, valueStr.substring(3).toInt(16))
DataType.WORD -> Value(DataType.WORD, valueStr.substring(2).toInt(16))
DataType.FLOAT -> Value(DataType.FLOAT, valueStr.substring(2).toDouble())
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
in StringDatatypes -> {
if(valueStr.startsWith('"') && valueStr.endsWith('"'))
throw VmExecutionException("encountered a var with a string value, but all string values should already have been moved into the heap")
else if(!valueStr.startsWith("heap:"))
@ -210,11 +238,7 @@ class Program (val name: String,
Value(type, heapId)
}
}
DataType.ARRAY_UB,
DataType.ARRAY_B,
DataType.ARRAY_UW,
DataType.ARRAY_W,
DataType.ARRAY_F -> {
in ArrayDatatypes -> {
if(!valueStr.startsWith("heap:"))
throw VmExecutionException("invalid array value, should be a heap reference")
else {
@ -222,6 +246,7 @@ class Program (val name: String,
Value(type, heapId)
}
}
else -> throw VmExecutionException("weird datatype")
}
vars[name] = value
}
@ -268,50 +293,4 @@ class Program (val name: String,
}
}
}
private fun connect() {
val it1 = program.iterator()
val it2 = program.iterator()
it2.next()
while(it1.hasNext() && it2.hasNext()) {
val instr = it1.next()
val nextInstr = it2.next()
when(instr.opcode) {
Opcode.TERMINATE -> instr.next = instr // won't ever execute a next instruction
Opcode.RETURN -> instr.next = instr // kinda a special one, in actuality the return instruction is dynamic
Opcode.JUMP -> {
if(instr.callLabel==null) {
throw VmExecutionException("stackVm doesn't support JUMP to memory address")
} else {
// jump to label
val target = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}")
instr.next = target
}
}
Opcode.BCC, Opcode.BCS, Opcode.BZ, Opcode.BNZ, Opcode.BNEG, Opcode.BPOS, Opcode.JZ, Opcode.JNZ, Opcode.JZW, Opcode.JNZW -> {
if(instr.callLabel==null) {
throw VmExecutionException("stackVm doesn't support branch to memory address")
} else {
// branch to label
val jumpInstr = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}")
instr.next = jumpInstr
instr.nextAlt = nextInstr
}
}
Opcode.CALL -> {
if(instr.callLabel==null) {
throw VmExecutionException("stackVm doesn't support CALL to memory address")
} else {
// call label
val jumpInstr = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}")
instr.next = jumpInstr
instr.nextAlt = nextInstr // instruction to return to
}
}
else -> instr.next = nextInstr
}
}
}
}

View File

@ -15,6 +15,8 @@ class BitmapScreenPanel : KeyListener, JPanel() {
private val image = BufferedImage(SCREENWIDTH, SCREENHEIGHT, BufferedImage.TYPE_INT_ARGB)
private val g2d = image.graphics as Graphics2D
private var cursorX: Int=0
private var cursorY: Int=0
init {
val size = Dimension(image.width * SCALING, image.height * SCALING)
@ -48,6 +50,8 @@ class BitmapScreenPanel : KeyListener, JPanel() {
fun clearScreen(color: Int) {
g2d.background = palette[color and 15]
g2d.clearRect(0, 0, BitmapScreenPanel.SCREENWIDTH, BitmapScreenPanel.SCREENHEIGHT)
cursorX = 0
cursorY = 0
}
fun setPixel(x: Int, y: Int, color: Int) {
image.setRGB(x, y, palette[color and 15].rgb)
@ -56,25 +60,74 @@ class BitmapScreenPanel : KeyListener, JPanel() {
g2d.color = palette[color and 15]
g2d.drawLine(x1, y1, x2, y2)
}
fun writeText(x: Int, y: Int, text: String, color: Int) {
if(color!=1) {
TODO("text can only be white for now")
}
var xx=x
var yy=y
for(sc in Petscii.encodeScreencode(text, true)) {
setChar(xx, yy, sc)
xx++
if(xx>=(SCREENWIDTH/8)) {
yy++
xx=0
fun printText(text: String, color: Int, lowercase: Boolean) {
val lines = text.split('\n')
for(line in lines.withIndex()) {
printTextSingleLine(line.value, color, lowercase)
if(line.index<lines.size-1) {
cursorX=0
cursorY++
}
}
}
private fun printTextSingleLine(text: String, color: Int, lowercase: Boolean) {
if(color!=1) {
TODO("text can only be white for now")
}
for(clearx in cursorX until cursorX+text.length) {
g2d.clearRect(8*clearx, 8*y, 8, 8)
}
for(sc in Petscii.encodeScreencode(text, lowercase)) {
setChar(cursorX, cursorY, sc)
cursorX++
if(cursorX>=(SCREENWIDTH/8)) {
cursorY++
cursorX=0
}
}
}
fun printChar(char: Short) {
if(char==13.toShort() || char==141.toShort()) {
cursorX=0
cursorY++
} else {
setChar(cursorX, cursorY, char)
cursorX++
if (cursorX >= (SCREENWIDTH / 8)) {
cursorY++
cursorX = 0
}
}
}
fun setChar(x: Int, y: Int, screenCode: Short) {
g2d.clearRect(8*x, 8*y, 8, 8)
g2d.drawImage(Charset.shiftedChars[screenCode.toInt()], 8*x, 8*y , null)
}
fun setCursorPos(x: Int, y: Int) {
cursorX = x
cursorY = y
}
fun getCursorPos(): Pair<Int, Int> {
return Pair(cursorX, cursorY)
}
fun writeText(x: Int, y: Int, text: String, color: Int, lowercase: Boolean) {
var xx=x
if(color!=1) {
TODO("text can only be white for now")
}
for(clearx in xx until xx+text.length) {
g2d.clearRect(8*clearx, 8*y, 8, 8)
}
for(sc in Petscii.encodeScreencode(text, lowercase)) {
setChar(xx++, y, sc)
}
}
companion object {
const val SCREENWIDTH = 320

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,10 @@ import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.empty
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.ByteDatatypes
import prog8.ast.DataType
import prog8.ast.IterableDatatypes
import prog8.ast.WordDatatypes
import prog8.compiler.HeapValues
import prog8.compiler.intermediate.Instruction
import prog8.compiler.intermediate.Opcode
@ -55,7 +58,7 @@ class TestStackVmOpcodes {
private fun makeProg(ins: MutableList<Instruction>,
vars: Map<String, Value>?=null,
memoryPointers: Map<String, Pair<Int, DataType>>?=null,
labels: Map<String, Instruction>?=null,
labels: Map<String, Int>?=null,
mem: Map<Int, List<Value>>?=null) : Program {
val heap = HeapValues()
return Program("test", ins, vars ?: mapOf(), memoryPointers ?: mapOf(), labels ?: mapOf(), mem ?: mapOf(), heap)
@ -327,8 +330,6 @@ class TestStackVmOpcodes {
@Test
fun testPow() {
testBinaryOperator(Value(DataType.UBYTE, 3), Opcode.POW_UB, Value(DataType.UBYTE, 4), Value(DataType.UBYTE, 81))
testBinaryOperator(Value(DataType.UWORD, 3), Opcode.POW_UW, Value(DataType.UWORD, 4), Value(DataType.UWORD, 81))
testBinaryOperator(Value(DataType.FLOAT, 1.1), Opcode.POW_F, Value(DataType.FLOAT, 81.0), Value(DataType.FLOAT, 2253.2402360440274))
}
@ -748,7 +749,7 @@ class TestStackVmOpcodes {
Instruction(Opcode.LINE, callLabel = "string1"),
Instruction(Opcode.TERMINATE),
Instruction(Opcode.LINE, callLabel = "string2"))
val labels = mapOf("label" to ins.last()) // points to the second LINE instruction
val labels = mapOf("label" to ins.size-1) // points to the second LINE instruction
vm.load(makeProg(ins, labels=labels), null)
vm.step(2)
assertEquals("", vm.sourceLine)
@ -768,7 +769,7 @@ class TestStackVmOpcodes {
Instruction(Opcode.LINE, callLabel = "string1"),
Instruction(Opcode.TERMINATE),
Instruction(Opcode.LINE, callLabel = "string2"))
val labels = mapOf("label" to ins.last()) // points to the second LINE instruction
val labels = mapOf("label" to ins.size-1) // points to the second LINE instruction
vm.load(makeProg(ins, labels=labels), null)
assertFalse(vm.P_carry)
vm.step(2)
@ -789,7 +790,7 @@ class TestStackVmOpcodes {
Instruction(Opcode.LINE, callLabel = "string1"),
Instruction(Opcode.TERMINATE),
Instruction(Opcode.LINE, callLabel = "string2"))
val labels = mapOf("label" to ins.last()) // points to the second LINE instruction
val labels = mapOf("label" to ins.size-1) // points to the second LINE instruction
vm.load(makeProg(ins, labels=labels), null)
vm.step(2)
assertEquals("", vm.sourceLine)
@ -809,7 +810,7 @@ class TestStackVmOpcodes {
Instruction(Opcode.LINE, callLabel = "string1"),
Instruction(Opcode.TERMINATE),
Instruction(Opcode.LINE, callLabel = "string2"))
val labels = mapOf("label" to ins.last()) // points to the second LINE instruction
val labels = mapOf("label" to ins.size-1) // points to the second LINE instruction
vm.load(makeProg(ins, labels=labels), null)
vm.step(2)
assertEquals("", vm.sourceLine)
@ -829,7 +830,7 @@ class TestStackVmOpcodes {
Instruction(Opcode.LINE, callLabel = "string1"),
Instruction(Opcode.TERMINATE),
Instruction(Opcode.LINE, callLabel = "string2"))
val labels = mapOf("label" to ins.last()) // points to the second LINE instruction
val labels = mapOf("label" to ins.size-1) // points to the second LINE instruction
vm.load(makeProg(ins, labels=labels), null)
vm.step(2)
assertEquals("", vm.sourceLine)
@ -849,7 +850,7 @@ class TestStackVmOpcodes {
Instruction(Opcode.LINE, callLabel = "string1"),
Instruction(Opcode.TERMINATE),
Instruction(Opcode.LINE, callLabel = "string2"))
val labels = mapOf("label" to ins.last()) // points to the second LINE instruction
val labels = mapOf("label" to ins.size-1) // points to the second LINE instruction
vm.load(makeProg(ins, labels=labels), null)
vm.step(2)
assertEquals("", vm.sourceLine)
@ -866,7 +867,7 @@ class TestStackVmOpcodes {
Instruction(Opcode.LINE, callLabel = "string1"),
Instruction(Opcode.TERMINATE),
Instruction(Opcode.LINE, callLabel = "string2"))
val labels = mapOf("label" to ins.last()) // points to the second LINE instruction
val labels = mapOf("label" to ins.size-1) // points to the second LINE instruction
vm.load(makeProg(ins, labels=labels), null)
vm.step(2)
assertEquals("string2", vm.sourceLine)
@ -886,7 +887,7 @@ class TestStackVmOpcodes {
vm.step(1)
}
vm.callstack.add(ins[2]) // set the LINE opcode as return instruction
vm.callstack.add(2) // set the LINE opcode as return instruction
assertEquals("", vm.sourceLine)
vm.step(2)
assertEquals("string1", vm.sourceLine)
@ -903,12 +904,12 @@ class TestStackVmOpcodes {
Instruction(Opcode.LINE, callLabel = "called"),
Instruction(Opcode.RETURN)
)
val labels = mapOf("label" to ins[3]) // points to the LINE instruction
val labels = mapOf("label" to 3) // points to the LINE instruction
vm.load(makeProg(ins, labels = labels), null)
vm.step(1)
assertEquals("", vm.sourceLine)
assertEquals(1, vm.callstack.size)
assertSame(ins[1], vm.callstack.peek())
assertSame(1, vm.callstack.peek())
vm.step(1)
assertEquals("called", vm.sourceLine)
vm.step(1)
@ -1234,12 +1235,11 @@ class TestStackVmOpcodes {
private fun pushOpcode(dt: DataType): Opcode {
return when (dt) {
DataType.UBYTE, DataType.BYTE -> Opcode.PUSH_BYTE
DataType.UWORD, DataType.WORD -> Opcode.PUSH_WORD
in ByteDatatypes -> Opcode.PUSH_BYTE
in WordDatatypes -> Opcode.PUSH_WORD
in IterableDatatypes -> Opcode.PUSH_WORD
DataType.FLOAT -> Opcode.PUSH_FLOAT
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS,
DataType.ARRAY_UB, DataType.ARRAY_UW, DataType.ARRAY_F,
DataType.ARRAY_B, DataType.ARRAY_W -> Opcode.PUSH_WORD
else -> throw IllegalArgumentException("invalid datatype")
}
}

View File

@ -138,23 +138,41 @@ class TestZeropage {
assertFailsWith<CompilerException> {
zp.allocate("", DataType.FLOAT, null)
}
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true))
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true))
zp2.allocate("", DataType.FLOAT, null)
}
@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))
assertFailsWith<CompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true))
}
assertFailsWith<CompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true))
}
}
@Test
fun testFreeSpaces() {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
assertEquals(19, zp1.available())
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true))
assertEquals(67, zp2.available())
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true))
assertEquals(238, zp3.available())
assertEquals(20, zp1.available())
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false))
assertEquals(95, zp2.available())
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false))
assertEquals(129, zp3.available())
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
assertEquals(238, zp4.available())
}
@Test
fun testReservedSpace() {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true))
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
assertEquals(238, zp1.available())
assertTrue(50 in zp1.free)
assertTrue(100 in zp1.free)
@ -163,8 +181,7 @@ class TestZeropage {
assertTrue(200 in zp1.free)
assertTrue(255 in zp1.free)
assertTrue(199 in zp1.free)
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL,
listOf(50 .. 100, 200..255), true))
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false))
assertEquals(139, zp2.available())
assertFalse(50 in zp2.free)
assertFalse(100 in zp2.free)
@ -178,7 +195,7 @@ class TestZeropage {
@Test
fun testBasicsafeAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
assertEquals(19, zp.available())
assertEquals(20, zp.available())
zp.allocate("", DataType.FLOAT, null)
assertFailsWith<ZeropageDepletedError> {
@ -201,32 +218,28 @@ class TestZeropage {
@Test
fun testFullAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
assertEquals(238, zp.available())
val loc = zp.allocate("", DataType.FLOAT, null)
val loc = zp.allocate("", DataType.UWORD, null)
assertTrue(loc > 3)
assertFalse(loc in zp.free)
val num = zp.available() / 5
val rest = zp.available() % 5
val num = zp.available() / 2
for(i in 0..num-4) {
zp.allocate("", DataType.FLOAT, null)
zp.allocate("", DataType.UWORD, null)
}
assertEquals(18,zp.available())
assertEquals(6,zp.available())
assertFailsWith<ZeropageDepletedError> {
// can't allocate because no more sequential bytes, only fragmented
zp.allocate("", DataType.FLOAT, null)
zp.allocate("", DataType.UWORD, null)
}
for(i in 0..13) {
for(i in 0..5) {
zp.allocate("", DataType.UBYTE, null)
}
zp.allocate("", DataType.UWORD, null)
assertEquals(2, zp.available())
zp.allocate("", DataType.UBYTE, null)
zp.allocate("", DataType.UBYTE, null)
assertEquals(0, zp.available())
assertFailsWith<ZeropageDepletedError> {
// no more space
zp.allocate("", DataType.UBYTE, null)
@ -236,7 +249,7 @@ class TestZeropage {
@Test
fun testEfficientAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
assertEquals(19, zp.available())
assertEquals(20, zp.available())
assertEquals(0x04, zp.allocate("", DataType.FLOAT, null))
assertEquals(0x09, zp.allocate("", DataType.UBYTE, null))
assertEquals(0x0d, zp.allocate("", DataType.UWORD, null))
@ -245,6 +258,7 @@ class TestZeropage {
assertEquals(0xa9, zp.allocate("", DataType.UWORD, null))
assertEquals(0xb5, zp.allocate("", DataType.UWORD, null))
assertEquals(0xf7, zp.allocate("", DataType.UWORD, null))
assertEquals(0x0a, zp.allocate("", DataType.UBYTE, null))
assertEquals(0xf9, zp.allocate("", DataType.UBYTE, null))
assertEquals(0, zp.available())
}

View File

@ -82,7 +82,6 @@ class TestStackVmValue {
fun testEqualsAndNotEqualsHeapTypes()
{
assertTrue(sameValueAndType(Value(DataType.STR, 999), Value(DataType.STR, 999)))
assertFalse(sameValueAndType(Value(DataType.STR, 999), Value(DataType.STR_P, 999)))
assertFalse(sameValueAndType(Value(DataType.STR, 999), Value(DataType.STR, 222)))
assertTrue(sameValueAndType(Value(DataType.ARRAY_UB, 99), Value(DataType.ARRAY_UB, 99)))

21
create_compiler_jar.sh Executable file
View File

@ -0,0 +1,21 @@
#!/bin/sh
# this script uses the Gradle build to compile the code,
# and then adds the contents of several jar files into one output jar.
./gradlew jar
mkdir -p compiler_jar/extracted
mkdir -p compiler_jar/source
cp compiler/build/libs/compiler.jar parser/build/libs/parser.jar parser/antlr/lib/antlr-runtime-4.7.2.jar compiler_jar/source/
KOTLINLIBS=$(kotlinc -verbose -script 2>&1 | grep home | cut -d ' ' -f 6-)/lib
cp ${KOTLINLIBS}/kotlin-stdlib-jdk8.jar ${KOTLINLIBS}/kotlin-stdlib.jar compiler_jar/source/
pushd compiler_jar/extracted
for i in ../source/*.jar; do jar xf $i; done
cd ..
jar cfe ../prog8compiler.jar prog8.CompilerMainKt -C extracted .
popd
rm -r compiler_jar
ls -l prog8compiler.jar

View File

@ -5,7 +5,7 @@
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="jdk" jdkName="Python 3.7" jdkType="Python SDK" />
<orderEntry type="jdk" jdkName="Python 3.7 (py3)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View File

@ -2,8 +2,33 @@
Writing and building a program
==============================
What is a "Program" anyway?
---------------------------
First, getting a working compiler
---------------------------------
Before you can compile Prog8 programs, you'll have to build the compiler itself.
First make sure you have installed the :ref:`requirements`.
Then you can choose a few ways to create the compiler:
**Using the shell script:**
#. run the "build_the_compiler.sh" shell script
#. it will create a "prog8compiler.jar" file which contains everything.
#. run the compiler with "java -jar prog8compiler.jar" to see how you can use it.
**using the Gradle build system:**
#. run the command "./gradlew installDist"
#. it will create the commands and required libraries in the "./compiler/build/install/p8compile/" directory
#. run the compiler with the "./compiler/build/install/p8compile/bin/p8compile" command to see how you can use it.
**download a precompiled version from github:**
#. download a recent "prog8compiler.jar" from `the releases on Github <https://github.com/irmen/prog8/releases>`_
#. run the compiler with "java -jar prog8compiler.jar" to see how you can use it.
What is a Prog8 "Program" anyway?
---------------------------------
A "complete runnable program" is a compiled, assembled, and linked together single unit.
It contains all of the program's code and data and has a certain file format that

View File

@ -25,58 +25,73 @@ The project is on github: https://github.com/irmen/prog8.git
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
.. image:: _static/cube3d.png
:width: 33%
:alt: 3d rotating sprites
.. image:: _static/wizzine.png
:width: 33%
:alt: Simple wizzine sprite effect
.. image:: _static/tehtriz.png
:width: 33%
:alt: Fully playable tetris clone
Code example
------------
When this code is compiled::
This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
%import c64lib
%import c64utils
%import c64flt
%zeropage basicsafe
~ main {
ubyte[256] sieve
ubyte candidate_prime = 2
sub start() {
; set text color and activate lowercase charset
c64.COLOR = 13
c64.VMCSB |= 2
memset(sieve, 256, false)
; use optimized routine to write text
c64scr.print("Hello!\n")
; use iteration to write text
str question = "How are you?\n"
for ubyte char in question
c64.CHROUT(char)
; use indexed loop to write characters
str bye = "Goodbye!\n"
for ubyte c in 0 to len(bye)
c64.CHROUT(bye[c])
float clock_seconds = ((mkword(c64.TIME_LO, c64.TIME_MID) as float)
+ (c64.TIME_HI as float)*65536.0)
/ 60
float hours = floor(clock_seconds / 3600)
clock_seconds -= hours*3600
float minutes = floor(clock_seconds / 60)
clock_seconds = floor(clock_seconds - minutes * 60.0)
c64scr.print("system time in ti$ is ")
c64flt.print_f(hours)
c64.CHROUT(':')
c64flt.print_f(minutes)
c64.CHROUT(':')
c64flt.print_f(clock_seconds)
c64scr.print("prime numbers up to 255:\n\n")
ubyte amount=0
while true {
ubyte prime = find_next_prime()
if prime==0
break
c64scr.print_ub(prime)
c64scr.print(", ")
amount++
}
c64.CHROUT('\n')
c64scr.print("number of primes (expected 54): ")
c64scr.print_ub(amount)
c64.CHROUT('\n')
}
sub find_next_prime() -> ubyte {
while sieve[candidate_prime] {
candidate_prime++
if candidate_prime==0
return 0
}
sieve[candidate_prime] = true
uword multiple = candidate_prime
while multiple < len(sieve) {
sieve[lsb(multiple)] = true
multiple += candidate_prime
}
return candidate_prime
}
}
when compiled an ran on a C-64 you'll get:
you get a program that outputs this when loaded on a C-64:
.. image:: _static/hello_screen.png
.. image:: _static/primes_example.png
:align: center
:alt: result when run on C-64
@ -120,8 +135,12 @@ A recent .exe version of this tool for Windows can be obtained from my `clone <h
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 the packaged compiler.
If you're scared of Oracle's licensing terms, most Linux distributions ship OpenJDK instead
and for Windows it's possible to get that as well: for instance,
`Azul's Zulu <https://www.azul.com/downloads/zulu/>`_ is a certified OpenJDK
implementation available for various platforms.
A C-64 emulator or a real C-64 to run the programs on. The compiler assumes the presence
Finally: a **C-64 emulator** (or a real C-64 ofcourse) to run the programs on. The compiler assumes the presence
of the `Vice emulator <http://vice-emu.sourceforge.net/>`_.
.. hint::
@ -130,11 +149,16 @@ of the `Vice emulator <http://vice-emu.sourceforge.net/>`_.
packaged jar file.
.. note::
To build the compiler from source, Kotlin SDK version 1.3 or newer is needed.
Building the compiler itself:
(re)building the compiler itself requires a Kotlin SDK version 1.3.
The compiler is developed using the `IntelliJ IDEA <https://www.jetbrains.com/idea/>`_
IDE from Jetbrains, with the Kotlin plugin (free community edition of this IDE is available).
But a bare Kotlin SDK installation should work just as well.
A shell script is provided to build and package the compiler from the command line.
A shell script (``build_the_compiler.sh``) is provided to build and package the compiler from the command line.
You can also use the Gradle build system to build the compiler (it will take care of
downloading all required libraries for you) by typing ``gradle installDist`` for instance.
The output of this gradle build will appear in the "./compiler/build/install/p8compile/" directory.
.. note::
Development and testing is done on Linux, but the compiler should run on most

View File

@ -187,9 +187,13 @@ Values will usually be part of an expression or assignment statement::
byte counter = 42 ; variable of size 8 bits, with initial value 42
.. todo::
There must be a way to tell the compiler which variables you require to be in Zeropage:
``zeropage`` modifier keyword on vardecl perhaps?
*zeropage tag:*
If you add the ``@zp`` tag to the variable declaration, the compiler will prioritize this variable
when selecting variables to put into zero page. If there are enough free locations in the zeropage,
it will then try to fill it with as much other variables as possible (before they will be put in regular memory pages).
Example::
byte @zp zeropageCounter = 42
Variables that represent CPU hardware registers
@ -233,9 +237,9 @@ Arrays
^^^^^^
Array types are also supported. They can be made of bytes, words or floats::
byte[4] array = [1, 2, 3, 4] ; initialize the array
byte[99] array = 255 ; initialize array with all 255's [255, 255, 255, 255, ...]
byte[100] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
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]
value = array[3] ; the fourth value in the array (index is 0-based)
char = string[4] ; the fifth character (=byte) in the string
@ -277,13 +281,13 @@ You'll have to specify the initial value expression. This value is then used
by the compiler everywhere you refer to the constant (and no storage is allocated
for the constant itself). This is only valid for the simple numeric types (byte, word, float).
When using ``memory``, the variable will point to specific location in memory,
When using ``&`` (the address-of operator but now applied to a datatype), the variable will point to specific location in memory,
rather than being newly allocated. The initial value (mandatory) must be a valid
memory address. Reading the variable will read the given data type from the
address you specified, and setting the varible will directly modify that memory location(s)::
const byte max_age = 2000 - 1974 ; max_age will be the constant value 26
memory word SCREENCOLORS = $d020 ; a 16-bit word at the addres $d020-$d021
&word SCREENCOLORS = $d020 ; a 16-bit word at the addres $d020-$d021
.. note::
@ -321,6 +325,11 @@ be set to zero only for the first run of the program. A second run will utilize
where it left off (but your code will be a bit smaller because no initialization instructions
are generated)
.. caution::
variables that get allocated in zero-page will *not* have a zero starting value when you omit
the variable's initialization. They'll be whatever the last value in that zero page
location was. So it's best to don't depend on the uninitialized starting value!
.. warning::
this behavior may change in a future version so that subsequent runs always
use the same initial values
@ -462,11 +471,7 @@ There are various built-in functions such as sin(), cos(), min(), max() that can
You can also reference idendifiers defined elsewhere in your code.
.. attention::
**Data type conversion (during calculations) and floating point handling:**
BYTE values used in arithmetic expressions (calculations) will be automatically converted into WORD values
if the calculation needs that to store the resulting value. Once a WORD value is used, all other results will be WORDs as well
(there's no automatic conversion of WORD into BYTE).
**Floating points used in expressions:**
When a floating point value is used in a calculation, the result will be a floating point, and byte or word values
will be automatically converted into floats in this case. The compiler will issue a warning though when this happens, because floating
@ -494,6 +499,28 @@ Usually the normal precedence rules apply (``*`` goes before ``+`` etc.) but sub
within parentheses will be evaluated first. So ``(4 + 8) * 2`` is 24 and not 20,
and ``(true or false) and false`` is false instead of true.
.. attention::
**calculations keep their datatype:**
When you do calculations on a BYTE type, the result will remain a BYTE.
When you do calculations on a WORD type, the result will remain a WORD.
For instance::
byte b = 44
word w = b*55 ; the result will be 116! (even though the target variable is a word)
w *= 999 ; the result will be -15188 (the multiplication stays within a word)
The compiler will NOT give a warning about this! It's doing this for
performance reasons - so you won't get sudden 16 bit (or even float)
calculations where you needed only simple fast byte arithmetic.
If you do need the extended resulting value, cast at least one of the
operands of an operator to the larger datatype. For example::
byte b = 44
word w = b*55.w ; the result will be 2420
w = (b as word)*55 ; same result
Subroutines
-----------
@ -505,7 +532,6 @@ Subroutines are parts of the code that can be repeatedly invoked using a subrout
Their definition, using the ``sub`` statement, includes the specification of the required parameters and return value.
Subroutines can be defined in a Block, but also nested inside another subroutine. Everything is scoped accordingly.
Calling a subroutine
^^^^^^^^^^^^^^^^^^^^
@ -514,9 +540,13 @@ It is possible to not store the return value but the compiler
will issue a warning then telling you the result values of a subroutine call are discarded.
.. caution::
Note that *recursive* subroutine calls are not supported at this time.
Note that due to the way parameters are processed by the compiler,
subroutines are *non-reentrant*. This means you cannot create recursive calls.
If you do need a recursive algorithm, you'll have to hand code it in embedded assembly for now,
or rewrite it into an iterative algorithm.
Also, subroutines used in the main program should not be used from an IRQ handler. This is because
the subroutine may be interrupted, and will then call itself from the IRQ handler. Results are
then undefined because the variables will get overwritten.
.. _builtinfunctions:
@ -574,8 +604,11 @@ ln(x)
log2(x)
Base 2 logarithm.
sqrt16(w)
16 bit unsigned integer Square root. Result is unsigned byte.
sqrt(x)
Square root.
Floating point Square root.
round(x)
Rounds the floating point to the closest integer.
@ -609,6 +642,11 @@ len(x)
Note: this can be different from the number of *bytes* in memory if the datatype isn't a byte.
Note: lengths of strings and arrays are determined at compile-time! If your program modifies the actual
length of the string during execution, the value of len(string) may no longer be correct!
(use strlen function if you want to dynamically determine the length)
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.
lsb(x)
Get the least significant byte of the word x. Equivalent to the cast "x as ubyte".
@ -707,3 +745,19 @@ rsave()
rrestore()
Restores the CPU registers and the status flags from previously saved values.
read_flags()
Returns the current value of the CPU status register.
Library routines
----------------
There are many routines available in the compiler libraries.
Some are used internally by the compiler as well.
There's too many to list here, just have a look through the source code
of the library modules to see what's there.
(They can be found in the compiler/res directory)
The example programs also use a small set of the library routines, you can study
their source code to see how they might be used.

View File

@ -60,16 +60,20 @@ 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).
- 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
touch change anything else. This allows full use of BASIC and KERNAL ROM routines including default IRQs
during normal system operation.
When the program exits, it simply returns to the BASIC ready prompt.
- 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``.
It is also not possible to cleanly exit the program, other than resetting the machine.
This option makes programs smaller and faster because many more variables can
be stored in the ZP, which is more efficient.
As with ``kernalsafe``, it is not possible to cleanly exit the program, other than to reset the machine.
This option makes programs smaller and faster because even more variables can
be stored in the ZP (which allows for more efficient assembly code).
Also read :ref:`zeropage`.
@ -119,6 +123,7 @@ Directives
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"
@ -129,6 +134,8 @@ Directives
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
@ -209,9 +216,11 @@ Variable declarations
Variables should be declared with their exact type and size so the compiler can allocate storage
for them. You must give them an initial value as well. That value can be a simple literal value,
or an expression. The syntax is::
or an expression. You can add a ``@zp`` zeropage-tag, to tell the compiler to prioritize it
when selecting variables to be put into zeropage.
The syntax is::
<datatype> <variable name> [ = <initial value> ]
<datatype> [ @zp ] <variable name> [ = <initial value> ]
Various examples::
@ -220,10 +229,12 @@ Various examples::
byte age = 2018 - 1974
float wallet = 55.25
str name = "my name is Irmen"
word address = #counter
byte[5] values = [11, 22, 33, 44, 55]
uword address = &counter
byte[] values = [11, 22, 33, 44, 55]
byte[5] values = 255 ; initialize with five 255 bytes
word @zp zpword = 9999 ; prioritize this when selecting vars for zeropage storage
Data types
@ -243,22 +254,24 @@ type identifier type storage size example var declara
``uword`` unsigned word 2 bytes = 16 bits ``uword myvar = $8fee``
``float`` floating-point 5 bytes = 40 bits ``float myvar = 1.2345``
stored in 5-byte cbm MFLPT format
``byte[x]`` signed byte array x bytes ``byte[4] myvar = [1, 2, 3, 4]``
``ubyte[x]`` unsigned byte array x bytes ``ubyte[4] myvar = [1, 2, 3, 4]``
``word[x]`` signed word array 2*x bytes ``word[4] myvar = [1, 2, 3, 4]``
``uword[x]`` unsigned word array 2*x bytes ``uword[4] myvar = [1, 2, 3, 4]``
``float[x]`` floating-point array 5*x bytes ``float[4] myvar = [1.1, 2.2, 3.3, 4.4]``
``byte[x]`` signed byte array x bytes ``byte[4] myvar``
``ubyte[x]`` unsigned byte array x bytes ``ubyte[4] myvar``
``word[x]`` signed word array 2*x bytes ``word[4] myvar``
``uword[x]`` unsigned word array 2*x bytes ``uword[4] myvar``
``float[x]`` floating-point array 5*x bytes ``float[4] myvar``
``byte[]`` signed byte array depends on value ``byte[] myvar = [1, 2, 3, 4]``
``ubyte[]`` unsigned byte array depends on value ``ubyte[] myvar = [1, 2, 3, 4]``
``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`` string (petscii) varies ``str myvar = "hello."``
implicitly terminated by a 0-byte
``str_p`` pascal-string (petscii) varies ``str_p myvar = "hello."``
implicit first byte = length, no 0-byte
``str_s`` string (screencodes) varies ``str_s myvar = "hello."``
implicitly terminated by a 0-byte
``str_ps`` pascal-string varies ``str_ps myvar = "hello."``
(screencodes) implicit first byte = length, no 0-byte
=============== ======================= ================= =========================================
**arrays:** you can split an array initializer list over several lines if you want.
**arrays:** you can split an array initializer list over several lines if you want. When an initialization
value is given, the array size in the declaration can be omitted.
**hexadecimal numbers:** you can use a dollar prefix to write hexadecimal numbers: ``$20ac``
@ -278,13 +291,6 @@ of something with an operand starting with 1 or 0, you'll have to add a space in
- You can force a byte value into a word value by adding the ``.w`` datatype suffix to the number: ``$2a.w`` is equivalent to ``$002a``.
.. todo::
omit the array size in the var decl if an initialization array is given?
**@todo pointers/addresses? (as opposed to normal WORDs)**
Data type conversion
^^^^^^^^^^^^^^^^^^^^
Many type conversions are possible by just writing ``as <type>`` at the end of an expression,
@ -294,11 +300,11 @@ for example ``ubyte ub = floatvalue as ubyte`` will convert the floating point v
Memory mapped variables
^^^^^^^^^^^^^^^^^^^^^^^
The ``memory`` keyword is used in front of a data type keyword, to say that no storage
The ``&`` (address-of operator) used in front of a data type keyword, indicates that no storage
should be allocated by the compiler. Instead, the (mandatory) value assigned to the variable
should be the *memory address* where the value is located::
memory byte BORDER = $d020
&byte BORDERCOLOR = $d020
Direct access to memory locations
@ -339,7 +345,7 @@ which represents a range of numbers or characters,
from the starting value to (and including) the ending value.
If used in the place of a literal value, it expands into the actual array of values::
byte[100] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
byte[] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
Array indexing
@ -356,19 +362,13 @@ Syntax is familiar with brackets: ``arrayvar[x]`` ::
Operators
---------
.. todo::
address-of: ``#``
Takes the address of the symbol following it: ``word address = #somevar``
arithmetic: ``+`` ``-`` ``*`` ``/`` ``**`` ``%``
``+``, ``-``, ``*``, ``/`` are the familiar arithmetic operations.
``/`` is division (will result in integer division when using on integer operands, and a floating point division when at least one of the operands is a float)
``**`` is the power operator: ``3 ** 5`` is equal to 3*3*3*3*3 and is 243.
``**`` is the power operator: ``3 ** 5`` is equal to 3*3*3*3*3 and is 243. (it only works on floating point variables)
``%`` is the remainder operator: ``25 % 7`` is 4. Be careful: without a space, %10 will be parsed as the binary number 2
Remainder is only supported on integer operands (not floats).
bitwise arithmetic: ``&`` ``|`` ``^`` ``~`` ``<<`` ``>>``
``&`` is bitwise and, ``|`` is bitwise or, ``^`` is bitwise xor, ``~`` is bitwise invert (this one is an unary operator)
``<<`` is bitwise left shift and ``>>`` is bitwise right shift (both will not change the datatype of the value)
@ -403,12 +403,19 @@ range creation: ``to``
X = 10
A to X ; range of 5, 6, 7, 8, 9, 10
byte[4] array = 10 to 13 ; sets the array to [1, 2, 3, 4]
byte[] array = 10 to 13 ; sets the array to [1, 2, 3, 4]
for i in 0 to 127 {
; i loops 0, 1, 2, ... 127
}
address of: ``&``
This is a prefix operator that can be applied to a string or array variable or literal value.
It results in the memory address (UWORD) of that string or array in memory: ``uword a = &stringvar``
Sometimes the compiler silently inserts this operator to make it easier for instance
to pass strings or arrays as subroutine call arguments.
This operator can also be used as a prefix to a variable's data type keyword to indicate that
it is a memory mapped variable (for instance: ``&ubyte screencolor = $d021``)
precedence grouping in expressions, or subroutine parameter list: ``(`` *expression* ``)``
Parentheses are used to group parts of an expression to change the order of evaluation.
@ -427,10 +434,20 @@ You call a subroutine like this::
[ result = ] subroutinename_or_address ( [argument...] )
; example:
resultvariable = subroutine ( arg1, arg2, arg3 )
resultvariable = subroutine(arg1, arg2, arg3)
Arguments are separated by commas. The argument list can also be empty if the subroutine
takes no parameters.
takes no parameters. If the subroutine returns a value, you can still omit the assignment to
a result variable (but the compiler will warn you about discarding the result of the call).
Normal subroutines can only return zero or one return values.
However, the special ``asmsub`` routines (implemented in assembly code or referencing
a routine in kernel ROM) can return more than one return values, for instance a status
in the carry bit and a number in A, or a 16-bit value in A/Y registers.
Only for these kind of subroutines it is possible to write a multi value assignment to
store the resulting values::
var1, var2, var3 = asmsubroutine()
@ -499,13 +516,20 @@ For example, this is a for loop using the existing byte variable ``i`` to loop o
And this is a loop over the values of the array ``fibonacci_numbers`` where the loop variable is declared in the loop itself::
word[20] fibonacci_numbers = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181]
word[] fibonacci_numbers = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181]
for word fibnr in fibonacci_numbers {
; do something
}
You can inline the loop variable declaration in the for statement, including optional zp-tag::
for ubyte @zp fastindex in 10 to 20 {
; do something
}
while loop
^^^^^^^^^^

View File

@ -77,11 +77,6 @@ Theoretically they can all be used in a program, with the follwoing limitations:
- it's more convenient and safe to let the compiler allocate these addresses for you and just
use symbolic names in the program code.
.. todo::
There must be a way to tell the compiler which variables you require to be in Zeropage:
``zeropage`` modifier keyword on vardecl perhaps?
Prog8 knows what addresses are safe to use in the various ZP handling configurations.
It will use the free ZP addresses to place its ZP variables in,
until they're all used up. If instructed to output a program that takes over the entire
@ -93,6 +88,7 @@ treats the ZP for the program. The default is to be reasonably restrictive to us
part of the ZP that is not used by the C64's kernal routines.
It's possible to claim the whole ZP as well (by disabling the operating system or kernal).
If you want, it's also possible to be more restricive and stay clear of the addresses used by BASIC routines too.
This allows the program to exit cleanly back to a BASIC ready prompt - something that is not possible in the other modes.
IRQs and the ZeroPage
@ -121,7 +117,8 @@ The following 6502 CPU hardware registers are directly usable in program code (a
- ``A``, ``X``, ``Y`` the three main cpu registers (8 bits)
- the status register (P) carry flag and interrupt disable flag can be written via a couple of special
builtin functions (``set_carry()``, ``clear_carry()``, ``set_irqd()``, ``clear_irqd()``)
builtin functions (``set_carry()``, ``clear_carry()``, ``set_irqd()``, ``clear_irqd()``),
and read via the ``read_flags()`` function.
However, you must assume that the 3 hardware registers ``A``, ``X`` and ``Y``
are volatile. Their values cannot be depended upon, the compiler will use them as required.
@ -148,3 +145,31 @@ Arguments and result values are passed via global variables stored in memory
*These are not allocated on a stack* so it is not possible to create recursive calls!
The result value(s) of a subroutine are returned on the evaluation stack,
to make it possible to use subroutines in expressions.
IRQ Handling
============
Normally, the system's default IRQ handling is not interfered with.
You can however install your own IRQ handler.
This is possible ofcourse by doing it all using customized inline assembly,
but there are a few library routines available to make setting up C-64 IRQs and raster IRQs a lot easier (no assembly code required).
These routines are::
c64utils.set_irqvec()
c64utils.set_irqvec_excl()
c64utils.set_rasterirq( <raster line> )
c64utils.set_rasterirq_excl( <raster line> )
c64utils.restore_irqvec() ; set it back to the systems default irq handler
If you activate an IRQ handler with one of these, it expects the handler to be defined
as a subroutine ``irq`` in the module ``irq`` so like this::
~ irq {
sub irq() {
; ... irq handling here ...
}
}

View File

@ -5,7 +5,7 @@ TODO
Memory Block Operations integrated in language?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@todo list,string memory block operations?
list,string memory block operations?
- list operations (whole list, individual element)
operations: set, get, copy (from another list with the same length), shift-N(left,right), rotate-N(left,right)
@ -15,9 +15,9 @@ Memory Block Operations integrated in language?
- strings: identical operations as on lists.
these should call (or emit inline) optimized pieces of assembly code, so they run as fast as possible
these should call optimized pieces of assembly code, so they run as fast as possible
For now, we have the ``memcopy`` and ``memset`` builtin functions.
For now, we have the ``memcopy``, ``memset`` and ``strlen`` builtin functions.
@ -29,14 +29,49 @@ Add more compiler optimizations to the existing ones.
- on the language AST level
- on the StackVM intermediate code level
- on the final assembly source level
- can the parameter passing to subroutines be optimized to avoid copying?
- subroutines with 1 or 2 byte args (or 1 word arg) should be converted to asm calling convention with the args in A/Y register
this requires rethinking the way parameters are represented, simply injecting vardecls to
declare local variables for them is not always correct anymore
Also some library routines and code patterns could perhaps be optimized further
Eval stack redesign? (lot of work)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The eval stack is now a split lsb/msb stack using X as the stackpointer.
Is it easier/faster to just use a single page unsplit stack?
It could then even be moved into the zeropage to greatly reduce code size and slowness.
Or just move the LSB portion into a slab of the zeropage.
Allocate a fixed word in ZP that is the TOS so we can operate on TOS directly
without having to to index into the stack?
structs?
^^^^^^^^
A user defined struct type would be nice to group a bunch
of values together (and use it multiple times). Something like::
struct Point {
ubyte color
word[] vec = [0,0,0]
}
Point p1
Point p2
Point p3
p1.color = 3
p1.vec[2] = 2
Misc
^^^^
- sqrt() should have integer implementation as well, instead of relying on float SQRT for all argument types
- code generation for POW instruction
- make sure user-defined blocks come BEFORE library blocks (this helps zeropage variable allocations)
- are there any other missing instructions in the code generator?

View File

@ -44,10 +44,10 @@ sub start() {
sub print_notes(ubyte n1, ubyte n2) {
c64.CHROUT('\n')
c64scr.PLOT(n1/2, 24)
c64scr.plot(n1/2, 24)
c64.COLOR=7
c64.CHROUT('Q')
c64scr.PLOT(n2/2, 24)
c64scr.plot(n2/2, 24)
c64.COLOR=4
c64.CHROUT('Q')
}
@ -56,7 +56,7 @@ sub start() {
; details about the boulderdash music can be found here:
; https://www.elmerproductions.com/sp/peterb/sounds.html#Theme%20tune
uword[128] notes = [
uword[] notes = [
$1622, $1d26, $2229, $252e, $1424, $1f27, $2029, $2730,
$122a, $122c, $1e2e, $1231, $202c, $3337, $212d, $3135,
$1622, $162e, $161d, $1624, $1420, $1430, $1424, $1420,
@ -76,7 +76,7 @@ sub start() {
]
uword[59] music_freq_table = [
uword[] music_freq_table = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
732, 778, 826, 876, 928, 978, 1042, 1100, 1170, 1238, 1312, 1390, 1464, 1556,
1652, 1752, 1856, 1956, 2084, 2200, 2340, 2476, 2624, 2780, 2928, 3112, 3304,

View File

@ -1,4 +1,6 @@
%import c64utils
%zeropage basicsafe
~ main {

View File

@ -1,5 +1,6 @@
%import c64utils
%import c64flt
%zeropage basicsafe
~ main {

View File

@ -1,4 +1,5 @@
%import c64utils
%zeropage basicsafe
~ main {

View File

@ -1,4 +1,5 @@
%import c64utils
%zeropage basicsafe
~ main {

View File

@ -1,4 +1,5 @@
%import c64utils
%zeropage basicsafe
~ main {

View File

@ -1,4 +1,5 @@
%import c64utils
%zeropage basicsafe
~ main {

View File

@ -1,5 +1,6 @@
%import c64utils
%import c64flt
%zeropage basicsafe
~ main {

View File

@ -1,4 +1,5 @@
%import c64utils
%zeropage basicsafe
~ main {

View File

@ -1,4 +1,5 @@
%import c64utils
%zeropage basicsafe
~ main {

View File

@ -1,4 +1,5 @@
%import c64utils
%zeropage basicsafe
~ main {

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -8,9 +8,9 @@
const uword height = 25
; vertices
float[8] xcoor = [ -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0 ]
float[8] ycoor = [ -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0 ]
float[8] zcoor = [ -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0 ]
float[] xcoor = [ -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0 ]
float[] ycoor = [ -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0 ]
float[] zcoor = [ -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0 ]
; storage for rotated coordinates
float[len(xcoor)] rotatedx=0.0
@ -24,7 +24,7 @@
c64scr.clear_screenchars(32)
draw_edges()
time+=0.2
c64scr.PLOT(0,0)
c64scr.plot(0,0)
c64scr.print("3d cube! (float) ")
c64scr.print_ub(c64.TIME_LO)
c64scr.print(" jiffies/frame")

View File

@ -6,7 +6,7 @@
; it must start on an address aligned to 64 bytes.
%option force_output ; make sure the data in this block appears in the resulting program
ubyte[128] sprites = [
ubyte[] sprites = [
%00000000,%00000000,%00000000,
%00000000,%00111100,%00000000,
%00000000,%11111111,%00000000,
@ -64,9 +64,9 @@
const uword height = 200
; vertices
byte[8] xcoor = [ -100, -100, -100, -100, 100, 100, 100, 100 ]
byte[8] ycoor = [ -100, -100, 100, 100, -100, -100, 100, 100 ]
byte[8] zcoor = [ -100, 100, -100, 100, -100, 100, -100, 100 ]
byte[] xcoor = [ -100, -100, -100, -100, 100, 100, 100, 100 ]
byte[] ycoor = [ -100, -100, 100, 100, -100, -100, 100, 100 ]
byte[] zcoor = [ -100, 100, -100, 100, -100, 100, -100, 100 ]
; storage for rotated coordinates
word[len(xcoor)] rotatedx
@ -89,7 +89,7 @@
anglex-=500
angley+=217
anglez+=452
c64scr.PLOT(0,0)
c64scr.plot(0,0)
c64scr.print("3d cube! (sprites) ")
c64scr.print_ub(c64.TIME_LO)
c64scr.print(" jiffies/frame ")
@ -146,7 +146,7 @@
}
}
ubyte[8] spritecolors = [1,1,7,15,12,11,9,9]
ubyte[] spritecolors = [1,1,7,15,12,11,9,9]
for ubyte i in 0 to 7 {
word zc = rotatedz[i]

View File

@ -20,12 +20,12 @@
const uword height = 200
; vertices
float[8] xcoor = [ -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0 ]
float[8] ycoor = [ -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0 ]
float[8] zcoor = [ -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0 ]
float[] xcoor = [ -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0 ]
float[] ycoor = [ -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0 ]
float[] zcoor = [ -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0 ]
; edges (msb=from vertex, lsb=to vertex)
uword[12] edges = [$0001, $0103, $0302, $0200, $0405, $0507, $0706, $0604, $0004, $0105, $0206, $0307]
uword[] edges = [$0001, $0103, $0302, $0200, $0405, $0507, $0706, $0604, $0004, $0105, $0206, $0307]
; storage for rotated coordinates
float[len(xcoor)] rotatedx

View File

@ -7,9 +7,9 @@
const uword height = 25
; vertices
byte[8] xcoor = [ -40, -40, -40, -40, 40, 40, 40, 40 ]
byte[8] ycoor = [ -40, -40, 40, 40, -40, -40, 40, 40 ]
byte[8] zcoor = [ -40, 40, -40, 40, -40, 40, -40, 40 ]
byte[] xcoor = [ -40, -40, -40, -40, 40, 40, 40, 40 ]
byte[] ycoor = [ -40, -40, 40, 40, -40, -40, 40, 40 ]
byte[] zcoor = [ -40, 40, -40, 40, -40, 40, -40, 40 ]
; storage for rotated coordinates
word[len(xcoor)] rotatedx
@ -29,7 +29,7 @@
anglex+=1000
angley+=433
anglez+=907
c64scr.PLOT(0,0)
c64scr.plot(0,0)
c64scr.print("3d cube! (integer) ")
c64scr.print_ub(c64.TIME_LO)
c64scr.print(" jiffies/frame")
@ -68,7 +68,7 @@
}
}
ubyte[6] vertexcolors = [1,7,7,12,11,6]
ubyte[] vertexcolors = [1,7,7,12,11,6]
sub draw_edges() {

View File

@ -1,4 +1,5 @@
%import c64utils
%zeropage basicsafe
; This example computes the first 20 values of the Fibonacci sequence.
; It uses the feature that variables that don't have an initialization value,

View File

@ -1,6 +1,7 @@
%import c64lib
%import c64utils
%import c64flt
%zeropage basicsafe
~ main {
@ -31,7 +32,6 @@
float minutes = floor(clock_seconds / 60)
clock_seconds = floor(clock_seconds - minutes * 60.0)
; @todo implement strcpy/strcat/strlen?
c64scr.print("system time in ti$ is ")
c64flt.print_f(hours)
c64.CHROUT(':')

View File

@ -1,6 +1,8 @@
%import c64lib
%import c64utils
%import c64flt
%zeropage basicsafe
~ main {
const uword width = 30
@ -8,7 +10,7 @@
const ubyte max_iter = 16
sub start() {
c64scr.print("calculating mandelbrot fractal...\n")
c64scr.print("calculating mandelbrot fractal...")
c64.TIME_HI=0
c64.TIME_MID=0
@ -40,7 +42,7 @@
float duration = floor(((c64.TIME_LO as float)
+ 256.0*(c64.TIME_MID as float)
+ 65536.0*(c64.TIME_HI as float))/60.0)
c64scr.PLOT(0, 21)
c64scr.plot(0, 21)
c64scr.print("finished in ")
c64flt.print_f(duration)
c64scr.print(" seconds!\n")

View File

@ -1,5 +1,6 @@
%import c64utils
%import c64lib
%zeropage basicsafe
; The classic number guessing game.
; This version uses mostly high level subroutine calls and loops.
@ -28,7 +29,7 @@
c64scr.print("es")
c64scr.print(" left.\nWhat is your next guess? ")
c64scr.input_chars(input)
ubyte guess = c64utils.str2ubyte(input)
ubyte guess = lsb(c64utils.str2uword(input))
if guess==secretnumber {
return ending(true)

View File

@ -1,27 +1,34 @@
%import c64utils
%zeropage basicsafe
~ main {
ubyte[256] sieve
ubyte candidate_prime = 2
ubyte candidate_prime = 2 ; is increased in the loop
sub start() {
memset(sieve, 256, false) ; clear the sieve
memset(sieve, 256, false) ; clear the sieve, to reset starting situation on subsequent runs
; calculate primes
c64scr.print("prime numbers up to 255:\n\n")
ubyte amount=0
while true {
ubyte prime = find_next_prime()
if prime==0
break
c64scr.print_ub(prime)
c64scr.print(", ")
amount++
}
c64.CHROUT('\n')
c64scr.print("number of primes (expected 54): ")
c64scr.print_ub(amount)
c64.CHROUT('\n')
}
sub find_next_prime() -> ubyte {
while sieve[candidate_prime] {
candidate_prime++
if candidate_prime==0
@ -30,7 +37,7 @@
; found next one, mark the multiples and return it.
sieve[candidate_prime] = true
uword multiple = candidate_prime**2
uword multiple = candidate_prime
while multiple < len(sieve) {
sieve[lsb(multiple)] = true
multiple += candidate_prime

View File

@ -18,28 +18,24 @@
~ irq {
const ubyte barheight = 4
ubyte[13] colors = [6,2,4,5,15,7,1,13,3,12,8,11,9]
ubyte[] colors = [6,2,4,5,15,7,1,13,3,12,8,11,9]
ubyte color = 0
ubyte ypos = 0
sub irq() {
Y++ ; delay for alignment
Y++ ; delay for alignment
Y++ ; delay for alignment
Y++ ; delay for alignment
Y++ ; slight timing delay to avoid rasterline transition issues
ubyte rasterpos = c64.RASTER
if color!=len(colors) {
c64.EXTCOL = colors[color]
c64.RASTER = rasterpos+barheight
color++
c64.RASTER = rasterpos+barheight
}
else {
Y++ ; delay for alignment
Y++ ; delay for alignment
ypos += 2
c64.EXTCOL = 0
c64.RASTER = sin8u(ypos)/2+40
color = 0
c64.RASTER = sin8u(ypos)/2+40
}
}
}

View File

@ -1,5 +1,6 @@
%import c64utils
%import c64lib
%zeropage basicsafe
~ spritedata $0a00 {
@ -7,27 +8,27 @@
; it must start on an address aligned to 64 bytes.
%option force_output ; make sure the data in this block appears in the resulting program
ubyte[63] balloonsprite = [ %00000000,%01111111,%00000000,
%00000001,%11111111,%11000000,
%00000011,%11111111,%11100000,
%00000011,%11100011,%11100000,
%00000111,%11011100,%11110000,
%00000111,%11011101,%11110000,
%00000111,%11011100,%11110000,
%00000011,%11100011,%11100000,
%00000011,%11111111,%11100000,
%00000011,%11111111,%11100000,
%00000010,%11111111,%10100000,
%00000001,%01111111,%01000000,
%00000001,%00111110,%01000000,
%00000000,%10011100,%10000000,
%00000000,%10011100,%10000000,
%00000000,%01001001,%00000000,
%00000000,%01001001,%00000000,
%00000000,%00111110,%00000000,
%00000000,%00111110,%00000000,
%00000000,%00111110,%00000000,
%00000000,%00011100,%00000000 ]
ubyte[] balloonsprite = [ %00000000,%01111111,%00000000,
%00000001,%11111111,%11000000,
%00000011,%11111111,%11100000,
%00000011,%11100011,%11100000,
%00000111,%11011100,%11110000,
%00000111,%11011101,%11110000,
%00000111,%11011100,%11110000,
%00000011,%11100011,%11100000,
%00000011,%11111111,%11100000,
%00000011,%11111111,%11100000,
%00000010,%11111111,%10100000,
%00000001,%01111111,%01000000,
%00000001,%00111110,%01000000,
%00000000,%10011100,%10000000,
%00000000,%10011100,%10000000,
%00000000,%01001001,%00000000,
%00000000,%01001001,%00000000,
%00000000,%00111110,%00000000,
%00000000,%00111110,%00000000,
%00000000,%00111110,%00000000,
%00000000,%00011100,%00000000 ]
}
~ main {

622
examples/tehtriz.p8 Normal file
View File

@ -0,0 +1,622 @@
; TehTriz - a Tetris clone.
;
; features:
; holding area
; wall kick rotations
; shows next piece
; staged speed increase
; some simple sound effects
;
; @todo show ghost?
~ main {
const ubyte boardOffsetX = 14
const ubyte boardOffsetY = 3
const ubyte boardWidth = 10
const ubyte boardHeight = 20
const ubyte startXpos = boardOffsetX + 3
const ubyte startYpos = boardOffsetY - 2
uword lines
uword score
ubyte xpos
ubyte ypos
ubyte nextBlock
ubyte speedlevel
ubyte holding
ubyte holdingAllowed
sub start() {
@(650) = 128 ; set all keys to repeat
sound.init()
newGame()
drawBoard()
gameOver()
newgame:
newGame()
drawBoard()
spawnNextBlock()
waitkey:
if c64.TIME_LO>=(60-4*speedlevel) {
c64.TIME_LO = 0
drawBlock(xpos, ypos, 32) ; hide block
if blocklogic.noCollision(xpos, ypos+1) {
; slowly move the block down
ypos++
drawBlock(xpos, ypos, 160) ; show block on new position
} else {
; block can't move further down!
; check if the game area is full, if not, spawn the next block at the top.
if blocklogic.isGameOver(xpos, ypos) {
gameOver()
goto newgame
} else {
sound.blockrotate()
checkForLines()
spawnNextBlock()
score++
}
}
drawScore()
}
ubyte key=c64.GETIN()
if key==0 goto waitkey
keypress(key)
goto waitkey
}
sub keypress(ubyte key) {
if key==157 or key==',' {
; move left
drawBlock(xpos, ypos, 32)
if blocklogic.noCollision(xpos-1, ypos) {
xpos--
}
drawBlock(xpos, ypos, 160)
}
else if key==29 or key=='/' {
; move right
drawBlock(xpos, ypos, 32)
if blocklogic.noCollision(xpos+1, ypos) {
xpos++
}
drawBlock(xpos, ypos, 160)
}
else if key==17 or key=='.' {
; move down faster
drawBlock(xpos, ypos, 32)
if blocklogic.noCollision(xpos, ypos+1) {
ypos++
}
drawBlock(xpos, ypos, 160)
}
else if key==145 or key==' ' {
; drop down immediately
drawBlock(xpos, ypos, 32)
ubyte dropypos
for dropypos in ypos+1 to boardOffsetY+boardHeight-1 {
if not blocklogic.noCollision(xpos, dropypos) {
dropypos-- ; the furthest down that still fits
break
}
}
if dropypos>ypos {
ypos = dropypos
sound.blockdrop()
drawBlock(xpos, ypos, 160)
checkForLines()
spawnNextBlock()
score++
drawScore()
}
}
else if key=='z' { ; no joystick equivalent (there is only 1 fire button)
; rotate counter clockwise
drawBlock(xpos, ypos, 32)
if blocklogic.canRotateCCW(xpos, ypos) {
blocklogic.rotateCCW()
sound.blockrotate()
}
else if blocklogic.canRotateCCW(xpos-1, ypos) {
xpos--
blocklogic.rotateCCW()
sound.blockrotate()
}
else if blocklogic.canRotateCCW(xpos+1, ypos) {
xpos++
blocklogic.rotateCCW()
sound.blockrotate()
}
drawBlock(xpos, ypos, 160)
}
else if key=='x' {
; rotate clockwise
drawBlock(xpos, ypos, 32)
if blocklogic.canRotateCW(xpos, ypos) {
blocklogic.rotateCW()
sound.blockrotate()
}
else if blocklogic.canRotateCW(xpos-1, ypos) {
xpos--
blocklogic.rotateCW()
sound.blockrotate()
}
else if blocklogic.canRotateCW(xpos+1, ypos) {
xpos++
blocklogic.rotateCW()
sound.blockrotate()
}
drawBlock(xpos, ypos, 160)
}
else if key=='c' {
; hold
if holdingAllowed {
sound.swapping()
if holding<7 {
drawBlock(xpos, ypos, 32)
ubyte newholding = blocklogic.currentBlockNum
swapBlock(holding)
holding = newholding
holdingAllowed = false
} else {
holding = blocklogic.currentBlockNum
drawBlock(xpos, ypos, 32)
spawnNextBlock()
}
drawHoldBlock()
}
}
}
sub checkForLines() {
; check if line(s) are full -> flash/clear line(s) + add score + move rest down
ubyte[boardHeight] complete_lines
ubyte num_lines=0
memset(complete_lines, len(complete_lines), 0)
for ubyte linepos in boardOffsetY to boardOffsetY+boardHeight-1 {
if blocklogic.isLineFull(linepos) {
complete_lines[num_lines]=linepos
num_lines++
for ubyte x in boardOffsetX to boardOffsetX+boardWidth-1
c64scr.setcc(x, linepos, 160, 1)
}
}
if num_lines {
if num_lines>3
sound.lineclear_big()
else
sound.lineclear()
c64.TIME_LO=0
while c64.TIME_LO<20 {
; slight delay to flash the line
}
c64.TIME_LO=0
for ubyte linepos in complete_lines
if linepos and blocklogic.isLineFull(linepos)
blocklogic.collapse(linepos)
lines += num_lines
uword[] scores = [10, 25, 50, 100] ; can never clear more than 4 lines
score += scores[num_lines-1]
speedlevel = 1+lsb(lines/10)
drawScore()
}
}
sub gameOver() {
sound.gameover()
c64scr.plot(7, 7)
c64.CHROUT('U')
c64scr.print("────────────────────────")
c64.CHROUT('I')
c64scr.plot(7, 8)
c64scr.print("│*** g a m e o v e r ***│")
c64scr.plot(7, 9)
c64.CHROUT('J')
c64scr.print("────────────────────────")
c64.CHROUT('K')
c64scr.plot(7, 18)
c64.CHROUT('U')
c64scr.print("────────────────────────")
c64.CHROUT('I')
c64scr.plot(7, 19)
c64scr.print("│ f1 for new game │")
c64scr.plot(7, 20)
c64.CHROUT('J')
c64scr.print("────────────────────────")
c64.CHROUT('K')
while(c64.GETIN()!=133) {
; endless loop until user presses F1 to restart the game
}
}
sub newGame() {
lines = 0
score = 0
xpos = startXpos
ypos = startYpos
speedlevel = 1
nextBlock = rnd() % 7
holding = 255
holdingAllowed = true
}
sub swapBlock(ubyte newblock) {
c64.TIME_LO = 0
blocklogic.newCurrentBlock(newblock)
xpos = startXpos
ypos = startYpos
drawBlock(xpos, ypos, 160)
}
sub spawnNextBlock() {
swapBlock(nextBlock)
nextBlock = (rnd() + c64.RASTER) % 7
drawNextBlock()
holdingAllowed = true
}
sub drawBoard() {
c64.CLEARSCR()
c64.COLOR = 7
c64scr.plot(1,1)
c64scr.print("irmen's")
c64scr.plot(2,2)
c64scr.print("teh▁triz")
c64.COLOR = 5
c64scr.plot(6,4)
c64scr.print("hold:")
c64scr.plot(2,22)
c64scr.print("speed: ")
c64scr.plot(28,3)
c64scr.print("next:")
c64scr.plot(28,10)
c64scr.print("lines:")
c64scr.plot(28,14)
c64scr.print("score:")
c64.COLOR = 12
c64scr.plot(27,18)
c64scr.print("controls:")
c64.COLOR = 11
c64scr.plot(28,19)
c64scr.print(",/ move")
c64scr.plot(28,20)
c64scr.print("zx rotate")
c64scr.plot(29,21)
c64scr.print(". descend")
c64scr.plot(27,22)
c64scr.print("spc drop")
c64scr.plot(29,23)
c64scr.print("c hold")
c64scr.setcc(boardOffsetX-1, boardOffsetY-2, 255, 0) ; invisible barrier
c64scr.setcc(boardOffsetX-1, boardOffsetY-3, 255, 0) ; invisible barrier
c64scr.setcc(boardOffsetX+boardWidth, boardOffsetY-2, 255, 0) ; invisible barrier
c64scr.setcc(boardOffsetX+boardWidth, boardOffsetY-3, 255, 0) ; invisible barrier
c64scr.setcc(boardOffsetX-1, boardOffsetY-1, 108, 12)
c64scr.setcc(boardOffsetX+boardWidth, boardOffsetY-1, 123, 12)
c64scr.setcc(boardOffsetX+boardWidth, boardOffsetY-1, 123, 12)
c64scr.setcc(boardOffsetX-1, boardOffsetY+boardHeight, 124, 12)
c64scr.setcc(boardOffsetX+boardWidth, boardOffsetY+boardHeight, 126, 12)
ubyte i
for i in boardOffsetX+boardWidth-1 to boardOffsetX step -1 {
c64scr.setcc(i, boardOffsetY-3, 255, 0) ; invisible barrier
c64scr.setcc(i, boardOffsetY+boardHeight, 69, 11)
}
for i in boardOffsetY+boardHeight-1 to boardOffsetY step -1 {
c64scr.setcc(boardOffsetX-1, i, 89, 11)
c64scr.setcc(boardOffsetX+boardWidth, i, 84, 11)
}
ubyte[] colors = [6,8,7,5,4]
for i in len(colors)-1 to 0 step -1 {
for ubyte x in 5 to 0 step -1 {
c64scr.setcc(6+x-i, 11+2*i, 102, colors[i])
}
}
drawScore()
}
sub drawScore() {
c64.COLOR=1
c64scr.plot(30,11)
c64scr.print_uw(lines)
c64scr.plot(30,15)
c64scr.print_uw(score)
c64scr.plot(9,22)
c64scr.print_ub(speedlevel)
}
sub drawNextBlock() {
const ubyte nextBlockXpos = 29
const ubyte nextBlockYpos = 5
for ubyte x in nextBlockXpos+3 to nextBlockXpos step -1 {
c64scr.setcc(x, nextBlockYpos, ' ', 0)
c64scr.setcc(x, nextBlockYpos+1, ' ', 0)
}
; reuse the normal block draw routine (because we can't manipulate array pointers yet)
ubyte prev = blocklogic.currentBlockNum
blocklogic.newCurrentBlock(nextBlock)
drawBlock(nextBlockXpos, nextBlockYpos, 160)
blocklogic.newCurrentBlock(prev)
}
sub drawHoldBlock() {
const ubyte holdBlockXpos = 7
const ubyte holdBlockYpos = 6
for ubyte x in holdBlockXpos+3 to holdBlockXpos step -1 {
c64scr.setcc(x, holdBlockYpos, '@', 0)
c64scr.setcc(x, holdBlockYpos+1, '@', 0)
}
if holding < 7 {
; reuse the normal block draw routine (because we can't manipulate array pointers yet)
ubyte prev = blocklogic.currentBlockNum
blocklogic.newCurrentBlock(holding)
drawBlock(holdBlockXpos, holdBlockYpos, 160)
blocklogic.newCurrentBlock(prev)
}
}
sub drawBlock(ubyte x, ubyte y, ubyte character) {
for ubyte i in 15 to 0 step -1 {
ubyte c=blocklogic.currentBlock[i]
if c
c64scr.setcc((i&3)+x, (i/4)+y, character, c)
}
}
}
~ blocklogic {
ubyte currentBlockNum
ubyte[16] currentBlock
ubyte[16] rotated
; the 7 tetrominos
ubyte[] blockI = [0,0,0,0, ; cyan ; note: special rotation (around matrix center)
3,3,3,3,
0,0,0,0,
0,0,0,0]
ubyte[] blockJ = [6,0,0,0, ; blue
6,6,6,0,
0,0,0,0,
0,0,0,0]
ubyte[] blockL = [0,0,8,0, ; orange
8,8,8,0,
0,0,0,0,
0,0,0,0]
ubyte[] blockO = [0,7,7,0, ; yellow ; note: no rotation (square)
0,7,7,0,
0,0,0,0,
0,0,0,0]
ubyte[] blockS = [0,5,5,0, ; green
5,5,0,0,
0,0,0,0,
0,0,0,0]
ubyte[] blockT = [0,4,0,0, ; purple
4,4,4,0,
0,0,0,0,
0,0,0,0]
ubyte[] blockZ = [2,2,0,0, ; red
0,2,2,0,
0,0,0,0,
0,0,0,0]
uword[] blocks = [&blockI, &blockJ, &blockL, &blockO, &blockS, &blockT, &blockZ]
sub newCurrentBlock(ubyte block) {
currentBlockNum = block
memcopy(blocks[block], currentBlock, len(currentBlock))
}
sub rotateCW() {
; rotates the current block clockwise.
if currentBlockNum==0 {
; the 'I' block rotates a 4x4 matrix around the center
rotated[0] = currentBlock[12]
rotated[1] = currentBlock[8]
rotated[2] = currentBlock[4]
rotated[3] = currentBlock[0]
rotated[4] = currentBlock[13]
rotated[5] = currentBlock[9]
rotated[6] = currentBlock[5]
rotated[7] = currentBlock[1]
rotated[8] = currentBlock[14]
rotated[9] = currentBlock[10]
rotated[10] = currentBlock[6]
rotated[11] = currentBlock[2]
rotated[12] = currentBlock[15]
rotated[13] = currentBlock[11]
rotated[14] = currentBlock[7]
rotated[15] = currentBlock[3]
memcopy(rotated, currentBlock, len(currentBlock))
}
else if currentBlockNum!=3 {
; rotate all blocks (except 3, the square) around their center square in a 3x3 matrix
memset(rotated, len(rotated), 0)
rotated[0] = currentBlock[8]
rotated[1] = currentBlock[4]
rotated[2] = currentBlock[0]
rotated[4] = currentBlock[9]
rotated[5] = currentBlock[5]
rotated[6] = currentBlock[1]
rotated[8] = currentBlock[10]
rotated[9] = currentBlock[6]
rotated[10] = currentBlock[2]
memcopy(rotated, currentBlock, len(currentBlock))
}
}
sub rotateCCW() {
; rotates the current block counterclockwise.
if currentBlockNum==0 {
; the 'I' block rotates a 4x4 matrix around the center
rotated[0] = currentBlock[3]
rotated[1] = currentBlock[7]
rotated[2] = currentBlock[11]
rotated[3] = currentBlock[15]
rotated[4] = currentBlock[2]
rotated[5] = currentBlock[6]
rotated[6] = currentBlock[10]
rotated[7] = currentBlock[14]
rotated[8] = currentBlock[1]
rotated[9] = currentBlock[5]
rotated[10] = currentBlock[9]
rotated[11] = currentBlock[13]
rotated[12] = currentBlock[0]
rotated[13] = currentBlock[4]
rotated[14] = currentBlock[8]
rotated[15] = currentBlock[12]
memcopy(rotated, currentBlock, len(currentBlock))
}
else if currentBlockNum!=3 {
; rotate all blocks (except 3, the square) around their center square in a 3x3 matrix
memset(rotated, len(rotated), 0)
rotated[0] = currentBlock[2]
rotated[1] = currentBlock[6]
rotated[2] = currentBlock[10]
rotated[4] = currentBlock[1]
rotated[5] = currentBlock[5]
rotated[6] = currentBlock[9]
rotated[8] = currentBlock[0]
rotated[9] = currentBlock[4]
rotated[10] = currentBlock[8]
memcopy(rotated, currentBlock, len(currentBlock))
}
}
; For movement checking it is not needed to clamp the x/y coordinates,
; because we have to check for brick collisions anyway.
; The full play area is bordered by (in)visible characters that will collide.
; Collision is determined by reading the screen data directly.
sub canRotateCW(ubyte xpos, ubyte ypos) -> ubyte {
rotateCW()
ubyte nocollision = noCollision(xpos, ypos)
rotateCCW()
return nocollision
}
sub canRotateCCW(ubyte xpos, ubyte ypos) -> ubyte {
rotateCCW()
ubyte nocollision = noCollision(xpos, ypos)
rotateCW()
return nocollision
}
sub noCollision(ubyte xpos, ubyte ypos) -> ubyte {
for ubyte i in 15 to 0 step -1 {
if currentBlock[i] and c64scr.getchr(xpos + (i&3), ypos+i/4)!=32
return false
}
return true
}
sub isGameOver(ubyte xpos, ubyte ypos) -> ubyte {
main.drawBlock(xpos, ypos, 32)
ubyte result = ypos==main.startYpos and not noCollision(xpos, ypos+1)
main.drawBlock(xpos, ypos, 160)
return result
}
sub isLineFull(ubyte ypos) -> ubyte {
for ubyte x in main.boardOffsetX to main.boardOffsetX+main.boardWidth-1 {
if c64scr.getchr(x, ypos)==32
return false
}
return true
}
sub collapse(ubyte ypos) {
while(ypos>main.startYpos+1) {
for ubyte x in main.boardOffsetX+main.boardWidth-1 to main.boardOffsetX step -1 {
ubyte char = c64scr.getchr(x, ypos-1)
ubyte color = c64scr.getclr(x, ypos-1)
c64scr.setcc(x, ypos, char, color)
}
ypos--
}
}
}
~ sound {
sub init() {
c64.MVOL = 15
}
sub blockrotate() {
; soft click
c64.MVOL = 5
c64.AD1 = %00100010
c64.SR1 = %00000000
c64.FREQ1 = 15600
c64.CR1 = %10000000
c64.CR1 = %10000001
}
sub blockdrop() {
; swish
c64.MVOL = 5
c64.AD1 = %01010111
c64.SR1 = %00000000
c64.FREQ1 = 4600
c64.CR1 = %10000000
c64.CR1 = %10000001
}
sub swapping() {
; beep
c64.MVOL = 8
c64.AD1 = %01010111
c64.SR1 = %00000000
c64.FREQ1 = 5500
c64.CR1 = %00010000
c64.CR1 = %00010001
}
sub lineclear() {
; explosion
c64.MVOL = 15
c64.AD1 = %01100110
c64.SR1 = %00000000
c64.FREQ1 = 1600
c64.CR1 = %10000000
c64.CR1 = %10000001
}
sub lineclear_big() {
; big explosion
c64.MVOL = 15
c64.AD1 = %01101010
c64.SR1 = %00000000
c64.FREQ1 = 2600
c64.CR1 = %10000000
c64.CR1 = %10000001
}
sub gameover() {
; buzz
c64.MVOL = 15
c64.FREQ2 = 600
c64.AD2 = %00111010
c64.SR2 = %00000000
c64.CR2 = %00110000
c64.CR2 = %00110001
}
}

View File

@ -1,35 +1,28 @@
%import c64utils
%zeropage basicsafe
%option enable_floats
%import c64flt
~ main {
float[] fa = [1.1,2.2,3.3]
ubyte[] uba = [10,2,3,4]
byte[] ba = [-10,2,3,4]
uword[] uwa = [100,20,30,40]
word[] wa = [-100,20,30,40]
sub start() {
ubyte @zp ub = 22
byte @zp b = 22
word @zp w = 2222
uword @zp uw = 2222
float a
a=avg([1,2,3,4])
c64flt.print_f(a)
c64.CHROUT('\n')
a=avg([100,200,300,400])
c64flt.print_f(a)
c64.CHROUT('\n')
a=avg([1.1,2.2,3.3,4.4])
c64flt.print_f(a)
c64.CHROUT('\n')
byte nonzp1 = 42
byte nonzp2 = 42
byte nonzp3 = 42
foo.bar()
}
}
~ foo {
sub bar() {
ubyte @zp ub = 33
byte @zp b = 33
word @zp w = 3333
uword @zp uw = 3333
word nonzp1 = 4444
word nonzp2 = 4444
word nonzp3 = 4444
A=55
}
}

View File

@ -1,5 +1,6 @@
%import c64utils
%import c64lib
%zeropage basicsafe
~ spritedata $0a00 {
@ -7,27 +8,27 @@
; it must start on an address aligned to 64 bytes.
%option force_output ; make sure the data in this block appears in the resulting program
ubyte[63] balloonsprite = [ %00000000,%01111111,%00000000,
%00000001,%11111111,%11000000,
%00000011,%11111111,%11100000,
%00000011,%11100011,%11100000,
%00000111,%11011100,%11110000,
%00000111,%11011101,%11110000,
%00000111,%11011100,%11110000,
%00000011,%11100011,%11100000,
%00000011,%11111111,%11100000,
%00000011,%11111111,%11100000,
%00000010,%11111111,%10100000,
%00000001,%01111111,%01000000,
%00000001,%00111110,%01000000,
%00000000,%10011100,%10000000,
%00000000,%10011100,%10000000,
%00000000,%01001001,%00000000,
%00000000,%01001001,%00000000,
%00000000,%00111110,%00000000,
%00000000,%00111110,%00000000,
%00000000,%00111110,%00000000,
%00000000,%00011100,%00000000 ]
ubyte[] balloonsprite = [ %00000000,%01111111,%00000000,
%00000001,%11111111,%11000000,
%00000011,%11111111,%11100000,
%00000011,%11100011,%11100000,
%00000111,%11011100,%11110000,
%00000111,%11011101,%11110000,
%00000111,%11011100,%11110000,
%00000011,%11100011,%11100000,
%00000011,%11111111,%11100000,
%00000011,%11111111,%11100000,
%00000010,%11111111,%10100000,
%00000001,%01111111,%01000000,
%00000001,%00111110,%01000000,
%00000000,%10011100,%10000000,
%00000000,%10011100,%10000000,
%00000000,%01001001,%00000000,
%00000000,%01001001,%00000000,
%00000000,%00111110,%00000000,
%00000000,%00111110,%00000000,
%00000000,%00111110,%00000000,
%00000000,%00011100,%00000000 ]
}
~ main {

2
gradle.properties Normal file
View File

@ -0,0 +1,2 @@
org.gradle.caching=true

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

172
gradlew vendored Executable file
View File

@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

84
gradlew.bat vendored Normal file
View File

@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@ -1,7 +1,9 @@
#!/usr/bin/env bash
# compile using regular Kotlin sdk command line tool
echo "Compiling the parser..."
java -jar ./parser/antlr/lib/antlr-4.7.2-complete.jar -o ./parser/src/prog8/parser -Xexact-output-dir -no-listener -no-visitor -package prog8.parser ./parser/antlr/prog8.g4
java -jar ./parser/antlr/lib/antlr-4.7.2-complete.jar -o ./parser/src/prog8/parser -Xexact-output-dir -no-listener -no-visitor ./parser/antlr/prog8.g4
PARSER_CLASSES=./out/production/parser
@ -12,7 +14,7 @@ mkdir -p ${PARSER_CLASSES}
javac -d ${PARSER_CLASSES} -cp ${ANTLR_RUNTIME} ./parser/src/prog8/parser/prog8Lexer.java ./parser/src/prog8/parser/prog8Parser.java
echo "Compiling the compiler itself..."
kotlinc -verbose -include-runtime -d ${COMPILER_JAR} -cp ${ANTLR_RUNTIME}:${PARSER_CLASSES} ./compiler/src/prog8
JAVA_OPTS="-Xmx3G -Xms300M" kotlinc -verbose -include-runtime -d ${COMPILER_JAR} -jvm-target 1.8 -cp ${ANTLR_RUNTIME}:${PARSER_CLASSES} ./compiler/src/prog8
echo "Finalizing the compiler jar file..."
# add the antlr parser classes

View File

@ -1,5 +1,7 @@
set PROG8CLASSPATH=./out/production/compiler;./out/production/parser
set KOTLINPATH=%USERPROFILE%/.IdeaIC2018.3/config/plugins/Kotlin
@echo off
set PROG8CLASSPATH=./out/production/compiler_main;./out/production/parser_main
set KOTLINPATH=%USERPROFILE%/.IdeaIC2019.1/config/plugins/Kotlin
set LIBJARS=%KOTLINPATH%/lib/kotlin-stdlib.jar;%KOTLINPATH%/lib/kotlin-reflect.jar;./parser/antlr/lib/antlr-runtime-4.7.2.jar
java -cp %PROG8CLASSPATH%;%LIBJARS% prog8.CompilerMainKt %*

View File

@ -1,7 +1,7 @@
#!/usr/bin/env sh
PROG8CLASSPATH=./out/production/compiler:./out/production/parser
KOTLINPATH=${HOME}/.IntelliJIdea2018.3/config/plugins/Kotlin
PROG8CLASSPATH=./compiler/build/classes/kotlin/main:./compiler/build/resources/main:./parser/build/classes/java/main
KOTLINPATH=${HOME}/.IntelliJIdea2019.1/config/plugins/Kotlin
LIBJARS=${KOTLINPATH}/lib/kotlin-stdlib.jar:${KOTLINPATH}/lib/kotlin-reflect.jar:./parser/antlr/lib/antlr-runtime-4.7.2.jar
java -cp ${PROG8CLASSPATH}:${LIBJARS} prog8.CompilerMainKt $*

Some files were not shown because too many files have changed in this diff Show More