Compare commits

..
141 Commits
Author SHA1 Message Date
Irmen de Jong 47dbafacd4 correct version 2024-03-21 20:15:54 +01:00
Irmen de Jong 5b6811d073 not separate 2024-03-21 20:07:44 +01:00
Irmen de Jong 7516116bb7 last attempt for hash build step 2024-03-21 20:04:07 +01:00
Irmen de Jong e6014ea4dd version 10.3.1 2024-03-21 19:32:16 +01:00
Irmen de Jong 362abfe284 ci 2024-03-20 22:46:18 +01:00
Irmen de Jong ad4880997a no operand swap on logical expressions with shortcircuit evaluation (and,or are no longer associative!) 2024-03-20 22:34:48 +01:00
Irmen de Jong 592becc126 allow %breakpoint also in if/else blocks and other anonymous scopes 2024-03-19 23:31:21 +01:00
Irmen de Jong c38765301e gfx2 screenmode tweak 2024-03-19 01:21:56 +01:00
Irmen de Jong 5f27426f59 only on release event 2024-03-17 23:35:53 +01:00
Irmen de Jong d924f8bff8 another attempt to get the hashes working 2024-03-17 23:31:04 +01:00
Irmen de Jong d14c61b160 added string.findstr(). string.find() returns 255 if not found.
also fix string assignment bug for sub args.
2024-03-17 23:18:33 +01:00
Irmen de Jong fe2b67998c vm: fix load_raw, fix rng bug in textelite (carry flag shifting...) 2024-03-17 16:46:26 +01:00
Irmen de Jong 04df3c9f7f vm: implemented in-place array multiplication better 2024-03-17 13:39:05 +01:00
Irmen de Jong de3d0b40dc fixed vm problem with branching instructions in global init chunk 2024-03-17 13:22:17 +01:00
Irmen de Jong 4db4a5f1b2 vm: txt.width() and height() now return the actual console terminal width and height if possible. 2024-03-16 22:40:08 +01:00
Irmen de Jong 5a0524ff4d various fixes 2024-03-16 18:48:06 +01:00
Irmen de Jong 5b7801eea1 added crc verifications to diskspeed 2024-03-16 16:26:39 +01:00
Irmen de Jong fbe231793b optimized and added "streaming" crc32 and crc16 routines to math module. Return value is put in different register now! r14+r15 instead of r0+r1! 2024-03-16 01:07:03 +01:00
Irmen de Jong 6a9269111e some changes in SMC routines for the cx16:
sys.poweroff_system() moved to cx16
sys.set_leds_brightness() moved to cx16 and changed to set_led_brightness, you can only change the activity led brightness.
2024-03-15 23:00:14 +01:00
Irmen de Jong a94cfd34f5 don't apply absorption law on functioncall operands 2024-03-15 01:04:27 +01:00
Irmen de Jong 28eae5a0fd updated diskspeed example to deal with increased I/O speeds 2024-03-15 00:37:13 +01:00
Irmen de Jong 1818738fc8 fixed potential bug in cx16.kbdbuf_clear() is and it is now cbm.kbdbuf_clear() and is available on all cbm like targets 2024-03-14 22:12:29 +01:00
Irmen de Jong 7e1e7a0780 fix conv.str_ub and conv.str_b for missing tens digits 2024-03-13 23:03:25 +01:00
Irmen de Jong 1fc79ff6dd implement the missing in-place array operators for split word arrays and numeric operand 2024-03-13 21:16:49 +01:00
Irmen de Jong 3535c1acda fix broken boolean != comparison optimization 2024-03-13 20:23:42 +01:00
Irmen de Jong 33c8caac8f get rid of containment expression restriction 2024-03-12 23:39:54 +01:00
Irmen de Jong 51d708bbdd fix monogfx issue 2024-03-12 23:27:15 +01:00
Irmen de Jong a5a918df84 update docs about boolean type 2024-03-12 18:54:56 +01:00
Irmen de Jong 820541e427 fixed and optimized pointervar indexed in-place operations 2024-03-11 23:27:48 +01:00
Irmen de Jong e63a8f0c01 fix vm textio prefix type 2024-03-11 22:22:30 +01:00
Irmen de Jong c11a9b8709 fix callgraph issue when module gets removed by optimizations 2024-03-11 20:34:22 +01:00
Irmen de Jong 80f39e8097 Merge branch 'booleans'
# Conflicts:
#	compiler/res/prog8lib/cx16/monogfx.p8
#	compiler/res/prog8lib/virtual/monogfx.p8
#	compiler/src/prog8/compiler/astprocessing/BoolRemover.kt
#	compiler/test/TestTypecasts.kt
#	docs/source/todo.rst
#	examples/cx16/highresbitmap.p8
#	examples/test.p8
#	httpCompilerService/src/prog8/http/TestHttp.kt
2024-03-11 01:00:48 +01:00
Irmen de Jong 2a8b65e29c test str to uword change in function params 2024-03-10 23:48:58 +01:00
Irmen de Jong 4bdf50145e recognise \t character (TAB) in string literals (note: only valid in iso encoding) 2024-03-10 13:20:09 +01:00
Irmen de Jong 3a9919a377 implemented a couple more diskio routines for the VM target 2024-03-09 17:36:39 +01:00
Irmen de Jong eef8ae00b8 replace str return type by uword 2024-03-09 15:38:46 +01:00
Irmen de Jong ed15fac691 improve IR error message 2024-03-09 13:38:25 +01:00
Irmen de Jong f739e679e4 added sys.exit2 and sys.exit3 to set more result registers at program exit
todo
2024-03-09 03:30:05 +01:00
Irmen de Jong fc0fae8caf tweak the redundant beq asm optimizer a bit more 2024-03-09 00:05:06 +01:00
Irmen de Jong f46896fd74 attempt to no longer have BuildVersion.kt in git, blocking easy pulls
(cherry picked from commit bfcf07c1a2)
2024-03-08 23:36:35 +01:00
Irmen de Jong 52649a8e4f conv routines now return the string buffer address. 2024-03-08 02:12:46 +01:00
Irmen de Jong bdfb01f6a0 VM: implemented a few core routines in diskio (load/save)
textelite can now load and save your progress like it already could in the real version
2024-03-06 23:21:01 +01:00
Irmen de Jong 1137e57393 update VTUI lib 2024-03-06 21:39:30 +01:00
Irmen de Jong 267ea13e8c clearer error msg 2024-03-06 20:10:10 +01:00
Irmen de Jong 04f7b772a3 lib updates, removed unused and obsolete http and dbus modules 2024-03-05 23:42:35 +01:00
Irmen de Jong 42c7569791 doc 2024-03-05 22:58:27 +01:00
Irmen de Jong 6d29b00a80 fix monogfx horizontal line and text draw in invert mode 2024-03-05 22:38:34 +01:00
Irmen de Jong 9f1bd2d7d6 asmoptimizer that removes redundant branches on boolean in A 2024-03-05 20:37:15 +01:00
Irmen de Jong 9826d7c494 optimize certain boolean comparisons more 2024-03-05 03:09:53 +01:00
Irmen de Jong c6bf57b390 non strict bools should also replace not byte with byte==0 2024-03-04 23:25:34 +01:00
Irmen de Jong bfcf07c1a2 attempt to no longer have BuildVersion.kt in git, blocking easy pulls 2024-03-04 20:45:59 +01:00
Irmen de Jong 4d7e96d423 add monogfx inverted (eor) draw mode 2024-03-03 23:28:37 +01:00
Irmen de Jong 449461e412 tweak monogfx stipple plot 2024-03-03 21:47:42 +01:00
Irmen de Jong 607275ec66 tweak 2024-03-03 19:52:46 +01:00
Irmen de Jong e55cde2a81 more nonstrictbool conversions 2024-03-03 19:43:48 +01:00
Irmen de Jong 84afb374e6 nostrictbool array conversions 2024-03-03 17:48:52 +01:00
Irmen de Jong da1620807f fixed all todos in ifelse gen 2024-03-03 12:10:42 +01:00
Irmen de Jong f39ef8f565 optimize byte comparison assignment to use rol trick instead of branching 2024-03-03 00:01:14 +01:00
Irmen de Jong fe8b6e820c getting rid of problematic fallback (infinite recursion) 2024-03-02 23:19:55 +01:00
Irmen de Jong f29d24e96a fixup split words array comparisons 2024-03-02 23:19:55 +01:00
Irmen de Jong 620ffe54ec asm optimizer: don't remove labels. remove redundant cmp/cpx/cpy instructions. 2024-03-02 23:19:53 +01:00
Irmen de Jong ceaa4cd07d array issue 2024-03-02 23:19:39 +01:00
Irmen de Jong af17f903ee fix that if not fcall() wasn't transformed to a conditional branch instruction 2024-03-02 23:19:39 +01:00
Irmen de Jong c532e28841 fix several remaining bool return values in library routines 2024-03-02 23:19:39 +01:00
Irmen de Jong dba0846866 optimize word >, word <= 2024-03-02 23:19:39 +01:00
Irmen de Jong bed629998a fix large code for some compares 2024-03-02 23:19:39 +01:00
Irmen de Jong bc2ede76bf tweak to byte compares 2024-03-02 23:19:39 +01:00
Irmen de Jong 2a1fec2ed2 fix codegen error for comparisons 2024-03-02 23:19:39 +01:00
Irmen de Jong 004048e5a7 fix IR codegen error for b=float>value 2024-03-02 23:19:39 +01:00
Irmen de Jong b941d6f1e4 new comparison tests 2024-03-02 23:19:39 +01:00
Irmen de Jong 37b346740b fix 6502 casting uword and float to bool 2024-03-02 23:19:39 +01:00
Irmen de Jong f5e332daf7 remove redundant IR instructions like SNZ 2024-03-02 23:19:39 +01:00
Irmen de Jong fe9a9fc5cb new if tests 2024-03-02 23:19:39 +01:00
Irmen de Jong cc57477b99 IR: support for indirect jump after if 2024-03-02 23:19:39 +01:00
Irmen de Jong a1574a7187 added txt.print_bool, several fixes 2024-03-02 23:19:39 +01:00
Irmen de Jong a5110b1f96 improved De Morgan rewrite rules 2024-03-02 23:19:39 +01:00
Irmen de Jong 006713fe13 optimize boolean to ubyte assignment (skip type cast) 2024-03-02 23:19:39 +01:00
Irmen de Jong 7868e672e0 ifelse more 2024-03-02 23:19:39 +01:00
Irmen de Jong e1a133c2c0 ifelse more 2024-03-02 23:19:39 +01:00
Irmen de Jong c77cd0da39 ifelse more 2024-03-02 23:19:39 +01:00
Irmen de Jong 577333f2c4 new ifelse codegen 2024-03-02 23:19:39 +01:00
Irmen de Jong 7d8cdcbfea more bool fixes and optimizations in codegen 2024-03-02 23:19:39 +01:00
Irmen de Jong c5c4c6f111 start of new ifelse 2024-03-02 23:19:39 +01:00
Irmen de Jong 73be754680 move in place assignment functions back to AssignmentGen 2024-03-02 23:19:39 +01:00
Irmen de Jong acd841dbb6 bool changes in 6502 assignment codegen 2024-03-02 23:19:37 +01:00
Irmen de Jong 6b52ba9397 6502 asmgen 2024-03-02 23:19:20 +01:00
Irmen de Jong 10d12f73d6 IR/VM: testing the boolean changes, added in-place and/or. 2024-03-02 23:19:20 +01:00
Irmen de Jong cd9119655c IR codegen 2024-03-02 23:19:20 +01:00
Irmen de Jong 41afeccd51 compiler stuff 2024-03-02 23:19:20 +01:00
Irmen de Jong 6b87cbb703 optimizers 2024-03-02 23:19:20 +01:00
Irmen de Jong 32afcbfe42 compilerAst BOOL type changes 2024-03-02 23:19:20 +01:00
Irmen de Jong bc2b38daf4 added PtBool and other changes to intermediate Ast 2024-03-02 23:19:20 +01:00
Irmen de Jong f40b7b62bb updated unit tests and some basic changes for them 2024-03-02 23:19:20 +01:00
Irmen de Jong 1ca3f64bf0 libraries: add ==0 or !=0 to expressions that depend on implicit conversion from byte to bool 2024-03-02 23:19:20 +01:00
Irmen de Jong 92527b4c1d examples: add ==0 or !=0 to expressions that depend on implicit conversion from byte to bool 2024-03-02 23:19:18 +01:00
Irmen de Jong c48012c385 tweak ifelse > and <= order 2024-03-02 17:01:31 +01:00
Irmen de Jong a282b17286 added asm optimizer for <= byte (bcc+beq->bcs) 2024-03-02 15:34:54 +01:00
Irmen de Jong 58d9463f16 consolidate word comparison codegen 2024-03-02 14:00:12 +01:00
Irmen de Jong 047decd552 consolidate byte comparison codegen 2024-03-01 22:21:22 +01:00
Irmen de Jong 82e0877e64 readme 2024-03-01 00:41:11 +01:00
Irmen de Jong 040d75dafa VM now supports indirect jump instruction 2024-02-22 16:19:26 +01:00
Irmen de Jong 4e1686f6e3 fix warnings in gradle build scripts 2024-02-22 10:34:51 +01:00
Irmen de Jong b5e691f367 IR: fix chunk reachability: via unchopped chunk label directly so that they don't get removed 2024-02-21 23:18:57 +01:00
Irmen de Jong 325f55f22d doc refs duplicates fix 2024-02-21 21:29:17 +01:00
Irmen de Jong 9724f2db7d readthedocs 2024-02-21 21:23:18 +01:00
Irmen de Jong 5f20f321f0 readthedocs tweaks 2024-02-21 21:05:38 +01:00
Irmen de Jong d4b087ea3f added txt.bell() to make terminal beep/bell sound on most systems 2024-02-21 17:35:37 +01:00
Irmen de Jong 8ff10724d1 fix some docs hyperlinks 2024-02-21 17:11:36 +01:00
Irmen de Jong 1581381467 streamline 2024-02-20 23:35:04 +01:00
Irmen de Jong 96b5a30f60 slightly less strict 2024-02-20 23:01:51 +01:00
Irmen de Jong 0e17a0474a added type check to catch invalid comparisons, fix maze example 2024-02-20 22:53:15 +01:00
Irmen de Jong b27368175d get rid of problematic rewrite of certain for loops that were causing the end expression to be evaluated every loop iteration 2024-02-20 21:03:21 +01:00
Irmen de Jong aba36f7c92 update github actions 2024-02-20 01:07:39 +01:00
Mike McGann a3fa946300 fix typo: reaturn -> return (#126) 2024-02-18 22:25:26 +01:00
Irmen de Jong 01bbc2234e fix absorption law optimization, add 2 additional optimizations 2024-02-14 23:12:54 +01:00
adiee5 58e1864144 Mention AUR package in documentation (#125)
Additionally, small fixes it nano highlighting
2024-02-12 20:42:00 +01:00
Irmen de Jong 88458f5355 faster array copy and fix for length 256 2024-02-11 23:57:38 +01:00
Irmen de Jong a4f697bae1 faster 2024-02-10 23:56:55 +01:00
Irmen de Jong 8201408f16 fix element size calc when copying array 2024-02-10 22:58:44 +01:00
Irmen de Jong 8b8caa1c2e added math.randrange() and math.randrangew() 2024-02-10 22:16:13 +01:00
Irmen de Jong 4dc50cb551 fix unused subroutine removal not removing all unused subs 2024-02-10 21:26:53 +01:00
Irmen de Jong 5522a305ab add -dumpvars option to dump all allocated variables (zp, normal ram, etc) 2024-02-10 18:42:31 +01:00
Irmen de Jong d7f72056fc rest of the array copying 2024-02-10 17:16:06 +01:00
Irmen de Jong 64c9c9b7fe hash only on release? 2024-02-10 02:11:39 +01:00
Irmen de Jong 98e1c843e4 hash only on release? 2024-02-10 02:07:37 +01:00
Irmen de Jong 906d9d858c implementing the array copys 2024-02-10 01:40:36 +01:00
Irmen de Jong 16c1309df1 try to add a hash to the artifact 2024-02-10 00:38:32 +01:00
Irmen de Jong 6eacf1bddd added a few more IR syscalls for often used copy routines 2024-02-09 23:56:44 +01:00
Irmen de Jong 6c8c8e11cc fix cpu register overwrites when using @(...) arguments to an asmsub 2024-02-09 19:55:35 +01:00
Irmen de Jong e941d2665a preparing for new array copy codegen 2024-02-09 18:22:41 +01:00
Irmen de Jong 68669dbef0 fix & of pointervar indexing 2024-02-08 23:03:53 +01:00
Irmen de Jong 6a48de9a9f IR: fix & of array-element 2024-02-08 21:27:53 +01:00
Irmen de Jong 9d6d98930b fix ast printing of & array-element 2024-02-08 21:27:53 +01:00
adiee5 3cc858db12 Adding Syntax Higlighting for nano (#123) 2024-02-08 21:27:07 +01:00
Irmen de Jong 386a391fd9 added string.lstripped() and string.ltrimmed() 2024-02-07 23:07:45 +01:00
Irmen de Jong d33aed4ed5 added txt.petscii2scr() and txt.petscii2scr_str() 2024-02-07 22:36:43 +01:00
Irmen de Jong 73ec8c31ad fix 6502 code for zp pointer lookup clobbering registers. 2024-02-07 22:09:04 +01:00
Irmen de Jong 24944ad49e added string.strip() and string.trim() and l/r variants.
fixed memsizer for pointers-to-ubyte.
2024-02-07 02:09:08 +01:00
Irmen de Jong 26ed231f61 version 2024-02-07 00:03:39 +01:00
Irmen de Jong 8485b8429f optimizing +=1/-=1 2024-02-06 23:49:40 +01:00
Irmen de Jong 358215e4dd removed postIncrDecr (still allow ++/-- to be parsed into +=1/-=1) 2024-02-06 18:50:08 +01:00
244 changed files with 12907 additions and 10069 deletions

No files matched your search

+19 -4
View File
@@ -10,7 +10,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: build and install recent 64tass
run: |
@@ -21,16 +21,31 @@ jobs:
sudo make install
- name: Set up JDK 11
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
java-version: 11
distribution: adopt
- name: Build and test with Gradle
run: ./gradlew build shadowJar --no-daemon
- name: Create compiler shadowJar artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: prog8-compiler-jar-zipped
path: compiler/build/libs/*-all.jar
- name: Calculate hash
uses: MCJack123/ghaction-generate-release-hashes@v4
if: github.event_name == 'release'
with:
get-assets: true
hash-type: sha256
file-name: hashes.txt
- name: Upload hashes
uses: actions/upload-artifact@v4
if: github.event_name == 'release'
with:
name: Artifact Hashes
path: hashes.txt
-1
View File
@@ -30,7 +30,6 @@ parsetab.py
compiler/lib/
.gradle
**/BuildVersion.kt
/prog8compiler.jar
sd*.img
*.d64
+9
View File
@@ -9,6 +9,15 @@
</inspection_tool>
<inspection_tool class="IncompleteDestructuring" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PyInterpreterInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredPackages">
<value>
<list size="1">
<item index="0" class="java.lang.String" itemvalue="sphinx_rtd_theme" />
</list>
</value>
</option>
</inspection_tool>
<inspection_tool class="SpellCheckingInspection" enabled="true" level="TYPO" enabled_by_default="true">
<option name="processCode" value="false" />
<option name="processLiterals" value="true" />
-25
View File
@@ -1,25 +0,0 @@
<component name="libraryTable">
<library name="github.hypfvieh.dbus.java" type="repository">
<properties maven-id="com.github.hypfvieh:dbus-java:3.3.2" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/github/hypfvieh/dbus-java/3.3.2/dbus-java-3.3.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-unixsocket/0.38.17/jnr-unixsocket-0.38.17.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-ffi/2.2.11/jnr-ffi-2.2.11.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jffi/1.3.9/jffi-1.3.9.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jffi/1.3.9/jffi-1.3.9-native.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm/9.2/asm-9.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-commons/9.2/asm-commons-9.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-analysis/9.2/asm-analysis-9.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-tree/9.2/asm-tree-9.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-util/9.2/asm-util-9.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-a64asm/1.0.0/jnr-a64asm-1.0.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-x86asm/1.0.2/jnr-x86asm-1.0.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-constants/0.10.3/jnr-constants-0.10.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-enxio/0.32.13/jnr-enxio-0.32.13.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-posix/3.1.15/jnr-posix-3.1.15.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
-10
View File
@@ -1,10 +0,0 @@
<component name="libraryTable">
<library name="glassfish.javax.json" type="repository">
<properties include-transitive-deps="false" maven-id="org.glassfish:javax.json:1.1.4" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/javax.json/1.1.4/javax.json-1.1.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
+3 -6
View File
@@ -1,13 +1,10 @@
<component name="libraryTable">
<library name="michael.bull.kotlin.result.jvm" type="repository">
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18" />
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.20" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/1.1.18/kotlin-result-jvm-1.1.18.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.8.10/kotlin-stdlib-jdk8-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.8.10/kotlin-stdlib-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/1.1.20/kotlin-result-jvm-1.1.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.22/kotlin-stdlib-1.9.22.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.8.10/kotlin-stdlib-jdk7-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.8.10/kotlin-stdlib-common-1.8.10.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
-11
View File
@@ -1,11 +0,0 @@
<component name="libraryTable">
<library name="slf4j.simple" type="repository">
<properties maven-id="org.slf4j:slf4j-simple:2.0.11" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-simple/2.0.11/slf4j-simple-2.0.11.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/2.0.11/slf4j-api-2.0.11.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
-18
View File
@@ -1,18 +0,0 @@
<component name="libraryTable">
<library name="takes" type="repository">
<properties maven-id="org.takes:takes:1.24.4" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/takes/takes/1.24.4/takes-1.24.4.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/cactoos/cactoos/0.54.0/cactoos-0.54.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/javax/xml/bind/jaxb-api/2.4.0-b180830.0359/jaxb-api-2.4.0-b180830.0359.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/javax/activation/javax.activation-api/1.2.0/javax.activation-api-1.2.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/sun/xml/bind/jaxb-core/4.0.0/jaxb-core-4.0.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/jakarta/xml/bind/jakarta.xml.bind-api/4.0.0/jakarta.xml.bind-api-4.0.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/jakarta/activation/jakarta.activation-api/2.1.0/jakarta.activation-api-2.1.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/angus/angus-activation/1.0.0/angus-activation-1.0.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/sun/xml/bind/jaxb-impl/4.0.0/jaxb-impl-4.0.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
-2
View File
@@ -10,10 +10,8 @@
<module fileurl="file://$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" filepath="$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" />
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
<module fileurl="file://$PROJECT_DIR$/compilerAst/compilerAst.iml" filepath="$PROJECT_DIR$/compilerAst/compilerAst.iml" />
<module fileurl="file://$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.iml" filepath="$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.iml" />
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
<module fileurl="file://$PROJECT_DIR$/httpCompilerService/httpCompilerService.iml" filepath="$PROJECT_DIR$/httpCompilerService/httpCompilerService.iml" />
<module fileurl="file://$PROJECT_DIR$/intermediate/intermediate.iml" filepath="$PROJECT_DIR$/intermediate/intermediate.iml" />
<module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
<module fileurl="file://$PROJECT_DIR$/virtualmachine/virtualmachine.iml" filepath="$PROJECT_DIR$/virtualmachine/virtualmachine.iml" />
+11 -9
View File
@@ -9,17 +9,19 @@ version: 2
build:
os: ubuntu-22.04
tools:
python: "3.11"
# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/source/conf.py
# If using Sphinx, optionally build your docs in additional formats such as PDF
formats:
- pdf
python: "3.12"
# Optionally declare the Python requirements required to build your docs
python:
install:
- requirements: docs/requirements.txt
# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/source/conf.py
fail_on_warning: true
# If using Sphinx, optionally build your docs in additional formats such as PDF
formats:
- pdf
- epub
+12
View File
@@ -0,0 +1,12 @@
# super simple Makefile to lauch the main gradle targets to build and/or test the prog8 compiler
.PHONY: all test
all:
gradle installdist installshadowdist
@echo "compiler launch script can be found here: compiler/build/install/compiler-shadow/bin/p8compile"
test:
gradle build
@echo "compiler launch script can be found here: compiler/build/install/compiler-shadow/bin/p8compile"
+5 -2
View File
@@ -29,6 +29,8 @@ How to get it/build it
- Or, if you want/need a bleeding edge development version, you can:
- download a build artifact zipfile from a recent [github action build](https://github.com/irmen/prog8/actions).
- you can also compile it yourself from source. [Instructions here](https://prog8.readthedocs.io/en/latest/compiling.html).
- Alternatively, you can also install the compiler as a package on some linux distros:
- Arch (via AUR): [`prog8`](https://aur.archlinux.org/packages/prog8)
Community
---------
@@ -49,8 +51,8 @@ GNU GPL 3.0 (see file LICENSE), with exception for generated code:
What does Prog8 provide?
------------------------
- reduction of source code length over raw assembly
- fast execution speed due to compilation to native assembly code. It's possible to write certain raster interrupt 'demoscene' effects purely in Prog8.
- all advantages of a higher level language over having to write assembly code manually
- programs run very fast because compilation to native machine code. It's possible to write games purely in Prog8, and even certain raster interrupt 'demoscene' effects.
- modularity, symbol scoping, subroutines
- various data types other than just bytes (16-bit words, floats, strings)
- floating point math is supported if the target system provides floating point library routines (C64 and Cx16 both do)
@@ -58,6 +60,7 @@ What does Prog8 provide?
- automatic static variable allocations, automatic string and array variables and string sharing
- subroutines with input parameters and result values
- high-level program optimizations
- no need for forward declarations
- small program boilerplate/compilersupport overhead
- programs can be run multiple times without reloading because of automatic variable (re)initializations.
- conditional branches
+1 -1
View File
@@ -26,7 +26,7 @@ compileTestKotlin {
dependencies {
// should have no dependencies to other modules
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.20"
}
sourceSets {
+3 -2
View File
@@ -195,7 +195,7 @@ class StStaticVariable(name: String,
require(onetimeInitializationArrayValue.isEmpty() ||onetimeInitializationArrayValue.size==length)
}
if(onetimeInitializationNumericValue!=null) {
require(dt in NumericDatatypes)
require(dt in NumericDatatypes || dt==DataType.BOOL)
}
if(onetimeInitializationArrayValue!=null) {
require(dt in ArrayDatatypes)
@@ -221,6 +221,7 @@ class StMemVar(name: String,
StNode(name, StNodeType.MEMVAR, astNode) {
init{
require(dt!=DataType.BOOL && dt!=DataType.ARRAY_BOOL)
if(dt in ArrayDatatypes || dt == DataType.STR)
require(length!=null) { "memory mapped array or string must have known length" }
}
@@ -250,7 +251,7 @@ class StRomSub(name: String,
class StSubroutineParameter(val name: String, val type: DataType)
class StRomSubParameter(val register: RegisterOrStatusflag, val type: DataType)
class StArrayElement(val number: Double?, val addressOfSymbol: String?)
class StArrayElement(val number: Double?, val addressOfSymbol: String?, val boolean: Boolean?)
typealias StString = Pair<String, Encoding>
typealias StArray = List<StArrayElement>
+4 -3
View File
@@ -128,10 +128,11 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
is PtAddressOf -> {
if(it.isFromArrayElement)
TODO("address-of array element $it in initial array value")
StArrayElement(null, it.identifier.name)
StArrayElement(null, it.identifier.name, null)
}
is PtIdentifier -> StArrayElement(null, it.name)
is PtNumber -> StArrayElement(it.number, null)
is PtIdentifier -> StArrayElement(null, it.name, null)
is PtNumber -> StArrayElement(it.number, null, null)
is PtBool -> StArrayElement(null, null, it.value)
else -> throw AssemblyError("invalid array element $it")
}
}
+1
View File
@@ -67,6 +67,7 @@ class PtProgram(
children.asSequence().filterIsInstance<PtBlock>()
fun entrypoint(): PtSub? =
// returns the main.start subroutine if it exists
allBlocks().firstOrNull { it.name == "main" || it.name=="p8b_main" }
?.children
?.firstOrNull { it is PtSub && (it.name == "start" || it.name=="main.start" || it.name=="p8s_start" || it.name=="p8b_main.p8s_start") } as PtSub?
+38 -11
View File
@@ -9,8 +9,6 @@ import kotlin.math.truncate
sealed class PtExpression(val type: DataType, position: Position) : PtNode(position) {
init {
if(type==DataType.BOOL)
throw IllegalArgumentException("bool should have become ubyte @$position")
if(type==DataType.UNDEFINED) {
@Suppress("LeakingThis")
when(this) {
@@ -49,6 +47,7 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
is PtMachineRegister -> other is PtMachineRegister && other.type==type && other.register==register
is PtMemoryByte -> other is PtMemoryByte && other.address isSameAs address
is PtNumber -> other is PtNumber && other.type==type && other.number==number
is PtBool -> other is PtBool && other.value==value
is PtPrefix -> other is PtPrefix && other.type==type && other.operator==operator && other.value isSameAs value
is PtRange -> other is PtRange && other.type==type && other.from==from && other.to==to && other.step==step
is PtTypeCast -> other is PtTypeCast && other.type==type && other.value isSameAs value
@@ -71,7 +70,7 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
}
}
fun asConstInteger(): Int? = (this as? PtNumber)?.number?.toInt()
fun asConstInteger(): Int? = (this as? PtNumber)?.number?.toInt() ?: (this as? PtBool)?.asInt()
fun isSimple(): Boolean {
return when(this) {
@@ -90,6 +89,7 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
is PtIdentifier -> true
is PtMachineRegister -> true
is PtMemoryByte -> address is PtNumber || address is PtIdentifier
is PtBool -> true
is PtNumber -> true
is PtPrefix -> value.isSimple()
is PtRange -> true
@@ -116,6 +116,7 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
is PtMachineRegister -> return withClonedChildrenFrom(this, PtMachineRegister(register, type, position))
is PtMemoryByte -> return withClonedChildrenFrom(this, PtMemoryByte(position))
is PtNumber -> return withClonedChildrenFrom(this, PtNumber(type, number, position))
is PtBool -> return withClonedChildrenFrom(this, PtBool(value, position))
is PtPrefix -> return withClonedChildrenFrom(this, PtPrefix(operator, type, position))
is PtRange -> return withClonedChildrenFrom(this, PtRange(type, position))
is PtString -> return withClonedChildrenFrom(this, PtString(value, encoding, position))
@@ -149,7 +150,7 @@ class PtArrayIndexer(elementType: DataType, position: Position): PtExpression(el
get() = variable.type in SplitWordArrayTypes
init {
require(elementType in NumericDatatypes)
require(elementType in NumericDatatypesWithBoolean)
}
}
@@ -183,15 +184,21 @@ class PtBuiltinFunctionCall(val name: String,
class PtBinaryExpression(val operator: String, type: DataType, position: Position): PtExpression(type, position) {
// note: "and", "or", "xor" do not occur anymore as operators. They've been replaced int the ast by their bitwise versions &, |, ^.
val left: PtExpression
get() = children[0] as PtExpression
val right: PtExpression
get() = children[1] as PtExpression
init {
if(operator in ComparisonOperators + LogicalOperators)
require(type==DataType.BOOL)
else
require(type!=DataType.BOOL) { "no bool allowed for this operator $operator"}
}
}
class PtContainmentCheck(position: Position): PtExpression(DataType.UBYTE, position) {
class PtContainmentCheck(position: Position): PtExpression(DataType.BOOL, position) {
val element: PtExpression
get() = children[0] as PtExpression
val iterable: PtIdentifier
@@ -228,6 +235,27 @@ class PtMemoryByte(position: Position) : PtExpression(DataType.UBYTE, position)
}
class PtBool(val value: Boolean, position: Position) : PtExpression(DataType.BOOL, position) {
companion object {
fun fromNumber(number: Number, position: Position): PtBool =
PtBool(if(number==0.0) false else true, position)
}
override fun hashCode(): Int = Objects.hash(type, value)
override fun equals(other: Any?): Boolean {
if(other==null || other !is PtBool)
return false
return value==other.value
}
override fun toString() = "PtBool:$value"
fun asInt(): Int = if(value) 1 else 0
}
class PtNumber(type: DataType, val number: Double, position: Position) : PtExpression(type, position) {
companion object {
@@ -237,7 +265,7 @@ class PtNumber(type: DataType, val number: Double, position: Position) : PtExpre
init {
if(type==DataType.BOOL)
throw IllegalArgumentException("bool should have become ubyte @$position")
throw IllegalArgumentException("use PtBool instead")
if(type!=DataType.FLOAT) {
val trunc = truncate(number)
if (trunc != number)
@@ -267,8 +295,7 @@ class PtPrefix(val operator: String, type: DataType, position: Position): PtExpr
get() = children.single() as PtExpression
init {
// note: the "not" operator may no longer occur in the ast; not x should have been replaced with x==0
require(operator in setOf("+", "-", "~")) { "invalid prefix operator: $operator" }
require(operator in setOf("+", "-", "~", "not")) { "invalid prefix operator: $operator" }
}
}
@@ -330,5 +357,5 @@ class PtTypeCast(type: DataType, position: Position) : PtExpression(type, positi
class PtMachineRegister(val register: Int, type: DataType, position: Position) : PtExpression(type, position)
fun constValue(expr: PtExpression): Double? = if(expr is PtNumber) expr.number else null
fun constIntValue(expr: PtExpression): Int? = if(expr is PtNumber) expr.number.toInt() else null
fun constValue(expr: PtExpression): Double? = if(expr is PtNumber) expr.number else if(expr is PtBool) expr.asInt().toDouble() else null
fun constIntValue(expr: PtExpression): Int? = if(expr is PtNumber) expr.number.toInt() else if(expr is PtBool) expr.asInt() else null
+13 -4
View File
@@ -15,7 +15,12 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
is PtAugmentedAssign -> "<inplace-assign> ${node.operator}"
is PtBreakpoint -> "%breakpoint"
is PtConditionalBranch -> "if_${node.condition.name.lowercase()}"
is PtAddressOf -> "&"
is PtAddressOf -> {
if(node.isFromArrayElement)
"& array-element"
else
"&"
}
is PtArray -> "array len=${node.children.size} ${type(node.type)}"
is PtArrayIndexer -> "<arrayindexer> ${type(node.type)} ${if(node.splitWords) "[splitwords]" else ""}"
is PtBinaryExpression -> "<expr> ${node.operator} ${type(node.type)}"
@@ -35,6 +40,7 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
val numstr = if(node.type == DataType.FLOAT) node.number.toString() else node.number.toHex()
"$numstr ${type(node.type)}"
}
is PtBool -> node.value.toString()
is PtPrefix -> node.operator
is PtRange -> "<range>"
is PtString -> "\"${node.value.escape()}\""
@@ -84,7 +90,11 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
"\nblock '${node.name}' $addr $align"
}
is PtConstant -> {
val value = if(node.type in IntegerDatatypes) node.value.toInt().toString() else node.value.toString()
val value = when(node.type) {
DataType.BOOL -> if(node.value==0.0) "false" else "true"
in IntegerDatatypes -> node.value.toInt().toString()
else -> node.value.toString()
}
"const ${node.type.name.lowercase()} ${node.name} = $value"
}
is PtLabel -> "${node.name}:"
@@ -121,9 +131,8 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
else
str
}
is PtNodeGroup -> "<group>"
is PtNodeGroup -> if(node.children.isNotEmpty()) "<group>" else ""
is PtNop -> "nop"
is PtPostIncrDecr -> "<post> ${node.operator}"
is PtProgram -> "PROGRAM ${node.name}"
is PtRepeatLoop -> "repeat"
is PtReturn -> "return"
+11 -11
View File
@@ -26,10 +26,10 @@ class PtSub(
) : PtNamedNode(name, position), IPtSubroutine, IPtStatementContainer {
init {
// params and return value should not be str
if(parameters.any{ it.type !in NumericDatatypes })
throw AssemblyError("non-numeric parameter")
if(returntype!=null && returntype !in NumericDatatypes)
throw AssemblyError("non-numeric returntype $returntype")
if(parameters.any{ it.type !in NumericDatatypes && it.type!=DataType.BOOL })
throw AssemblyError("non-numeric/non-bool parameter")
if(returntype!=null && returntype !in NumericDatatypes && returntype!=DataType.BOOL)
throw AssemblyError("non-numeric/non-bool returntype $returntype")
parameters.forEach { it.parent=this }
}
}
@@ -98,6 +98,8 @@ class PtIfElse(position: Position) : PtNode(position) {
get() = children[1] as PtNodeGroup
val elseScope: PtNodeGroup
get() = children[2] as PtNodeGroup
fun hasElse(): Boolean = children.size==3 && elseScope.children.isNotEmpty()
}
@@ -110,12 +112,6 @@ class PtJump(val identifier: PtIdentifier?, // note: even ad-hoc labels are
}
class PtPostIncrDecr(val operator: String, position: Position) : PtNode(position) {
val target: PtAssignTarget
get() = children.single() as PtAssignTarget
}
class PtRepeatLoop(position: Position) : PtNode(position) {
val count: PtExpression
get() = children[0] as PtExpression
@@ -152,7 +148,11 @@ class PtVariable(name: String, override val type: DataType, val zeropage: Zeropa
class PtConstant(name: String, override val type: DataType, val value: Double, position: Position) : PtNamedNode(name, position), IPtVariable
class PtMemMapped(name: String, override val type: DataType, val address: UInt, val arraySize: UInt?, position: Position) : PtNamedNode(name, position), IPtVariable
class PtMemMapped(name: String, override val type: DataType, val address: UInt, val arraySize: UInt?, position: Position) : PtNamedNode(name, position), IPtVariable {
init {
require(type!=DataType.BOOL && type!=DataType.ARRAY_BOOL)
}
}
class PtWhen(position: Position) : PtNode(position) {
@@ -79,27 +79,28 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
"sort" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
"reverse" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
// cmp returns a status in the carry flag, but not a proper return value
"cmp" to FSignature(false, listOf(FParam("value1", IntegerDatatypesNoBool), FParam("value2", NumericDatatypesNoBool)), null),
"cmp" to FSignature(false, listOf(FParam("value1", IntegerDatatypes), FParam("value2", NumericDatatypes)), null),
"prog8_lib_stringcompare" to FSignature(true, listOf(FParam("str1", arrayOf(DataType.STR)), FParam("str2", arrayOf(DataType.STR))), DataType.BYTE),
"prog8_lib_arraycopy" to FSignature(false, listOf(FParam("source", ArrayDatatypes), FParam("target", ArrayDatatypes)), null),
"prog8_lib_square_byte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.BYTE, DataType.UBYTE))), DataType.UBYTE),
"prog8_lib_square_word" to FSignature(true, listOf(FParam("value", arrayOf(DataType.WORD, DataType.UWORD))), DataType.UWORD),
"abs" to FSignature(true, listOf(FParam("value", NumericDatatypesNoBool)), null),
"abs" to FSignature(true, listOf(FParam("value", NumericDatatypes)), null),
"abs__byte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.BYTE))), DataType.BYTE),
"abs__word" to FSignature(true, listOf(FParam("value", arrayOf(DataType.WORD))), DataType.WORD),
"abs__float" to FSignature(true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT),
"len" to FSignature(true, listOf(FParam("values", IterableDatatypes)), DataType.UWORD),
// normal functions follow:
"sizeof" to FSignature(true, listOf(FParam("object", DataType.entries.toTypedArray())), DataType.UBYTE),
"sgn" to FSignature(true, listOf(FParam("value", NumericDatatypesNoBool)), DataType.BYTE),
"sqrt" to FSignature(true, listOf(FParam("value", NumericDatatypesNoBool)), null),
"sgn" to FSignature(true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE),
"sqrt" to FSignature(true, listOf(FParam("value", NumericDatatypes)), null),
"sqrt__ubyte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UBYTE))), DataType.UBYTE),
"sqrt__uword" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD))), DataType.UBYTE),
"sqrt__float" to FSignature(true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT),
"divmod" to FSignature(false, listOf(FParam("number", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("divident", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("division", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("remainder", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
"divmod__ubyte" to FSignature(false, listOf(FParam("number", arrayOf(DataType.UBYTE)), FParam("divident", arrayOf(DataType.UBYTE)), FParam("division", arrayOf(DataType.UBYTE)), FParam("remainder", arrayOf(DataType.UBYTE))), null),
"divmod__uword" to FSignature(false, listOf(FParam("number", arrayOf(DataType.UWORD)), FParam("divident", arrayOf(DataType.UWORD)), FParam("division", arrayOf(DataType.UWORD)), FParam("remainder", arrayOf(DataType.UWORD))), null),
"any" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE),
"all" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE),
"any" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.BOOL),
"all" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.BOOL),
"lsb" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE),
"msb" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE),
"mkword" to FSignature(true, listOf(FParam("msb", arrayOf(DataType.UBYTE)), FParam("lsb", arrayOf(DataType.UBYTE))), DataType.UWORD),
@@ -19,12 +19,14 @@ class CompilationOptions(val output: OutputType,
var asmQuiet: Boolean = false,
var asmListfile: Boolean = false,
var includeSourcelines: Boolean = false,
var dumpVariables: Boolean = false,
var experimentalCodegen: Boolean = false,
var varsHighBank: Int? = null,
var varsGolden: Boolean = false,
var slabsHighBank: Int? = null,
var slabsGolden: Boolean = false,
var splitWordArrays: Boolean = false,
var strictBool: Boolean = true,
var breakpointCpuInstruction: String? = null,
var outputDir: Path = Path(""),
var symbolDefs: Map<String, String> = emptyMap()
@@ -58,6 +58,7 @@ fun String.unescape(): String {
'\\' -> '\\'
'n' -> '\n'
'r' -> '\r'
't' -> '\t'
'"' -> '"'
'\'' -> '\''
'u' -> {
+13 -12
View File
@@ -24,8 +24,8 @@ enum class DataType {
*/
infix fun isAssignableTo(targetType: DataType) =
when(this) {
BOOL -> targetType.oneOf(BOOL, BYTE, UBYTE, WORD, UWORD, LONG, FLOAT)
UBYTE -> targetType.oneOf(UBYTE, WORD, UWORD, LONG, FLOAT, BOOL)
BOOL -> targetType == BOOL
UBYTE -> targetType.oneOf(UBYTE, WORD, UWORD, LONG, FLOAT)
BYTE -> targetType.oneOf(BYTE, WORD, LONG, FLOAT)
UWORD -> targetType.oneOf(UWORD, LONG, FLOAT)
WORD -> targetType.oneOf(WORD, LONG, FLOAT)
@@ -41,9 +41,9 @@ enum class DataType {
infix fun largerThan(other: DataType) =
when {
this == other -> false
this in ByteDatatypes -> false
this in WordDatatypes -> other in ByteDatatypes
this == LONG -> other in ByteDatatypes+WordDatatypes
this in ByteDatatypesWithBoolean -> false
this in WordDatatypes -> other in ByteDatatypesWithBoolean
this == LONG -> other in ByteDatatypesWithBoolean+WordDatatypes
this == STR && other == UWORD || this == UWORD && other == STR -> false
else -> true
}
@@ -51,7 +51,7 @@ enum class DataType {
infix fun equalsSize(other: DataType) =
when {
this == other -> true
this in ByteDatatypes -> other in ByteDatatypes
this in ByteDatatypesWithBoolean -> other in ByteDatatypesWithBoolean
this in WordDatatypes -> other in WordDatatypes
this== STR && other== UWORD || this== UWORD && other== STR -> true
else -> false
@@ -124,12 +124,13 @@ enum class BranchCondition {
}
val ByteDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.BOOL)
val ByteDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE)
val ByteDatatypesWithBoolean = ByteDatatypes + DataType.BOOL
val WordDatatypes = arrayOf(DataType.UWORD, DataType.WORD)
val IntegerDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.LONG)
val IntegerDatatypes = IntegerDatatypesNoBool + DataType.BOOL
val NumericDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.LONG, DataType.FLOAT)
val NumericDatatypes = NumericDatatypesNoBool + DataType.BOOL
val IntegerDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.LONG)
val IntegerDatatypesWithBoolean = IntegerDatatypes + DataType.BOOL
val NumericDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.LONG, DataType.FLOAT)
val NumericDatatypesWithBoolean = NumericDatatypes + DataType.BOOL
val SignedDatatypes = arrayOf(DataType.BYTE, DataType.WORD, DataType.LONG, DataType.FLOAT)
val ArrayDatatypes = arrayOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_UW_SPLIT, DataType.ARRAY_W, DataType.ARRAY_W_SPLIT, DataType.ARRAY_F, DataType.ARRAY_BOOL)
val StringlyDatatypes = arrayOf(DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B, DataType.UWORD)
@@ -141,7 +142,7 @@ val IterableDatatypes = arrayOf(
DataType.ARRAY_UW_SPLIT, DataType.ARRAY_W_SPLIT,
DataType.ARRAY_F, DataType.ARRAY_BOOL
)
val PassByValueDatatypes = NumericDatatypes
val PassByValueDatatypes = NumericDatatypesWithBoolean
val PassByReferenceDatatypes = IterableDatatypes
val ArrayToElementTypes = mapOf(
DataType.STR to DataType.UBYTE,
@@ -11,4 +11,6 @@ interface IErrorReporter {
if(numErrors>0)
throw ErrorsReportedException("There are $numErrors errors, $numWarnings warnings, and $numInfos infos.")
}
fun noErrorForLine(position: Position): Boolean
}
@@ -71,7 +71,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
val size: Int =
when (datatype) {
in IntegerDatatypes -> options.compTarget.memorySize(datatype)
in IntegerDatatypesWithBoolean -> options.compTarget.memorySize(datatype)
DataType.STR, in ArrayDatatypes -> {
val memsize = options.compTarget.memorySize(datatype, numElements!!)
if(position!=null)
@@ -119,7 +119,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
free.removeAll(address until address+size.toUInt())
if(name.isNotEmpty()) {
allocatedVariables[name] = when(datatype) {
in NumericDatatypes -> VarAllocation(address, datatype, size) // numerical variables in zeropage never have an initial value here because they are set in separate initializer assignments
in NumericDatatypes, DataType.BOOL -> VarAllocation(address, datatype, size) // numerical variables in zeropage never have an initial value here because they are set in separate initializer assignments
DataType.STR -> VarAllocation(address, datatype, size)
in ArrayDatatypes -> VarAllocation(address, datatype, size)
else -> throw AssemblyError("invalid dt")
@@ -151,7 +151,7 @@ class GoldenRam(options: CompilationOptions, val region: UIntRange): MemoryAlloc
val size: Int =
when (datatype) {
in IntegerDatatypes -> options.compTarget.memorySize(datatype)
in IntegerDatatypesWithBoolean -> options.compTarget.memorySize(datatype)
DataType.STR, in ArrayDatatypes -> {
options.compTarget.memorySize(datatype, numElements!!)
}
+2 -2
View File
@@ -1,8 +1,8 @@
package prog8.code.core
val AssociativeOperators = setOf("+", "*", "&", "|", "^", "==", "!=", "and", "or", "xor")
val AssociativeOperators = setOf("+", "*", "&", "|", "^", "==", "!=", "xor") // note: and,or are no longer associative because of Shortcircuit/McCarthy evaluation
val ComparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
val LogicalOperators = setOf("and", "or", "xor", "not")
val LogicalOperators = setOf("and", "or", "xor", "not", "in")
val BitwiseOperators = setOf("&", "|", "^", "~")
val PrefixOperators = setOf("+", "-", "~", "not")
@@ -15,7 +15,7 @@ class AtariTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in ByteDatatypesWithBoolean -> 1
in WordDatatypes, in PassByReferenceDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
else -> throw IllegalArgumentException("invalid datatype")
@@ -23,5 +23,9 @@ class AtariTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
}
override fun memorySize(arrayDt: DataType, numElements: Int) =
memorySize(ArrayToElementTypes.getValue(arrayDt)) * numElements
if(arrayDt==DataType.UWORD)
numElements // pointer to bytes.
else
memorySize(ArrayToElementTypes.getValue(arrayDt)) * numElements
}
+5 -2
View File
@@ -14,7 +14,7 @@ class VMTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in ByteDatatypesWithBoolean -> 1
in WordDatatypes, in PassByReferenceDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
else -> throw IllegalArgumentException("invalid datatype")
@@ -22,5 +22,8 @@ class VMTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
}
override fun memorySize(arrayDt: DataType, numElements: Int) =
memorySize(ArrayToElementTypes.getValue(arrayDt)) * numElements
if(arrayDt==DataType.UWORD)
numElements // pointer to bytes.
else
memorySize(ArrayToElementTypes.getValue(arrayDt)) * numElements
}
@@ -6,7 +6,7 @@ import prog8.code.core.*
internal object CbmMemorySizer: IMemSizer {
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in ByteDatatypesWithBoolean -> 1
in WordDatatypes, in PassByReferenceDatatypes -> 2
DataType.FLOAT -> Mflpt5.FLOAT_MEM_SIZE
else -> throw IllegalArgumentException("invalid datatype")
@@ -14,5 +14,8 @@ internal object CbmMemorySizer: IMemSizer {
}
override fun memorySize(arrayDt: DataType, numElements: Int) =
memorySize(ArrayToElementTypes.getValue(arrayDt)) * numElements
if(arrayDt==DataType.UWORD)
numElements // pointer to bytes.
else
memorySize(ArrayToElementTypes.getValue(arrayDt)) * numElements
}
+3 -3
View File
@@ -1,9 +1,7 @@
plugins {
id 'java'
id 'application'
id "org.jetbrains.kotlin.jvm"
id "io.kotest" version "0.3.9"
}
java {
@@ -28,9 +26,11 @@ dependencies {
implementation project(':codeCore')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.20"
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.8.0'
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
sourceSets {
File diff suppressed because it is too large. Load diff
@@ -58,7 +58,7 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefin
numberOfOptimizations++
}
mods = optimizeSamePointerIndexing(linesByFourteen)
mods = optimizeSamePointerIndexingAndUselessBeq(linesByFourteen)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFourteen = getLinesBy(lines, 14)
@@ -75,23 +75,48 @@ private fun String.isStoreReg() = this.startsWith("sta") || this.startsWith("sty
private fun String.isStoreRegOrZero() = this.isStoreReg() || this.startsWith("stz")
private fun String.isLoadReg() = this.startsWith("lda") || this.startsWith("ldy") || this.startsWith("ldx")
private class Modification(val lineIndex: Int, val remove: Boolean, val replacement: String?)
private class Modification(val lineIndex: Int, val remove: Boolean, val replacement: String?, val removeLabel: Boolean=false)
private fun apply(modifications: List<Modification>, lines: MutableList<String>) {
for (modification in modifications.sortedBy { it.lineIndex }.reversed()) {
if(modification.remove)
lines.removeAt(modification.lineIndex)
if(modification.remove) {
if(modification.removeLabel)
lines.removeAt(modification.lineIndex)
else {
val line = lines[modification.lineIndex]
if (line.length < 2 || line[0] == ';' || line.trimStart()[0] == ';')
lines.removeAt(modification.lineIndex)
else if (haslabel(line)) {
val label = keeplabel(line)
if (label.isNotEmpty())
lines[modification.lineIndex] = label
else
lines.removeAt(modification.lineIndex)
} else lines.removeAt(modification.lineIndex)
}
}
else
lines[modification.lineIndex] = modification.replacement!!
}
}
private fun haslabel(line: String): Boolean {
return line.length>1 && line[0]!=';' && (!line[0].isWhitespace() || ':' in line)
}
private fun keeplabel(line: String): String {
if(':' in line)
return line.substringBefore(':') + ':'
val splits = line.split('\t', ' ', limit=2)
return if(splits.size>1) splits[0] + ':' else ""
}
private fun getLinesBy(lines: MutableList<String>, windowSize: Int) =
// all lines (that aren't empty or comments) in sliding windows of certain size
lines.withIndex().filter { it.value.isNotBlank() && !it.value.trimStart().startsWith(';') }.windowed(windowSize, partialWindows = false)
lines.asSequence().withIndex().filter { it.value.isNotBlank() && !it.value.trimStart().startsWith(';') }.windowed(windowSize, partialWindows = false)
private fun optimizeSameAssignments(
linesByFourteen: List<List<IndexedValue<String>>>,
linesByFourteen: Sequence<List<IndexedValue<String>>>,
machine: IMachineDefinition,
symbolTable: SymbolTable
): List<Modification> {
@@ -281,7 +306,7 @@ private fun optimizeSameAssignments(
return mods
}
private fun optimizeSamePointerIndexing(linesByFourteen: List<List<IndexedValue<String>>>): List<Modification> {
private fun optimizeSamePointerIndexingAndUselessBeq(linesByFourteen: Sequence<List<IndexedValue<String>>>): List<Modification> {
// Optimize same pointer indexing where for instance we load and store to the same ptr index in Y
// if Y isn't modified in between we can omit the second LDY:
@@ -318,31 +343,57 @@ private fun optimizeSamePointerIndexing(linesByFourteen: List<List<IndexedValue<
mods.add(Modification(lines[4].index, true, null))
}
}
/*
beq +
lda #1
+
[ optional: label_xxxx_shortcut line here]
beq label_xxxx_shortcut / bne label_xxxx_shortcut
or *_afterif labels.
This gets generated after certain if conditions, and only the branch instruction is needed in these cases.
*/
if(first=="beq +" && second=="lda #1" && third=="+") {
if((fourth.startsWith("beq label_") || fourth.startsWith("bne label_")) &&
(fourth.endsWith("_shortcut") || fourth.endsWith("_afterif") || fourth.endsWith("_shortcut:") || fourth.endsWith("_afterif:"))) {
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, true, null))
}
else if(fourth.startsWith("label_") && (fourth.endsWith("_shortcut") || fourth.endsWith("_shortcut:"))) {
if((fifth.startsWith("beq label_") || fifth.startsWith("bne label_")) &&
(fifth.endsWith("_shortcut") || fifth.endsWith("_afterif") || fifth.endsWith("_shortcut:") || fifth.endsWith("_afterif:"))) {
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, true, null))
}
}
}
}
return mods
}
private fun optimizeStoreLoadSame(
linesByFour: List<List<IndexedValue<String>>>,
linesByFour: Sequence<List<IndexedValue<String>>>,
machine: IMachineDefinition,
symbolTable: SymbolTable
): List<Modification> {
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can OFTEN be eliminated
val mods = mutableListOf<Modification>()
for (lines in linesByFour) {
val first = lines[1].value.trimStart()
val second = lines[2].value.trimStart()
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can OFTEN be eliminated
if ((first.startsWith("sta ") && second.startsWith("lda ")) ||
(first.startsWith("stx ") && second.startsWith("ldx ")) ||
(first.startsWith("sty ") && second.startsWith("ldy ")) ||
(first.startsWith("lda ") && second.startsWith("lda ")) ||
(first.startsWith("ldy ") && second.startsWith("ldy ")) ||
(first.startsWith("ldx ") && second.startsWith("ldx ")) ||
(first.startsWith("sta ") && second.startsWith("lda ")) ||
(first.startsWith("sty ") && second.startsWith("ldy ")) ||
(first.startsWith("stx ") && second.startsWith("ldx "))
(first.startsWith("ldx ") && second.startsWith("ldx "))
) {
val third = lines[3].value.trimStart()
val attemptRemove =
@@ -385,6 +436,18 @@ private fun optimizeStoreLoadSame(
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, false, " tya"))
}
// lda X + sta X, ldy X + sty X, ldx X + stx X -> the second instruction can be eliminated
if ((first.startsWith("lda ") && second.startsWith("sta ")) ||
(first.startsWith("ldx ") && second.startsWith("stx ")) ||
(first.startsWith("ldy ") && second.startsWith("sty "))
) {
val firstLoc = first.substring(4).trimStart()
val secondLoc = second.substring(4).trimStart()
if (firstLoc == secondLoc)
mods.add(Modification(lines[2].index, true, null))
}
}
return mods
}
@@ -414,7 +477,7 @@ private fun getAddressArg(line: String, symbolTable: SymbolTable): UInt? {
}
}
private fun optimizeIncDec(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
private fun optimizeIncDec(linesByFour: Sequence<List<IndexedValue<String>>>): List<Modification> {
// sometimes, iny+dey / inx+dex / dey+iny / dex+inx sequences are generated, these can be eliminated.
val mods = mutableListOf<Modification>()
for (lines in linesByFour) {
@@ -431,14 +494,17 @@ private fun optimizeIncDec(linesByFour: List<List<IndexedValue<String>>>): List<
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[1].index, true, null))
}
}
return mods
}
private fun optimizeJsrRtsAndOtherCombinations(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<IndexedValue<String>>>): List<Modification> {
// jsr Sub + rts -> jmp Sub
// rts + jmp -> remove jmp
// rts + bxx -> remove bxx
// lda + cmp #0 -> remove cmp, same for cpy and cpx.
// and some other optimizations.
val mods = mutableListOf<Modification>()
for (lines in linesByFour) {
@@ -470,11 +536,54 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: List<List<IndexedVal
else if (" bvc" in second || "\tbvc" in second)
mods += Modification(lines[1].index, true, null)
}
if (!haslabel(second)) {
if ((" lda" in first || "\tlda" in first) && (" cmp #0" in second || "\tcmp #0" in second) ||
(" ldx" in first || "\tldx" in first) && (" cpx #0" in second || "\tcpx #0" in second) ||
(" ldy" in first || "\tldy" in first) && (" cpy #0" in second || "\tcpy #0" in second)
) {
mods.add(Modification(lines[1].index, true, null))
}
else if(" cmp #0" in second || "\tcmp #0" in second) {
// there are many instructions that modify A and set the bits...
for(instr in arrayOf("lda", "ora", "and", "eor", "adc", "sbc", "asl", "cmp", "inc a", "lsr", "pla", "rol", "ror", "txa", "tya")) {
if(" $instr" in first || "\t$instr" in first) {
mods.add(Modification(lines[1].index, true, null))
}
}
}
}
/*
LDA NUM1
CMP NUM2
BCC LABEL
BEQ LABEL
(or something similar) which branches to LABEL when NUM1 <= NUM2. (In this case NUM1 and NUM2 are unsigned numbers.) However, consider the following sequence:
LDA NUM2
CMP NUM1
BCS LABEL
*/
val tfirst = first.trimStart()
val tsecond = second.trimStart()
val tthird = lines[2].value.trimStart()
val tfourth = lines[3].value.trimStart()
if(tfirst.startsWith("lda") && tsecond.startsWith("cmp") && tthird.startsWith("bcc") && tfourth.startsWith("beq")) {
val label = tthird.substring(4)
if(label==tfourth.substring(4)) {
mods += Modification(lines[0].index, false, " lda ${tsecond.substring(4)}")
mods += Modification(lines[1].index, false, " cmp ${tfirst.substring(4)}")
mods += Modification(lines[2].index, false, " bcs $label")
mods += Modification(lines[3].index, true, null)
}
}
}
return mods
}
private fun optimizeUselessPushPopStack(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
private fun optimizeUselessPushPopStack(linesByFour: Sequence<List<IndexedValue<String>>>): List<Modification> {
val mods = mutableListOf<Modification>()
fun optimize(register: Char, lines: List<IndexedValue<String>>) {
@@ -511,7 +620,7 @@ private fun optimizeUselessPushPopStack(linesByFour: List<List<IndexedValue<Stri
return mods
}
private fun optimizeUnneededTempvarInAdd(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
private fun optimizeUnneededTempvarInAdd(linesByFour: Sequence<List<IndexedValue<String>>>): List<Modification> {
// sequence: sta P8ZP_SCRATCH_XX / lda something / clc / adc P8ZP_SCRATCH_XX
// this can be performed without the scratch variable: clc / adc something
val mods = mutableListOf<Modification>()
@@ -523,8 +632,8 @@ private fun optimizeUnneededTempvarInAdd(linesByFour: List<List<IndexedValue<Str
val fourth = lines[3].value.trimStart()
if(first.startsWith("sta P8ZP_SCRATCH_") && second.startsWith("lda") && third.startsWith("clc") && fourth.startsWith("adc P8ZP_SCRATCH_") ) {
if(fourth.substring(4)==first.substring(4)) {
mods.add(Modification(lines[0].index, false, " clc"))
mods.add(Modification(lines[1].index, false, " adc ${second.substring(3).trimStart()}"))
mods.add(Modification(lines[0].index, false, " clc"))
mods.add(Modification(lines[1].index, false, " adc ${second.substring(3).trimStart()}"))
mods.add(Modification(lines[2].index, true, null))
mods.add(Modification(lines[3].index, true, null))
}
@@ -71,12 +71,102 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
"prog8_lib_stringcompare" -> funcStringCompare(fcall, resultRegister)
"prog8_lib_square_byte" -> funcSquare(fcall, DataType.UBYTE, resultRegister)
"prog8_lib_square_word" -> funcSquare(fcall, DataType.UWORD, resultRegister)
"prog8_lib_arraycopy" -> funcArrayCopy(fcall)
else -> throw AssemblyError("missing asmgen for builtin func ${fcall.name}")
}
return BuiltinFunctions.getValue(fcall.name).returnType
}
private fun funcArrayCopy(fcall: PtBuiltinFunctionCall) {
val source = fcall.args[0] as PtIdentifier
val target = fcall.args[1] as PtIdentifier
val sourceSymbol = asmgen.symbolTable.lookup(source.name)
val numElements = when(sourceSymbol) {
is StStaticVariable -> sourceSymbol.length!!
is StMemVar -> sourceSymbol.length!!
else -> 0
}
val sourceAsm = asmgen.asmVariableName(source)
val targetAsm = asmgen.asmVariableName(target)
if(source.type in SplitWordArrayTypes && target.type in SplitWordArrayTypes) {
// split -> split words (copy lsb and msb arrays separately)
asmgen.out("""
lda #<${sourceAsm}_lsb
ldy #>${sourceAsm}_lsb
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<${targetAsm}_lsb
ldy #>${targetAsm}_lsb
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #${numElements and 255}
jsr prog8_lib.memcopy_small
lda #<${sourceAsm}_msb
ldy #>${sourceAsm}_msb
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<${targetAsm}_msb
ldy #>${targetAsm}_msb
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #${numElements and 255}
jsr prog8_lib.memcopy_small""")
}
else if(source.type in SplitWordArrayTypes) {
// split word array to normal word array (copy lsb and msb arrays separately)
require(target.type==DataType.ARRAY_UW || target.type==DataType.ARRAY_W)
asmgen.out("""
lda #<${sourceAsm}_lsb
ldy #>${sourceAsm}_lsb
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<${sourceAsm}_msb
ldy #>${sourceAsm}_msb
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
lda #<${targetAsm}
ldy #>${targetAsm}
ldx #${numElements and 255}
jsr prog8_lib.arraycopy_split_to_normal_words""")
}
else if(target.type in SplitWordArrayTypes) {
// normal word array to split array
require(source.type==DataType.ARRAY_UW || source.type==DataType.ARRAY_W)
asmgen.out("""
lda #<${targetAsm}_lsb
ldy #>${targetAsm}_lsb
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<${targetAsm}_msb
ldy #>${targetAsm}_msb
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
lda #<${sourceAsm}
ldy #>${sourceAsm}
ldx #${numElements and 255}
jsr prog8_lib.arraycopy_normal_to_split_words""")
}
else {
// normal array to array copy, various element types
val eltsize = asmgen.options.compTarget.memorySize(ArrayToElementTypes.getValue(source.type))
val numBytes = numElements * eltsize
asmgen.out("""
lda #<${sourceAsm}
ldy #>${sourceAsm}
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<${targetAsm}
ldy #>${targetAsm}
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #${numBytes and 255}
jsr prog8_lib.memcopy_small""")
}
}
private fun funcSquare(fcall: PtBuiltinFunctionCall, resultType: DataType, resultRegister: RegisterOrPair?) {
// square of word value is faster with dedicated routine, square of byte just use the regular multiplication routine.
when (resultType) {
@@ -225,6 +315,10 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp #${arg2.number.toInt()}")
}
is PtBool -> {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp #${arg2.asInt()}")
}
is PtMemoryByte -> {
if(arg2.address is PtNumber) {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
@@ -253,6 +347,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
cmp ${asmgen.asmVariableName(arg2)}
+""")
}
is PtBool -> TODO("word compare against bool")
is PtNumber -> {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
asmgen.out("""
@@ -665,6 +760,8 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
is PtAddressOf -> {
val mem = PtMemoryByte(fcall.position)
if((fcall.args[0] as PtAddressOf).isFromArrayElement)
TODO("address-of arrayelement")
if(msb) {
val address = PtBinaryExpression("+", DataType.UWORD, fcall.args[0].position)
address.add(fcall.args[0])
@@ -1313,8 +1410,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
ldy #>$identifierName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
""")
lda #${numElements and 255}""")
}
private fun translateArguments(call: PtBuiltinFunctionCall, scope: IPtSubroutine?) {
@@ -17,7 +17,7 @@ internal fun IPtSubroutine.returnsWhatWhere(): List<Pair<RegisterOrStatusflag, D
emptyList()
else {
val register = when (returntype!!) {
in ByteDatatypes -> RegisterOrStatusflag(RegisterOrPair.A, null)
in ByteDatatypesWithBoolean -> RegisterOrStatusflag(RegisterOrPair.A, null)
in WordDatatypes -> RegisterOrStatusflag(RegisterOrPair.AY, null)
DataType.FLOAT -> RegisterOrStatusflag(RegisterOrPair.FAC1, null)
else -> RegisterOrStatusflag(RegisterOrPair.AY, null)
@@ -31,7 +31,7 @@ internal fun IPtSubroutine.returnsWhatWhere(): List<Pair<RegisterOrStatusflag, D
internal fun PtSub.returnRegister(): RegisterOrStatusflag? {
return when(returntype) {
in ByteDatatypes -> RegisterOrStatusflag(RegisterOrPair.A, null)
in ByteDatatypesWithBoolean -> RegisterOrStatusflag(RegisterOrPair.A, null)
in WordDatatypes -> RegisterOrStatusflag(RegisterOrPair.AY, null)
DataType.FLOAT -> RegisterOrStatusflag(RegisterOrPair.FAC1, null)
null -> null
@@ -359,7 +359,7 @@ $loopLabel lda ${65535.toHex()} ; modified
bne $loopLabel
$endLabel""")
}
DataType.ARRAY_UB, DataType.ARRAY_B -> {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_BOOL -> {
val indexVar = asmgen.makeLabel("for_index")
asmgen.out("""
ldy #0
@@ -16,8 +16,8 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
}
internal fun optimizeIntArgsViaRegisters(sub: PtSub) =
(sub.parameters.size==1 && sub.parameters[0].type in IntegerDatatypes)
|| (sub.parameters.size==2 && sub.parameters[0].type in ByteDatatypes && sub.parameters[1].type in ByteDatatypes)
(sub.parameters.size==1 && sub.parameters[0].type in IntegerDatatypesWithBoolean)
|| (sub.parameters.size==2 && sub.parameters[0].type in ByteDatatypesWithBoolean && sub.parameters[1].type in ByteDatatypesWithBoolean)
internal fun translateFunctionCall(call: PtFunctionCall) {
// Output only the code to set up the parameters and perform the actual call
@@ -46,7 +46,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
else if(sub is PtSub) {
if(optimizeIntArgsViaRegisters(sub)) {
if(sub.parameters.size==1) {
val register = if (sub.parameters[0].type in ByteDatatypes) RegisterOrPair.A else RegisterOrPair.AY
val register = if (sub.parameters[0].type in ByteDatatypesWithBoolean) RegisterOrPair.A else RegisterOrPair.AY
argumentViaRegister(sub, IndexedValue(0, sub.parameters[0]), call.args[0], register)
} else {
// 2 byte params, second in Y, first in A
@@ -82,8 +82,9 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
is PtAddressOf -> false
is PtIdentifier -> false
is PtMachineRegister -> false
is PtMemoryByte -> false
is PtMemoryByte -> return usesOtherRegistersWhileEvaluating(arg.address)
is PtNumber -> false
is PtBool -> false
else -> true
}
}
@@ -152,7 +153,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
}
if (statusflag!=null) {
if(requiredDt!=value.type)
throw AssemblyError("for statusflag, byte value is required")
throw AssemblyError("for statusflag, byte or bool value is required")
if (statusflag == Statusflag.Pc) {
// this boolean param needs to be set last, right before the jsr
// for now, this is already enforced on the subroutine definition by the Ast Checker
@@ -161,6 +162,9 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
val carrySet = value.number.toInt() != 0
asmgen.out(if(carrySet) " sec" else " clc")
}
is PtBool -> {
asmgen.out(if(value.value) " sec" else " clc")
}
is PtIdentifier -> {
val sourceName = asmgen.asmVariableName(value)
// note: cannot use X register here to store A because it might be used for other arguments
@@ -218,6 +222,8 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
private fun isArgumentTypeCompatible(argType: DataType, paramType: DataType): Boolean {
if(argType isAssignableTo paramType)
return true
if(argType==DataType.BOOL && paramType==DataType.BOOL)
return true
if(argType in ByteDatatypes && paramType in ByteDatatypes)
return true
if(argType in WordDatatypes && paramType in WordDatatypes)
File diff suppressed because it is too large. Load diff
@@ -1,191 +0,0 @@
package prog8.codegen.cpu6502
import prog8.code.ast.*
import prog8.code.core.*
internal class PostIncrDecrAsmGen(private val program: PtProgram, private val asmgen: AsmGen6502Internal) {
internal fun translate(stmt: PtPostIncrDecr) {
val incr = stmt.operator=="++"
val targetIdent = stmt.target.identifier
val targetMemory = stmt.target.memory
val targetArrayIdx = stmt.target.array
when {
targetIdent!=null -> {
val what = asmgen.asmVariableName(targetIdent)
when (stmt.target.type) {
in ByteDatatypes -> asmgen.out(if (incr) " inc $what" else " dec $what")
in WordDatatypes -> {
if(incr)
asmgen.out(" inc $what | bne + | inc $what+1 |+")
else
asmgen.out("""
lda $what
bne +
dec $what+1
+ dec $what
""")
}
DataType.FLOAT -> {
asmgen.out(" lda #<$what | ldy #>$what")
asmgen.out(if(incr) " jsr floats.inc_var_f" else " jsr floats.dec_var_f")
}
else -> throw AssemblyError("need numeric type")
}
}
targetMemory!=null -> {
fun incDecViaExprEval() {
asmgen.assignExpressionToRegister(targetMemory.address, RegisterOrPair.AY)
asmgen.out(" sta (+) + 1 | sty (+) + 2")
if(incr)
asmgen.out("+\tinc ${'$'}ffff\t; modified")
else
asmgen.out("+\tdec ${'$'}ffff\t; modified")
}
fun tryOptimizedPointerIncDec(address: PtBinaryExpression): Boolean {
if(address.operator=="+") {
val offset = address.right.asConstInteger()
if(offset!=null && offset<256) {
// we have @(ptr + 255) ++ , or @(ptr+255)--
asmgen.assignExpressionToRegister(address.left, RegisterOrPair.AY, false)
asmgen.out("""
sta (+) + 1
sty (+) + 2
ldx #$offset""")
if(incr)
asmgen.out("+\tinc ${'$'}ffff,x\t; modified")
else
asmgen.out("+\tdec ${'$'}ffff,x\t; modified")
return true
} else if(address.right.type in ByteDatatypes) {
// we have @(ptr + bytevar) ++ , or @(ptr+bytevar)--
asmgen.assignExpressionToRegister(address.left, RegisterOrPair.AY, false)
asmgen.out(" sta (+) + 1 | sty (+) + 2")
asmgen.assignExpressionToRegister(address.right, RegisterOrPair.X, false)
if(incr)
asmgen.out("+\tinc ${'$'}ffff,x\t; modified")
else
asmgen.out("+\tdec ${'$'}ffff,x\t; modified")
return true
} else if((address.right as? PtTypeCast)?.value?.type in ByteDatatypes) {
// we have @(ptr + bytevar) ++ , or @(ptr+bytevar)--
asmgen.assignExpressionToRegister(address.left, RegisterOrPair.AY, false)
asmgen.out(" sta (+) + 1 | sty (+) + 2")
asmgen.assignExpressionToRegister((address.right as PtTypeCast).value, RegisterOrPair.X, false)
if(incr)
asmgen.out("+\tinc ${'$'}ffff,x\t; modified")
else
asmgen.out("+\tdec ${'$'}ffff,x\t; modified")
return true
}
}
return false
}
when (val addressExpr = targetMemory.address) {
is PtNumber -> {
val what = addressExpr.number.toHex()
asmgen.out(if(incr) " inc $what" else " dec $what")
}
is PtIdentifier -> {
val what = asmgen.asmVariableName(addressExpr)
asmgen.out(" lda $what | sta (+) +1 | lda $what+1 | sta (+) +2")
if(incr)
asmgen.out("+\tinc ${'$'}ffff\t; modified")
else
asmgen.out("+\tdec ${'$'}ffff\t; modified")
}
is PtBinaryExpression -> {
if(!tryOptimizedPointerIncDec(addressExpr)) {
incDecViaExprEval()
}
}
else -> {
incDecViaExprEval()
}
}
}
targetArrayIdx!=null -> {
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.variable)
val elementDt = targetArrayIdx.type
val constIndex = targetArrayIdx.index.asConstInteger()
if(targetArrayIdx.splitWords) {
if(constIndex!=null) {
if(incr)
asmgen.out(" inc ${asmArrayvarname}_lsb+$constIndex | bne + | inc ${asmArrayvarname}_msb+$constIndex |+")
else
asmgen.out("""
lda ${asmArrayvarname}_lsb+$constIndex
bne +
dec ${asmArrayvarname}_msb+$constIndex
+ dec ${asmArrayvarname}_lsb+$constIndex""")
} else {
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, CpuRegister.X)
if(incr)
asmgen.out(" inc ${asmArrayvarname}_lsb,x | bne + | inc ${asmArrayvarname}_msb,x |+")
else
asmgen.out("""
lda ${asmArrayvarname}_lsb,x
bne +
dec ${asmArrayvarname}_msb,x
+ dec ${asmArrayvarname}_lsb,x""")
}
return
}
if(constIndex!=null) {
val indexValue = constIndex * program.memsizer.memorySize(elementDt)
when(elementDt) {
in ByteDatatypes -> {
asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue")
}
in WordDatatypes -> {
if(incr)
asmgen.out(" inc $asmArrayvarname+$indexValue | bne + | inc $asmArrayvarname+$indexValue+1 |+")
else
asmgen.out("""
lda $asmArrayvarname+$indexValue
bne +
dec $asmArrayvarname+$indexValue+1
+ dec $asmArrayvarname+$indexValue""")
}
DataType.FLOAT -> {
asmgen.out(" lda #<($asmArrayvarname+$indexValue) | ldy #>($asmArrayvarname+$indexValue)")
asmgen.out(if(incr) " jsr floats.inc_var_f" else " jsr floats.dec_var_f")
}
else -> throw AssemblyError("need numeric type")
}
}
else
{
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, CpuRegister.X)
when(elementDt) {
in ByteDatatypes -> {
asmgen.out(if (incr) " inc $asmArrayvarname,x" else " dec $asmArrayvarname,x")
}
in WordDatatypes -> {
if(incr)
asmgen.out(" inc $asmArrayvarname,x | bne + | inc $asmArrayvarname+1,x |+")
else
asmgen.out("""
lda $asmArrayvarname,x
bne +
dec $asmArrayvarname+1,x
+ dec $asmArrayvarname,x""")
}
DataType.FLOAT -> {
asmgen.out("""
ldy #>$asmArrayvarname
clc
adc #<$asmArrayvarname
bcc +
iny
+ jsr floats.inc_var_f""")
}
else -> throw AssemblyError("weird array elt dt")
}
}
}
}
}
}
@@ -173,6 +173,7 @@ internal class ProgramAndVarsGen(
for(num in 1..count) {
val name = asmgen.buildTempVarName(dt, num)
when (dt) {
DataType.BOOL -> asmgen.out("$name .byte ?")
DataType.BYTE -> asmgen.out("$name .char ?")
DataType.UBYTE -> asmgen.out("$name .byte ?")
DataType.WORD -> asmgen.out("$name .sint ?")
@@ -404,7 +405,7 @@ internal class ProgramAndVarsGen(
if(sub.parameters.size==1) {
val dt = sub.parameters[0].type
val target = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, dt, sub, sub.parameters[0].position, variableAsmName = sub.parameters[0].name)
if(dt in ByteDatatypes)
if(dt in ByteDatatypesWithBoolean)
asmgen.assignRegister(RegisterOrPair.A, target)
else
asmgen.assignRegister(RegisterOrPair.AY, target)
@@ -604,7 +605,7 @@ internal class ProgramAndVarsGen(
private fun uninitializedVariable2asm(variable: StStaticVariable) {
when (variable.dt) {
DataType.UBYTE -> asmgen.out("${variable.name}\t.byte ?")
DataType.BOOL, DataType.UBYTE -> asmgen.out("${variable.name}\t.byte ?")
DataType.BYTE -> asmgen.out("${variable.name}\t.char ?")
DataType.UWORD -> asmgen.out("${variable.name}\t.word ?")
DataType.WORD -> asmgen.out("${variable.name}\t.sint ?")
@@ -634,6 +635,7 @@ internal class ProgramAndVarsGen(
} else 0
when (variable.dt) {
DataType.BOOL -> TODO("bool var to asm")
DataType.UBYTE -> asmgen.out("${variable.name}\t.byte ${initialValue.toHex()}")
DataType.BYTE -> asmgen.out("${variable.name}\t.char $initialValue")
DataType.UWORD -> asmgen.out("${variable.name}\t.word ${initialValue.toHex()}")
@@ -658,7 +660,7 @@ internal class ProgramAndVarsGen(
private fun arrayVariable2asm(varname: String, dt: DataType, value: StArray?, orNumberOfZeros: Int?) {
when(dt) {
DataType.ARRAY_UB -> {
DataType.ARRAY_UB, DataType.ARRAY_BOOL -> {
val data = makeArrayFillDataUnsigned(dt, value, orNumberOfZeros)
if (data.size <= 16)
asmgen.out("$varname\t.byte ${data.joinToString()}")
@@ -726,7 +728,7 @@ internal class ProgramAndVarsGen(
private fun zeroFilledArray(numElts: Int): StArray {
val values = mutableListOf<StArrayElement>()
repeat(numElts) {
values.add(StArrayElement(0.0, null))
values.add(StArrayElement(0.0, null, null))
}
return values
}
@@ -763,6 +765,16 @@ internal class ProgramAndVarsGen(
private fun makeArrayFillDataUnsigned(dt: DataType, value: StArray?, orNumberOfZeros: Int?): List<String> {
val array = value ?: zeroFilledArray(orNumberOfZeros!!)
return when (dt) {
DataType.ARRAY_BOOL ->
// byte array can never contain pointer-to types, so treat values as all integers
array.map {
if(it.boolean!=null)
if(it.boolean==true) "1" else "0"
else {
val number = it.number!!
if(number==0.0) "0" else "1"
}
}
DataType.ARRAY_UB ->
// byte array can never contain pointer-to types, so treat values as all integers
array.map {
@@ -91,7 +91,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
if(errors.noErrors()) {
val sortedList = varsDontCare.sortedByDescending { it.scopedName }
for (variable in sortedList) {
if(variable.dt in IntegerDatatypes) {
if(variable.dt in IntegerDatatypesWithBoolean) {
if(zeropage.free.isEmpty()) {
break
} else {
@@ -18,12 +18,12 @@ internal class AnyExprAsmGen(
) {
fun assignAnyExpressionUsingStack(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
when(expr.type) {
in ByteDatatypes -> {
if(expr.left.type in ByteDatatypes && expr.right.type in ByteDatatypes)
in ByteDatatypesWithBoolean -> {
if(expr.left.type in ByteDatatypesWithBoolean && expr.right.type in ByteDatatypesWithBoolean)
return assignByteBinExpr(expr, assign)
if (expr.left.type in WordDatatypes && expr.right.type in WordDatatypes) {
require(expr.operator in ComparisonOperators)
throw AssemblyError("words operands comparison -> byte, should have been handled by assignOptimizedComparisonWords()")
throw AssemblyError("words operands comparison -> byte, should have been handled elsewhere")
}
if (expr.left.type==DataType.FLOAT && expr.right.type==DataType.FLOAT) {
require(expr.operator in ComparisonOperators)
@@ -14,6 +14,7 @@ internal enum class TargetStorageKind {
}
internal enum class SourceStorageKind {
LITERALBOOLEAN,
LITERALNUMBER,
VARIABLE,
ARRAY,
@@ -43,8 +44,8 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
}
init {
if(register!=null && datatype !in NumericDatatypes)
throw AssemblyError("register must be integer or float type")
if(register!=null && datatype !in NumericDatatypesWithBoolean)
throw AssemblyError("must be numeric type")
}
companion object {
@@ -133,6 +134,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
val memory: PtMemoryByte? = null,
val register: RegisterOrPair? = null,
val number: PtNumber? = null,
val boolean: PtBool? = null,
val expression: PtExpression? = null
)
{
@@ -147,6 +149,9 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
val cv = value as? PtNumber
if(cv!=null)
return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, cv.type, number = cv)
val bv = value as? PtBool
if(bv!=null)
return AsmAssignSource(SourceStorageKind.LITERALBOOLEAN, program, asmgen, DataType.BOOL, boolean = bv)
return when(value) {
// checked above: is PtNumber -> throw AssemblyError("should have been constant value")
@@ -194,7 +199,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
// allow some signed/unsigned relaxations
fun withAdjustedDt(newType: DataType) =
AsmAssignSource(kind, program, asmgen, newType, variableAsmName, array, memory, register, number, expression)
AsmAssignSource(kind, program, asmgen, newType, variableAsmName, array, memory, register, number, boolean, expression)
if(target.datatype!=datatype) {
if(target.datatype in ByteDatatypes && datatype in ByteDatatypes) {
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+2 -1
View File
@@ -5,7 +5,7 @@ import prog8.code.core.*
internal object DummyMemsizer : IMemSizer {
override fun memorySize(dt: DataType) = when(dt) {
in ByteDatatypes -> 1
in ByteDatatypesWithBoolean -> 1
DataType.FLOAT -> 5
else -> 2
}
@@ -58,6 +58,7 @@ internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors:
}
override fun noErrors(): Boolean = errors.isEmpty()
override fun noErrorForLine(position: Position) = !errors.any { ":${position.line}:" in it }
override fun report() {
infos.forEach { println("UNITTEST COMPILATION REPORT: INFO: $it") }
+1 -1
View File
@@ -29,7 +29,7 @@ dependencies {
implementation project(':codeGenIntermediate')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.20"
}
+4 -1
View File
@@ -28,9 +28,12 @@ dependencies {
implementation project(':intermediate')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.20"
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.8.0'
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
sourceSets {
File diff suppressed because it is too large. Load diff
@@ -1,10 +1,7 @@
package prog8.codegen.intermediate
import prog8.code.ast.*
import prog8.code.core.AssemblyError
import prog8.code.core.DataType
import prog8.code.core.SignedDatatypes
import prog8.code.core.SplitWordArrayTypes
import prog8.code.core.*
import prog8.intermediate.*
@@ -48,10 +45,72 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"prog8_lib_stringcompare" -> funcStringCompare(call)
"prog8_lib_square_byte" -> funcSquare(call, IRDataType.BYTE)
"prog8_lib_square_word" -> funcSquare(call, IRDataType.WORD)
"prog8_lib_arraycopy" -> funcArrayCopy(call)
else -> throw AssemblyError("missing builtinfunc for ${call.name}")
}
}
private fun funcArrayCopy(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val source = call.args[0] as PtIdentifier
val target = call.args[1] as PtIdentifier
val sourceLength = codeGen.symbolTable.getLength(source.name)!!
val targetLength = codeGen.symbolTable.getLength(target.name)!!
require(sourceLength==targetLength)
val result = mutableListOf<IRCodeChunkBase>()
val fromReg = codeGen.registers.nextFree()
val toReg = codeGen.registers.nextFree()
val countReg = codeGen.registers.nextFree()
if(source.type in SplitWordArrayTypes && target.type in SplitWordArrayTypes) {
// split words -> split words, copy lsb and msb arrays separately
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=fromReg, labelSymbol = source.name+"_lsb")
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=toReg, labelSymbol = target.name+"_lsb")
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=countReg, immediate = sourceLength)
it += codeGen.makeSyscall(IMSyscall.MEMCOPY_SMALL, listOf(IRDataType.WORD to fromReg, IRDataType.WORD to toReg, IRDataType.BYTE to (countReg and 255)), returns = null)
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=fromReg, labelSymbol = source.name+"_msb")
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=toReg, labelSymbol = target.name+"_msb")
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=countReg, immediate = sourceLength)
it += codeGen.makeSyscall(IMSyscall.MEMCOPY_SMALL, listOf(IRDataType.WORD to fromReg, IRDataType.WORD to toReg, IRDataType.BYTE to (countReg and 255)), returns = null)
}
}
else if(source.type in SplitWordArrayTypes) {
// split -> normal words
require(target.type==DataType.ARRAY_UW || target.type==DataType.ARRAY_W)
val fromRegMsb = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=fromReg, labelSymbol = source.name+"_lsb")
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=fromRegMsb, labelSymbol = source.name+"_msb")
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=toReg, labelSymbol = target.name)
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=countReg, immediate = sourceLength)
}
result += codeGen.makeSyscall(IMSyscall.ARRAYCOPY_SPLITW_TO_NORMAL, listOf(IRDataType.WORD to fromReg, IRDataType.WORD to fromRegMsb, IRDataType.WORD to toReg, IRDataType.BYTE to countReg), returns = null)
}
else if(target.type in SplitWordArrayTypes) {
// normal -> split words
require(source.type==DataType.ARRAY_UW || source.type==DataType.ARRAY_W)
val toRegMsb = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=fromReg, labelSymbol = source.name)
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=toReg, labelSymbol = target.name+"_lsb")
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=toRegMsb, labelSymbol = target.name+"_msb")
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=countReg, immediate = sourceLength)
}
result += codeGen.makeSyscall(IMSyscall.ARRAYCOPY_NORMAL_TO_SPLITW, listOf(IRDataType.WORD to fromReg, IRDataType.WORD to toReg, IRDataType.WORD to toRegMsb, IRDataType.BYTE to countReg), returns = null)
}
else {
// normal array to array copy (various element types)
val eltsize = codeGen.options.compTarget.memorySize(ArrayToElementTypes.getValue(source.type))
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=fromReg, labelSymbol = source.name)
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=toReg, labelSymbol = target.name)
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=countReg, immediate = sourceLength * eltsize)
}
result += codeGen.makeSyscall(IMSyscall.MEMCOPY_SMALL, listOf(IRDataType.WORD to fromReg, IRDataType.WORD to toReg, IRDataType.BYTE to (countReg and 255)), returns = null)
}
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
private fun funcSquare(call: PtBuiltinFunctionCall, resultType: IRDataType): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val valueTr = exprGen.translateExpression(call.args[0])
@@ -185,7 +244,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val tr = exprGen.translateExpression(arrayName)
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = arrayLength), null)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = arrayLength!! and 255), null)
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to tr.resultReg)
return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1)
}
@@ -213,7 +272,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val tr = exprGen.translateExpression(arrayName)
addToResult(result, tr, tr.resultReg, -1)
val lengthReg = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = arrayLength), null)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = arrayLength!! and 255), null)
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to tr.resultReg)
return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1)
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -337,10 +337,33 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
}
}
}
// a SNZ etc. whose target register is not used can be removed altogether
if(ins.opcode in OpcodesThatSetRegFromStatusbits) {
val usages = regUsages(ins.reg1!!)
if(usages.toList().sumOf { it.second } <= 1) {
chunk.instructions.removeAt(idx)
changed = true
}
}
}
return changed
}
private fun regUsages(register: Int): Map<IRCodeChunkBase, Int> {
val chunks = mutableMapOf<IRCodeChunkBase, Int>()
irprog.foreachSub { sub ->
sub.chunks.forEach { chunk ->
val used = chunk.usedRegisters()
val numUsages = used.readRegs.getOrDefault(register, 0) + used.writeRegs.getOrDefault(register, 0)
if(numUsages>0) {
chunks[chunk] = numUsages
}
}
}
return chunks
}
private fun removeUselessArithmetic(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
// note: this is hard to solve for the non-immediate instructions atm because the values are loaded into registers first
var changed = false
@@ -148,6 +148,7 @@ class IRUnusedCodeRemover(
val entrypointSub = irprog.blocks.single { it.label=="main" }
.children.single { it is IRSubroutine && it.label=="main.start" }
val reachable = mutableSetOf((entrypointSub as IRSubroutine).chunks.first())
reachable.add(irprog.globalInits)
// all chunks referenced in array initializer values are also 'reachable':
irprog.st.allVariables()
@@ -168,7 +169,7 @@ class IRUnusedCodeRemover(
it.instructions.forEach { instr ->
if (instr.branchTarget == null)
instr.labelSymbol?.let { label ->
val chunk = allLabeledChunks[label.substringBeforeLast('.')]
val chunk = allLabeledChunks[label] ?: allLabeledChunks[label.substringBeforeLast('.')]
if(chunk!=null)
new+=chunk
else
@@ -230,6 +231,7 @@ class IRUnusedCodeRemover(
}
}
linkedChunks.add(irprog.globalInits)
return removeUnlinkedChunks(linkedChunks)
}
@@ -0,0 +1,47 @@
package prog8.codegen.intermediate
import prog8.code.ast.*
internal fun makeAllNodenamesScoped(program: PtProgram) {
val renames = mutableListOf<Pair<PtNamedNode, String>>()
fun recurse(node: PtNode) {
node.children.forEach {
if(it is PtNamedNode)
renames.add(it to it.scopedName)
recurse(it)
}
}
recurse(program)
renames.forEach { it.first.name = it.second }
}
internal fun moveAllNestedSubroutinesToBlockScope(program: PtProgram) {
val movedSubs = mutableListOf<Pair<PtBlock, PtSub>>()
val removedSubs = mutableListOf<Pair<PtSub, PtSub>>()
fun moveToBlock(block: PtBlock, parent: PtSub, asmsub: PtAsmSub) {
block.add(asmsub)
parent.children.remove(asmsub)
}
fun moveToBlock(block: PtBlock, parent: PtSub, sub: PtSub) {
sub.children.filterIsInstance<PtSub>().forEach { subsub -> moveToBlock(block, sub, subsub) }
sub.children.filterIsInstance<PtAsmSub>().forEach { asmsubsub -> moveToBlock(block, sub, asmsubsub) }
movedSubs += Pair(block, sub)
removedSubs += Pair(parent, sub)
}
program.allBlocks().forEach { block ->
block.children.toList().forEach {
if (it is PtSub) {
// Only regular subroutines can have nested subroutines.
it.children.filterIsInstance<PtSub>().forEach { subsub -> moveToBlock(block, it, subsub) }
it.children.filterIsInstance<PtAsmSub>().forEach { asmsubsub -> moveToBlock(block, it, asmsubsub) }
}
}
}
removedSubs.forEach { (parent, sub) -> parent.children.remove(sub) }
movedSubs.forEach { (block, sub) -> block.add(sub) }
}
+2 -1
View File
@@ -3,7 +3,7 @@ import prog8.code.core.*
internal object DummyMemsizer : IMemSizer {
override fun memorySize(dt: DataType) = when(dt) {
in ByteDatatypes -> 1
in ByteDatatypesWithBoolean -> 1
DataType.FLOAT -> 5
else -> 2
}
@@ -56,6 +56,7 @@ internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors:
}
override fun noErrors(): Boolean = errors.isEmpty()
override fun noErrorForLine(position: Position) = !errors.any { ":${position.line}:" in it }
override fun report() {
infos.forEach { println("UNITTEST COMPILATION REPORT: INFO: $it") }
+20 -20
View File
@@ -125,7 +125,7 @@ class TestVmCodeGen: FunSpec({
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("f1", DataType.FLOAT, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
val if1 = PtIfElse(Position.DUMMY)
val cmp1 = PtBinaryExpression("==", DataType.UBYTE, Position.DUMMY)
val cmp1 = PtBinaryExpression("==", DataType.BOOL, Position.DUMMY)
cmp1.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
cmp1.add(PtNumber(DataType.FLOAT, 0.0, Position.DUMMY))
if1.add(cmp1)
@@ -133,7 +133,7 @@ class TestVmCodeGen: FunSpec({
if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
sub.add(if1)
val if2 = PtIfElse(Position.DUMMY)
val cmp2 = PtBinaryExpression("!=", DataType.UBYTE, Position.DUMMY)
val cmp2 = PtBinaryExpression("!=", DataType.BOOL, Position.DUMMY)
cmp2.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
cmp2.add(PtNumber(DataType.FLOAT, 0.0, Position.DUMMY))
if2.add(cmp2)
@@ -141,7 +141,7 @@ class TestVmCodeGen: FunSpec({
if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
sub.add(if2)
val if3 = PtIfElse(Position.DUMMY)
val cmp3 = PtBinaryExpression("<", DataType.UBYTE, Position.DUMMY)
val cmp3 = PtBinaryExpression("<", DataType.BOOL, Position.DUMMY)
cmp3.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
cmp3.add(PtNumber(DataType.FLOAT, 0.0, Position.DUMMY))
if3.add(cmp3)
@@ -149,7 +149,7 @@ class TestVmCodeGen: FunSpec({
if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
sub.add(if3)
val if4 = PtIfElse(Position.DUMMY)
val cmp4 = PtBinaryExpression(">", DataType.UBYTE, Position.DUMMY)
val cmp4 = PtBinaryExpression(">", DataType.BOOL, Position.DUMMY)
cmp4.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
cmp4.add(PtNumber(DataType.FLOAT, 0.0, Position.DUMMY))
if4.add(cmp4)
@@ -188,7 +188,7 @@ class TestVmCodeGen: FunSpec({
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("f1", DataType.FLOAT, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
val if1 = PtIfElse(Position.DUMMY)
val cmp1 = PtBinaryExpression("==", DataType.UBYTE, Position.DUMMY)
val cmp1 = PtBinaryExpression("==", DataType.BOOL, Position.DUMMY)
cmp1.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
cmp1.add(PtNumber(DataType.FLOAT, 42.0, Position.DUMMY))
if1.add(cmp1)
@@ -196,7 +196,7 @@ class TestVmCodeGen: FunSpec({
if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
sub.add(if1)
val if2 = PtIfElse(Position.DUMMY)
val cmp2 = PtBinaryExpression("!=", DataType.UBYTE, Position.DUMMY)
val cmp2 = PtBinaryExpression("!=", DataType.BOOL, Position.DUMMY)
cmp2.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
cmp2.add(PtNumber(DataType.FLOAT, 42.0, Position.DUMMY))
if2.add(cmp2)
@@ -204,7 +204,7 @@ class TestVmCodeGen: FunSpec({
if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
sub.add(if2)
val if3 = PtIfElse(Position.DUMMY)
val cmp3 = PtBinaryExpression("<", DataType.UBYTE, Position.DUMMY)
val cmp3 = PtBinaryExpression("<", DataType.BOOL, Position.DUMMY)
cmp3.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
cmp3.add(PtNumber(DataType.FLOAT, 42.0, Position.DUMMY))
if3.add(cmp3)
@@ -212,7 +212,7 @@ class TestVmCodeGen: FunSpec({
if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
sub.add(if3)
val if4 = PtIfElse(Position.DUMMY)
val cmp4 = PtBinaryExpression(">", DataType.UBYTE, Position.DUMMY)
val cmp4 = PtBinaryExpression(">", DataType.BOOL, Position.DUMMY)
cmp4.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
cmp4.add(PtNumber(DataType.FLOAT, 42.0, Position.DUMMY))
if4.add(cmp4)
@@ -247,7 +247,7 @@ class TestVmCodeGen: FunSpec({
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("f1", DataType.FLOAT, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
val if1 = PtIfElse(Position.DUMMY)
val cmp1 = PtBinaryExpression("==", DataType.UBYTE, Position.DUMMY)
val cmp1 = PtBinaryExpression("==", DataType.BOOL, Position.DUMMY)
cmp1.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
cmp1.add(PtNumber(DataType.FLOAT, 42.0, Position.DUMMY))
if1.add(cmp1)
@@ -255,7 +255,7 @@ class TestVmCodeGen: FunSpec({
if1.add(PtNodeGroup())
sub.add(if1)
val if2 = PtIfElse(Position.DUMMY)
val cmp2 = PtBinaryExpression(">", DataType.UBYTE, Position.DUMMY)
val cmp2 = PtBinaryExpression(">", DataType.BOOL, Position.DUMMY)
cmp2.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
cmp2.add(PtNumber(DataType.FLOAT, 42.0, Position.DUMMY))
if2.add(cmp2)
@@ -294,7 +294,7 @@ class TestVmCodeGen: FunSpec({
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("sb1", DataType.BYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
val if1 = PtIfElse(Position.DUMMY)
val cmp1 = PtBinaryExpression("==", DataType.BYTE, Position.DUMMY)
val cmp1 = PtBinaryExpression("==", DataType.BOOL, Position.DUMMY)
cmp1.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
cmp1.add(PtNumber(DataType.BYTE, 0.0, Position.DUMMY))
if1.add(cmp1)
@@ -302,7 +302,7 @@ class TestVmCodeGen: FunSpec({
if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
sub.add(if1)
val if2 = PtIfElse(Position.DUMMY)
val cmp2 = PtBinaryExpression("!=", DataType.BYTE, Position.DUMMY)
val cmp2 = PtBinaryExpression("!=", DataType.BOOL, Position.DUMMY)
cmp2.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
cmp2.add(PtNumber(DataType.BYTE, 0.0, Position.DUMMY))
if2.add(cmp2)
@@ -310,7 +310,7 @@ class TestVmCodeGen: FunSpec({
if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
sub.add(if2)
val if3 = PtIfElse(Position.DUMMY)
val cmp3 = PtBinaryExpression("<", DataType.BYTE, Position.DUMMY)
val cmp3 = PtBinaryExpression("<", DataType.BOOL, Position.DUMMY)
cmp3.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
cmp3.add(PtNumber(DataType.BYTE, 0.0, Position.DUMMY))
if3.add(cmp3)
@@ -318,7 +318,7 @@ class TestVmCodeGen: FunSpec({
if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
sub.add(if3)
val if4 = PtIfElse(Position.DUMMY)
val cmp4 = PtBinaryExpression(">", DataType.BYTE, Position.DUMMY)
val cmp4 = PtBinaryExpression(">", DataType.BOOL, Position.DUMMY)
cmp4.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
cmp4.add(PtNumber(DataType.BYTE, 0.0, Position.DUMMY))
if4.add(cmp4)
@@ -357,7 +357,7 @@ class TestVmCodeGen: FunSpec({
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("sb1", DataType.BYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
val if1 = PtIfElse(Position.DUMMY)
val cmp1 = PtBinaryExpression("==", DataType.BYTE, Position.DUMMY)
val cmp1 = PtBinaryExpression("==", DataType.BOOL, Position.DUMMY)
cmp1.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
cmp1.add(PtNumber(DataType.BYTE, 42.0, Position.DUMMY))
if1.add(cmp1)
@@ -365,7 +365,7 @@ class TestVmCodeGen: FunSpec({
if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
sub.add(if1)
val if2 = PtIfElse(Position.DUMMY)
val cmp2 = PtBinaryExpression("!=", DataType.BYTE, Position.DUMMY)
val cmp2 = PtBinaryExpression("!=", DataType.BOOL, Position.DUMMY)
cmp2.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
cmp2.add(PtNumber(DataType.BYTE, 42.0, Position.DUMMY))
if2.add(cmp2)
@@ -373,7 +373,7 @@ class TestVmCodeGen: FunSpec({
if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
sub.add(if2)
val if3 = PtIfElse(Position.DUMMY)
val cmp3 = PtBinaryExpression("<", DataType.BYTE, Position.DUMMY)
val cmp3 = PtBinaryExpression("<", DataType.BOOL, Position.DUMMY)
cmp3.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
cmp3.add(PtNumber(DataType.BYTE, 42.0, Position.DUMMY))
if3.add(cmp3)
@@ -381,7 +381,7 @@ class TestVmCodeGen: FunSpec({
if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
sub.add(if3)
val if4 = PtIfElse(Position.DUMMY)
val cmp4 = PtBinaryExpression(">", DataType.BYTE, Position.DUMMY)
val cmp4 = PtBinaryExpression(">", DataType.BOOL, Position.DUMMY)
cmp4.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
cmp4.add(PtNumber(DataType.BYTE, 42.0, Position.DUMMY))
if4.add(cmp4)
@@ -416,7 +416,7 @@ class TestVmCodeGen: FunSpec({
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("ub1", DataType.UBYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
val if1 = PtIfElse(Position.DUMMY)
val cmp1 = PtBinaryExpression("==", DataType.UBYTE, Position.DUMMY)
val cmp1 = PtBinaryExpression("==", DataType.BOOL, Position.DUMMY)
cmp1.add(PtIdentifier("main.start.ub1", DataType.UBYTE, Position.DUMMY))
cmp1.add(PtNumber(DataType.UBYTE, 42.0, Position.DUMMY))
if1.add(cmp1)
@@ -424,7 +424,7 @@ class TestVmCodeGen: FunSpec({
if1.add(PtNodeGroup())
sub.add(if1)
val if2 = PtIfElse(Position.DUMMY)
val cmp2 = PtBinaryExpression(">", DataType.UBYTE, Position.DUMMY)
val cmp2 = PtBinaryExpression(">", DataType.BOOL, Position.DUMMY)
cmp2.add(PtIdentifier("main.start.ub1", DataType.UBYTE, Position.DUMMY))
cmp2.add(PtNumber(DataType.UBYTE, 42.0, Position.DUMMY))
if2.add(cmp2)
+1 -1
View File
@@ -27,7 +27,7 @@ dependencies {
implementation project(':codeCore')
implementation project(':compilerAst')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.20"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
}
@@ -7,6 +7,7 @@ import prog8.ast.expressions.FunctionCallExpression
import prog8.ast.expressions.NumericLiteral
import prog8.code.core.DataType
import prog8.code.core.IntegerDatatypes
import prog8.code.core.IntegerDatatypesWithBoolean
import prog8.code.core.Position
import kotlin.math.*
@@ -69,15 +70,15 @@ class ConstExprEvaluator {
}
private fun bitwiseXor(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
if(left.type== DataType.UBYTE) {
if(right.type in IntegerDatatypes) {
if(left.type==DataType.UBYTE || left.type==DataType.BOOL) {
if(right.type in IntegerDatatypesWithBoolean) {
return NumericLiteral(DataType.UBYTE, (left.number.toInt() xor (right.number.toInt() and 255)).toDouble(), left.position)
}
} else if(left.type== DataType.UWORD) {
} else if(left.type==DataType.UWORD) {
if(right.type in IntegerDatatypes) {
return NumericLiteral(DataType.UWORD, (left.number.toInt() xor right.number.toInt() and 65535).toDouble(), left.position)
}
} else if(left.type== DataType.LONG) {
} else if(left.type==DataType.LONG) {
if(right.type in IntegerDatatypes) {
return NumericLiteral.optimalNumeric((left.number.toInt() xor right.number.toInt()).toDouble(), left.position)
}
@@ -86,15 +87,15 @@ class ConstExprEvaluator {
}
private fun bitwiseOr(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
if(left.type== DataType.UBYTE) {
if(right.type in IntegerDatatypes) {
if(left.type==DataType.UBYTE || left.type==DataType.BOOL) {
if(right.type in IntegerDatatypesWithBoolean) {
return NumericLiteral(DataType.UBYTE, (left.number.toInt() or (right.number.toInt() and 255)).toDouble(), left.position)
}
} else if(left.type== DataType.UWORD) {
} else if(left.type==DataType.UWORD) {
if(right.type in IntegerDatatypes) {
return NumericLiteral(DataType.UWORD, (left.number.toInt() or right.number.toInt() and 65535).toDouble(), left.position)
}
} else if(left.type== DataType.LONG) {
} else if(left.type==DataType.LONG) {
if(right.type in IntegerDatatypes) {
return NumericLiteral.optimalNumeric((left.number.toInt() or right.number.toInt()).toDouble(), left.position)
}
@@ -103,11 +104,11 @@ class ConstExprEvaluator {
}
private fun bitwiseAnd(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
if(left.type== DataType.UBYTE) {
if(right.type in IntegerDatatypes) {
if(left.type==DataType.UBYTE || left.type==DataType.BOOL) {
if(right.type in IntegerDatatypesWithBoolean) {
return NumericLiteral(DataType.UBYTE, (left.number.toInt() and (right.number.toInt() and 255)).toDouble(), left.position)
}
} else if(left.type== DataType.UWORD) {
} else if(left.type==DataType.UWORD) {
if(right.type in IntegerDatatypes) {
return NumericLiteral(DataType.UWORD, (left.number.toInt() and right.number.toInt() and 65535).toDouble(), left.position)
}
@@ -36,9 +36,9 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
if(parent is Assignment) {
val iDt = parent.target.inferType(program)
if(iDt.isKnown && !iDt.isBool && !iDt.istype(numLiteral.type)) {
val casted = numLiteral.cast(iDt.getOr(DataType.UNDEFINED))
val casted = numLiteral.cast(iDt.getOr(DataType.UNDEFINED), true)
if(casted.isValid) {
return listOf(IAstModification.ReplaceNode(numLiteral, casted.value!!, parent))
return listOf(IAstModification.ReplaceNode(numLiteral, casted.valueOrZero(), parent))
}
}
}
@@ -313,23 +313,23 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
fun adjustRangeDt(rangeFrom: NumericLiteral, targetDt: DataType, rangeTo: NumericLiteral, stepLiteral: NumericLiteral?, range: RangeExpression): RangeExpression? {
val fromCast = rangeFrom.cast(targetDt)
val toCast = rangeTo.cast(targetDt)
val fromCast = rangeFrom.cast(targetDt, true)
val toCast = rangeTo.cast(targetDt, true)
if(!fromCast.isValid || !toCast.isValid)
return null
val newStep =
if(stepLiteral!=null) {
val stepCast = stepLiteral.cast(targetDt)
val stepCast = stepLiteral.cast(targetDt, true)
if(stepCast.isValid)
stepCast.value!!
stepCast.valueOrZero()
else
range.step
} else {
range.step
}
return RangeExpression(fromCast.value!!, toCast.value!!, newStep, range.position)
return RangeExpression(fromCast.valueOrZero(), toCast.valueOrZero(), newStep, range.position)
}
// adjust the datatype of a range expression in for loops to the loop variable.
@@ -386,9 +386,9 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
val valueDt = numval.inferType(program)
if(valueDt isnot decl.datatype) {
if(decl.datatype!=DataType.BOOL || valueDt.isnot(DataType.UBYTE)) {
val cast = numval.cast(decl.datatype)
val cast = numval.cast(decl.datatype, true)
if (cast.isValid)
return listOf(IAstModification.ReplaceNode(numval, cast.value!!, decl))
return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl))
}
}
}
@@ -35,12 +35,12 @@ class VarConstantValueTypeAdjuster(
// avoid silent float roundings
if(decl.datatype in IntegerDatatypes && declConstValue.type == DataType.FLOAT) {
errors.err("refused truncating of float to avoid loss of precision", decl.value!!.position)
} else {
// cast the numeric literal to the appropriate datatype of the variable
} else if(decl.datatype!=DataType.BOOL) {
// cast the numeric literal to the appropriate datatype of the variable if it's not boolean
declConstValue.linkParents(decl)
val cast = declConstValue.cast(decl.datatype)
val cast = declConstValue.cast(decl.datatype, true)
if (cast.isValid)
return listOf(IAstModification.ReplaceNode(decl.value!!, cast.value!!, decl))
return listOf(IAstModification.ReplaceNode(decl.value!!, cast.valueOrZero(), decl))
}
}
} catch (x: UndefinedSymbolError) {
@@ -270,7 +270,11 @@ class VarConstantValueTypeAdjuster(
// Replace all constant identifiers with their actual value,
// and the array var initializer values and sizes.
// This is needed because further constant optimizations depend on those.
internal class ConstantIdentifierReplacer(private val program: Program, private val errors: IErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() {
internal class ConstantIdentifierReplacer(
private val program: Program,
private val options: CompilationOptions,
private val errors: IErrorReporter
) : AstWalker() {
override fun before(addressOf: AddressOf, parent: Node): Iterable<IAstModification> {
val constValue = addressOf.constValue(program)
@@ -292,7 +296,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
return noModifications
val dt = identifier.inferType(program)
if(!dt.isKnown || !dt.isNumeric)
if(!dt.isKnown || !dt.isNumeric && !dt.isBool)
return noModifications
try {
@@ -313,7 +317,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
}
}
when (cval.type) {
in NumericDatatypes -> {
in NumericDatatypesWithBoolean -> {
if(parent is AddressOf)
return noModifications // cannot replace the identifier INSIDE the addr-of here, let's do it later.
return listOf(
@@ -369,7 +373,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
DataType.FLOAT -> {
// vardecl: for scalar float vars, promote constant integer initialization values to floats
val litval = decl.value as? NumericLiteral
if (litval!=null && litval.type in IntegerDatatypes) {
if (litval!=null && litval.type in IntegerDatatypesWithBoolean) {
val newValue = NumericLiteral(DataType.FLOAT, litval.number, litval.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
}
@@ -470,12 +474,11 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
val numericLv = decl.value as? NumericLiteral
val size = decl.arraysize?.constIndex() ?: return null
if(rangeExpr==null && numericLv!=null) {
// arraysize initializer is a single int, and we know the size.
// arraysize initializer is a single int, and we know the array size.
val fillvalue = numericLv.number
if (fillvalue < compTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > compTarget.machine.FLOAT_MAX_POSITIVE)
if (fillvalue < options.compTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > options.compTarget.machine.FLOAT_MAX_POSITIVE)
errors.err("float value overflow", numericLv.position)
else {
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.FLOAT, it, numericLv.position) }.toTypedArray<Expression>()
return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = numericLv.position)
}
@@ -485,9 +488,13 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
val numericLv = decl.value as? NumericLiteral
val size = decl.arraysize?.constIndex() ?: return null
if(numericLv!=null) {
// arraysize initializer is a single int, and we know the size.
val fillvalue = if(numericLv.number==0.0) 0.0 else 1.0
val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.UBYTE, fillvalue, numericLv.position) }.toTypedArray<Expression>()
// arraysize initializer is a single value, and we know the array size.
if(numericLv.type!=DataType.BOOL) {
if(options.strictBool || numericLv.type !in ByteDatatypes)
errors.err("initializer value is not a boolean", numericLv.position)
return null
}
val array = Array(size) {numericLv.number}.map { NumericLiteral(DataType.BOOL, it, numericLv.position) }.toTypedArray<Expression>()
return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_BOOL), array, position = numericLv.position)
}
}
@@ -1,13 +1,9 @@
package prog8.optimizer
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.*
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.maySwapOperandOrder
import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.Assignment
import prog8.ast.statements.IfElse
import prog8.ast.statements.Jump
import prog8.ast.walk.AstWalker
@@ -19,9 +15,7 @@ import kotlin.math.pow
// TODO add more peephole expression optimizations? Investigate what optimizations binaryen has?
class ExpressionSimplifier(private val program: Program,
private val errors: IErrorReporter,
private val compTarget: ICompilationTarget) : AstWalker() {
class ExpressionSimplifier(private val program: Program, private val options: CompilationOptions, private val errors: IErrorReporter) : AstWalker() {
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
@@ -31,9 +25,9 @@ class ExpressionSimplifier(private val program: Program,
// try to statically convert a literal value into one of the desired type
val literal = typecast.expression as? NumericLiteral
if (literal != null) {
val newLiteral = literal.cast(typecast.type)
if (newLiteral.isValid && newLiteral.value!! !== literal) {
mods += IAstModification.ReplaceNode(typecast, newLiteral.value!!, parent)
val newLiteral = literal.cast(typecast.type, typecast.implicit)
if (newLiteral.isValid && newLiteral.valueOrZero() !== literal) {
mods += IAstModification.ReplaceNode(typecast, newLiteral.valueOrZero(), parent)
}
}
@@ -114,16 +108,6 @@ class ExpressionSimplifier(private val program: Program,
if (!leftIDt.isKnown || !rightIDt.isKnown)
throw FatalAstException("can't determine datatype of both expression operands $expr")
// ConstValue <associativeoperator> X --> X <associativeoperator> ConstValue
if (leftVal != null && expr.operator in AssociativeOperators && rightVal == null && maySwapOperandOrder(expr))
return listOf(IAstModification.SwapOperands(expr))
// NonBinaryExpression <associativeoperator> BinaryExpression --> BinaryExpression <associativeoperator> NonBinaryExpression
if (expr.operator in AssociativeOperators && expr.left !is BinaryExpression && expr.right is BinaryExpression) {
if(parent !is Assignment || !(expr.left isSameAs parent.target) && maySwapOperandOrder(expr))
return listOf(IAstModification.SwapOperands(expr))
}
// X + (-A) --> X - A
if (expr.operator == "+" && (expr.right as? PrefixExpression)?.operator == "-") {
return listOf(IAstModification.ReplaceNode(
@@ -255,26 +239,98 @@ class ExpressionSimplifier(private val program: Program,
}
}
// boolvar & 1 --> boolvar
// boolvar & 2 --> false
if(expr.operator=="&" && rightDt in IntegerDatatypes && (leftDt == DataType.BOOL || (expr.left as? TypecastExpression)?.expression?.inferType(program)?.istype(DataType.BOOL)==true)) {
if(rightVal?.number==1.0) {
return listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
} else if(rightVal?.number!=null && (rightVal.number.toInt() and 1)==0) {
return listOf(IAstModification.ReplaceNode(expr, NumericLiteral.fromBoolean(false, expr.position), parent))
// optimize boolean constant comparisons
if(expr.operator=="==") {
if(rightDt==DataType.BOOL && leftDt==DataType.BOOL) {
if(rightVal?.asBooleanValue==true)
return listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
else
return listOf(IAstModification.ReplaceNode(expr, PrefixExpression("not", expr.left, expr.position), parent))
}
if (rightVal?.number == 1.0) {
if (options.strictBool) {
if (rightDt != leftDt) {
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
} else
return listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
}
else if (rightVal?.number == 0.0) {
if (options.strictBool) {
if (rightDt != leftDt) {
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
}
}
}
if (expr.operator=="!=") {
if(rightDt==DataType.BOOL && leftDt==DataType.BOOL) {
val rightConstBool = rightVal?.asBooleanValue
if(rightConstBool!=null) {
return if (rightConstBool)
listOf(IAstModification.ReplaceNode(expr, PrefixExpression("not", expr.left, expr.position), parent))
else
listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
}
}
if (rightVal?.number == 1.0) {
if(options.strictBool) {
if(rightDt!=leftDt) {
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
}
}
else if (rightVal?.number == 0.0) {
if(options.strictBool) {
if(rightDt!=leftDt) {
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
} else
return listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
}
}
if(leftDt==DataType.BOOL) {
// optimize boolean constant comparisons
// if(expr.operator=="==" && rightVal?.number==0.0)
// return listOf(IAstModification.ReplaceNode(expr, PrefixExpression("not", expr.left, expr.position), parent))
// if(expr.operator=="!=" && rightVal?.number==1.0)
// return listOf(IAstModification.ReplaceNode(expr, PrefixExpression("not", expr.left, expr.position), parent))
if(expr.operator=="==" && rightVal?.number==1.0)
return listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
if(expr.operator=="!=" && rightVal?.number==0.0)
return listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
if(expr.operator in arrayOf("and", "or", "xor")) {
if(leftVal!=null) {
val result = if(leftVal.asBooleanValue) {
when(expr.operator) {
"and" -> expr.right
"or" -> NumericLiteral.fromBoolean(true, expr.position)
"xor" -> PrefixExpression("not", expr.right, expr.position)
else -> throw FatalAstException("weird op")
}
} else {
when(expr.operator) {
"and" -> NumericLiteral.fromBoolean(false, expr.position)
"or" -> expr.right
"xor" -> expr.right
else -> throw FatalAstException("weird op")
}
}
return listOf(IAstModification.ReplaceNode(expr, result, parent))
}
else if(rightVal!=null) {
val result = if(rightVal.asBooleanValue) {
when(expr.operator) {
"and" -> expr.left
"or" -> NumericLiteral.fromBoolean(true, expr.position)
"xor" -> PrefixExpression("not", expr.left, expr.position)
else -> throw FatalAstException("weird op")
}
} else {
when(expr.operator) {
"and" -> NumericLiteral.fromBoolean(false, expr.position)
"or" -> expr.left
"xor" -> expr.left
else -> throw FatalAstException("weird op")
}
}
return listOf(IAstModification.ReplaceNode(expr, result, parent))
}
}
// simplify when a term is constant and directly determines the outcome
@@ -387,10 +443,14 @@ class ExpressionSimplifier(private val program: Program,
return noModifications
}
private fun applyAbsorptionLaws(expr: BinaryExpression): Expression? {
// NOTE: only when the terms are not function calls!!!
if(expr.left is IFunctionCall || expr.right is IFunctionCall)
return null
val rightB = expr.right as? BinaryExpression
if(rightB!=null) {
if(rightB.left is IFunctionCall || rightB.right is IFunctionCall)
return null
// absorption laws: a or (a and b) --> a, a and (a or b) --> a
if(expr.operator=="or" && rightB.operator=="and") {
if(expr.left isSameAs rightB.left || expr.left isSameAs rightB.right) {
@@ -402,12 +462,26 @@ class ExpressionSimplifier(private val program: Program,
return expr.left
}
}
else if(expr.operator=="or" && rightB.operator=="or") {
if(expr.left isSameAs rightB.left || expr.left isSameAs rightB.right) {
// a or (a or b) -> a or b
return expr.right
}
}
else if(expr.operator=="and" && rightB.operator=="and") {
if(expr.left isSameAs rightB.left || expr.left isSameAs rightB.right) {
// a and (a and b) -> a and b
return expr.right
}
}
}
val leftB = expr.left as? BinaryExpression
if(leftB!=null) {
if(leftB.left is IFunctionCall || leftB.right is IFunctionCall)
return null
// absorption laws: (a and b) or a --> a, (a or b) and a --> a
if(expr.operator=="or" && leftB.operator=="and") {
if(expr.right isSameAs leftB.left || expr.left isSameAs leftB.right) {
if(expr.right isSameAs leftB.left || expr.right isSameAs leftB.right) {
return expr.right
}
}
@@ -416,6 +490,18 @@ class ExpressionSimplifier(private val program: Program,
return expr.right
}
}
else if(expr.operator=="or" && leftB.operator=="or") {
if(expr.right isSameAs leftB.left || expr.right isSameAs leftB.right) {
// (a or b) or a -> a or b
return expr.left
}
}
else if(expr.operator=="and" && leftB.operator=="and") {
if(expr.right isSameAs leftB.left || expr.right isSameAs leftB.right) {
// (a and b) or a -> a and b
return expr.left
}
}
}
return null
}
@@ -3,7 +3,6 @@ package prog8.optimizer
import prog8.ast.IBuiltinFunctions
import prog8.ast.Program
import prog8.code.core.CompilationOptions
import prog8.code.core.ICompilationTarget
import prog8.code.core.IErrorReporter
@@ -13,7 +12,7 @@ fun Program.constantFold(errors: IErrorReporter, options: CompilationOptions) {
if(errors.noErrors()) {
valuetypefixer.applyModifications()
val replacer = ConstantIdentifierReplacer(this, errors, options.compTarget)
val replacer = ConstantIdentifierReplacer(this, options, errors)
replacer.visit(this)
if (errors.noErrors()) {
replacer.applyModifications()
@@ -60,8 +59,8 @@ fun Program.inlineSubroutines(options: CompilationOptions): Int {
return inliner.applyModifications()
}
fun Program.simplifyExpressions(errors: IErrorReporter, target: ICompilationTarget) : Int {
val opti = ExpressionSimplifier(this, errors, target)
fun Program.simplifyExpressions(errors: IErrorReporter, options: CompilationOptions) : Int {
val opti = ExpressionSimplifier(this, options, errors)
opti.visit(this)
return opti.applyModifications()
}
+8 -12
View File
@@ -87,6 +87,14 @@ class Inliner(private val program: Program, private val options: CompilationOpti
} else
false
targetInline || valueInline
} else if(stmt.target.identifier!=null && stmt.isAugmentable) {
val binExpr = stmt.value as BinaryExpression
if(binExpr.operator in "+-" && binExpr.right.constValue(program)?.number==1.0) {
makeFullyScoped(stmt.target.identifier!!)
makeFullyScoped(binExpr.left as IdentifierReference)
true
} else
false
} else
false
}
@@ -107,18 +115,6 @@ class Inliner(private val program: Program, private val options: CompilationOpti
inline
}
is PostIncrDecr -> {
if (stmt.target.identifier != null) {
makeFullyScoped(stmt.target.identifier!!)
true
} else if (stmt.target.memoryAddress?.addressExpression is NumericLiteral || stmt.target.memoryAddress?.addressExpression is IdentifierReference) {
if (stmt.target.memoryAddress?.addressExpression is IdentifierReference)
makeFullyScoped(stmt.target.memoryAddress?.addressExpression as IdentifierReference)
true
} else
false
}
is Jump -> true
else -> false
}
@@ -5,9 +5,10 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.*
import prog8.code.target.VMTarget
import kotlin.math.floor
import prog8.code.core.AssociativeOperators
import prog8.code.core.CompilationOptions
import prog8.code.core.DataType
import prog8.code.core.IErrorReporter
class StatementOptimizer(private val program: Program,
@@ -82,7 +83,7 @@ class StatementOptimizer(private val program: Program,
// empty true part? switch with the else part
if(ifElse.truepart.isEmpty() && ifElse.elsepart.isNotEmpty()) {
val invertedCondition = BinaryExpression(ifElse.condition, "==", NumericLiteral(DataType.UBYTE, 0.0, ifElse.condition.position), ifElse.condition.position)
val invertedCondition = invertCondition(ifElse.condition, program)
val emptyscope = AnonymousScope(mutableListOf(), ifElse.elsepart.position)
val truepart = AnonymousScope(ifElse.elsepart.statements, ifElse.truepart.position)
return listOf(
@@ -102,25 +103,40 @@ class StatementOptimizer(private val program: Program,
}
}
// remove obvious dangling elses (else after a return)
if(ifElse.elsepart.isNotEmpty() && ifElse.truepart.statements.singleOrNull() is Return) {
val elsePart = AnonymousScope(ifElse.elsepart.statements, ifElse.elsepart.position)
return listOf(
IAstModification.ReplaceNode(ifElse.elsepart, AnonymousScope(mutableListOf(), ifElse.elsepart.position), ifElse),
IAstModification.InsertAfter(ifElse, elsePart, parent as IStatementContainer)
)
}
if(ifElse.elsepart.isNotEmpty()) {
// remove obvious dangling elses (else after a return)
if(ifElse.truepart.statements.singleOrNull() is Return) {
val elsePart = AnonymousScope(ifElse.elsepart.statements, ifElse.elsepart.position)
return listOf(
IAstModification.ReplaceNode(ifElse.elsepart, AnonymousScope(mutableListOf(), ifElse.elsepart.position), ifElse),
IAstModification.InsertAfter(ifElse, elsePart, parent as IStatementContainer)
)
}
// switch if/else around if the else is just a jump or branch
if(ifElse.elsepart.isNotEmpty() && ifElse.elsepart.statements.size==1) {
val jump = ifElse.elsepart.statements[0]
if(jump is Jump) {
val newTruePart = AnonymousScope(mutableListOf(jump), ifElse.elsepart.position)
// switch if/else around if the else is just a jump or branch
if(ifElse.elsepart.statements.size==1) {
val jump = ifElse.elsepart.statements[0]
if(jump is Jump) {
val newTruePart = AnonymousScope(mutableListOf(jump), ifElse.elsepart.position)
val newElsePart = AnonymousScope(ifElse.truepart.statements, ifElse.truepart.position)
return listOf(
IAstModification.ReplaceNode(ifElse.elsepart, newElsePart, ifElse),
IAstModification.ReplaceNode(ifElse.truepart, newTruePart, ifElse),
IAstModification.ReplaceNode(ifElse.condition, invertCondition(ifElse.condition, program), ifElse)
)
}
}
// switch if/else around if the condition is a not
val prefixCond = ifElse.condition as? PrefixExpression
if(prefixCond?.operator=="not") {
errors.info("invert conditon and swap if/else blocks", ifElse.condition.position)
val newTruePart = AnonymousScope(ifElse.elsepart.statements, ifElse.elsepart.position)
val newElsePart = AnonymousScope(ifElse.truepart.statements, ifElse.truepart.position)
return listOf(
IAstModification.ReplaceNode(ifElse.elsepart, newElsePart, ifElse),
IAstModification.ReplaceNode(ifElse.truepart, newTruePart, ifElse),
IAstModification.ReplaceNode(ifElse.condition, invertCondition(ifElse.condition, program), ifElse)
IAstModification.ReplaceNode(ifElse.condition, prefixCond.expression, ifElse)
)
}
}
@@ -185,47 +201,12 @@ class StatementOptimizer(private val program: Program,
val loopvarDt = forLoop.loopVarDt(program)
if(loopvarDt.istype(DataType.UWORD) || loopvarDt.istype(DataType.UBYTE)) {
if (range != null && range.from.constValue(program)?.number == 0.0 && range.step.constValue(program)?.number==1.0) {
val toBinExpr = range.to as? BinaryExpression
if(toBinExpr!=null && toBinExpr.operator=="-" && toBinExpr.right.constValue(program)?.number==1.0) {
// FOR var IN 0 TO X-1 .... ---> var=0, DO {... , var++} UNTIL var==X
val pos = forLoop.position
val condition = BinaryExpression(forLoop.loopVar.copy(), "==", toBinExpr.left, pos)
val incOne = PostIncrDecr(AssignTarget(forLoop.loopVar.copy(), null, null, pos), "++", pos)
forLoop.body.statements.add(incOne)
val replacement = AnonymousScope(mutableListOf(
Assignment(AssignTarget(forLoop.loopVar.copy(), null, null, pos),
NumericLiteral.optimalNumeric(0.0, pos),
AssignmentOrigin.OPTIMIZER, pos),
UntilLoop(forLoop.body, condition, pos)
), pos)
return listOf(IAstModification.ReplaceNode(forLoop, replacement, parent))
}
if(options.compTarget.name!=VMTarget.NAME) {
// this optimization is not effective for the VM target.
val toConst = range.to.constValue(program)
if (toConst == null) {
// FOR var in 0 TO X ... ---> var=0, REPEAT { ... , IF var==X break , var++ }
val pos = forLoop.position
val incOne = PostIncrDecr(AssignTarget(forLoop.loopVar.copy(), null, null, pos), "++", pos)
val breakCondition = IfElse(
BinaryExpression(forLoop.loopVar, "==", range.to, pos),
AnonymousScope(mutableListOf(Break(pos)), pos),
AnonymousScope(mutableListOf(), pos),
pos
)
forLoop.body.statements.add(breakCondition)
forLoop.body.statements.add(incOne)
val replacement = AnonymousScope(mutableListOf(
Assignment(AssignTarget(forLoop.loopVar.copy(), null, null, pos),
NumericLiteral.optimalNumeric(0.0, pos),
AssignmentOrigin.OPTIMIZER, pos),
RepeatLoop(null, forLoop.body, pos)
), pos)
return listOf(IAstModification.ReplaceNode(forLoop, replacement, parent))
}
}
fun incOrDec(inc: Boolean): Assignment {
val pos = forLoop.position
val loopVar = forLoop.loopVar
val addSubOne = BinaryExpression(loopVar.copy(), if(inc) "+" else "-", NumericLiteral.optimalInteger(1, pos), pos, false)
return Assignment(AssignTarget(loopVar.copy(), null, null, pos), addSubOne, AssignmentOrigin.USERCODE, pos)
}
if (range != null && range.to.constValue(program)?.number == 0.0 && range.step.constValue(program)?.number==-1.0) {
@@ -235,7 +216,7 @@ class StatementOptimizer(private val program: Program,
val pos = forLoop.position
val checkValue = NumericLiteral(loopvarDt.getOr(DataType.UNDEFINED), if(loopvarDt.istype(DataType.UBYTE)) 255.0 else 65535.0, pos)
val condition = BinaryExpression(forLoop.loopVar.copy(), "==", checkValue, pos)
val decOne = PostIncrDecr(AssignTarget(forLoop.loopVar.copy(), null, null, pos), "--", pos)
val decOne = incOrDec(false)
forLoop.body.statements.add(decOne)
val replacement = AnonymousScope(mutableListOf(
Assignment(AssignTarget(forLoop.loopVar.copy(), null, null, pos),
@@ -390,35 +371,15 @@ class StatementOptimizer(private val program: Program,
// assignments of the form: X = X <operator> <expr>
// remove assignments that have no effect (such as X=X+0)
// optimize/rewrite some other expressions
val targetDt = targetIDt.getOr(DataType.UNDEFINED)
val vardeclDt = (assignment.target.identifier?.targetVarDecl(program))?.type
when (bexpr.operator) {
"+" -> {
if (rightCv == 0.0) {
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..3.0 && options.compTarget.name!=VMTarget.NAME) {
// replace by several INCs if it's not a memory address (inc on a memory mapped register doesn't work very well)
val incs = AnonymousScope(mutableListOf(), assignment.position)
repeat(rightCv.toInt()) {
incs.statements.add(PostIncrDecr(assignment.target.copy(), "++", assignment.position))
}
return listOf(IAstModification.ReplaceNode(assignment, if(incs.statements.size==1) incs.statements[0] else incs, parent))
}
}
}
"-" -> {
if (rightCv == 0.0) {
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..3.0 && options.compTarget.name!=VMTarget.NAME) {
// replace by several DECs if it's not a memory address (dec on a memory mapped register doesn't work very well)
val decs = AnonymousScope(mutableListOf(), assignment.position)
repeat(rightCv.toInt()) {
decs.statements.add(PostIncrDecr(assignment.target.copy(), "--", assignment.position))
}
return listOf(IAstModification.ReplaceNode(assignment, decs, parent))
}
}
}
"*" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
@@ -453,6 +414,33 @@ class StatementOptimizer(private val program: Program,
}
}
// xx+=2 -> xx++ xx++
// note: ideally this optimization should be done by the code generator, but doing it there
// requires doing it multiple times (because lots of different things can be incremented/decremented)
if(assignment.target.identifier!=null
|| assignment.target.arrayindexed?.isSimple==true
|| assignment.target.memoryAddress?.addressExpression?.isSimple==true) {
if(assignment.value.inferType(program).isBytes && assignment.isAugmentable) {
val binExpr = assignment.value as? BinaryExpression
if(binExpr!=null) {
if(binExpr.operator in "+-") {
val value = binExpr.right.constValue(program)?.number?.toInt()
if(value!=null && value==2) {
val stmts = mutableListOf<Statement>()
repeat(value) {
val incrdecr = Assignment(assignment.target.copy(),
BinaryExpression(assignment.target.toExpression(), binExpr.operator, NumericLiteral.optimalInteger(1, assignment.position), assignment.position),
AssignmentOrigin.OPTIMIZER, assignment.position)
stmts.add(incrdecr)
}
val incrdecrs = AnonymousScope(stmts, assignment.position)
return listOf(IAstModification.ReplaceNode(assignment, incrdecrs, parent))
}
}
}
}
}
return noModifications
}
@@ -472,38 +460,6 @@ class StatementOptimizer(private val program: Program,
return listOf(IAstModification.ReplaceNode(whenStmt, ifStmt, parent))
}
if(whenStmt.condition.inferType(program).isBool) {
if(whenStmt.choices.all { it.values?.size==1 }) {
if (whenStmt.choices.all { it.values!!.single().constValue(program)!!.number in arrayOf(0.0, 1.0) }) {
// it's a when statement on booleans that can just be replaced by an if or if-else.
if (whenStmt.choices.size == 1) {
return if(whenStmt.choices[0].values!![0].constValue(program)!!.number==1.0) {
replaceWithIf(whenStmt.condition, whenStmt.choices[0].statements, null)
} else {
val notCondition = BinaryExpression(whenStmt.condition, "==", NumericLiteral(DataType.UBYTE, 0.0, whenStmt.condition.position), whenStmt.condition.position)
replaceWithIf(notCondition, whenStmt.choices[0].statements, null)
}
} else if (whenStmt.choices.size == 2) {
var trueBlock: AnonymousScope? = null
var elseBlock: AnonymousScope? = null
if(whenStmt.choices[0].values!![0].constValue(program)!!.number==1.0) {
trueBlock = whenStmt.choices[0].statements
} else {
elseBlock = whenStmt.choices[0].statements
}
if(whenStmt.choices[1].values!![0].constValue(program)!!.number==1.0) {
trueBlock = whenStmt.choices[1].statements
} else {
elseBlock = whenStmt.choices[1].statements
}
if(trueBlock!=null && elseBlock!=null) {
return replaceWithIf(whenStmt.condition, trueBlock, elseBlock)
}
}
}
}
}
val constantValue = whenStmt.condition.constValue(program)?.number
if(constantValue!=null) {
// when condition is a constant
@@ -20,7 +20,12 @@ class UnusedCodeRemover(private val program: Program,
private val compTarget: ICompilationTarget
): AstWalker() {
private val callgraph = CallGraph(program)
private lateinit var callgraph: CallGraph
override fun before(program: Program): Iterable<IAstModification> {
callgraph = CallGraph(program)
return noModifications
}
override fun before(module: Module, parent: Node): Iterable<IAstModification> {
return if (!module.isLibrary && (module.containsNoCodeNorVars || callgraph.unused(module)))
+7 -4
View File
@@ -2,8 +2,7 @@ plugins {
id 'java'
id 'application'
id 'org.jetbrains.kotlin.jvm'
id 'com.github.johnrengelman.shadow' version '7.1.2'
id 'io.kotest' version '0.3.9'
id 'com.github.johnrengelman.shadow' version '8.1.1'
id 'com.peterabeles.gversion' version '1.10.2'
}
@@ -37,10 +36,14 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.6'
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.20"
testImplementation project(':codeCore')
testImplementation project(':intermediate')
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.8.0'
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
configurations.all {
@@ -102,7 +105,7 @@ test {
gversion {
srcDir = "src/" // path is relative to the sub-project by default
classPackage = "prog8.buildversion"
className = "BuildVersion"
className = "Version"
language = "kotlin"
}
+20 -10
View File
@@ -99,20 +99,14 @@ sys {
rts ; nothing to copy
_copyshort
; decrease source and target pointers so we can simply index by Y
lda P8ZP_SCRATCH_W1
bne +
dec P8ZP_SCRATCH_W1+1
+ dec P8ZP_SCRATCH_W1
lda P8ZP_SCRATCH_W2
bne +
dec P8ZP_SCRATCH_W2+1
+ dec P8ZP_SCRATCH_W2
dey
beq +
- lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
dey
bne -
+ lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
rts
_longcopy
@@ -262,6 +256,22 @@ save_SCRATCH_ZPWORD2 .word 0
}}
}
asmsub exit2(ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y) {
; -- immediately exit the program with result values in the A, X and Y registers.
; TODO where to store A,X,Y as exit code?
%asm {{
jmp exit
}}
}
asmsub exit3(ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y, bool carry @Pc) {
; -- immediately exit the program with result values in the A, X and Y registers, and the Carry flag in the status register.
; TODO where to store A,X,Y,Carry as exit code?
%asm {{
jmp exit
}}
}
inline asmsub progend() -> uword @AY {
%asm {{
lda #<prog8_program_end
+8 -4
View File
@@ -3,7 +3,7 @@
%import syslib
%import conv
%import shared_textio_functions
txt {
@@ -14,15 +14,19 @@ const ubyte DEFAULT_HEIGHT = 24
sub clear_screen() {
txt.chrout(125)
chrout(125)
}
sub nl() {
txt.chrout('\n')
chrout('\n')
}
sub spc() {
txt.chrout(' ')
chrout(' ')
}
sub bell() {
chrout($fd)
}
sub column(ubyte col) {
+50 -12
View File
@@ -98,14 +98,14 @@ romsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; (via 810 ($32A)
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, bool dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, bool dir @ Pc) clobbers(A) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
; ---- end of C64 compatible ROM kernal routines ----
; ---- utilities -----
asmsub STOP2() clobbers(X) -> ubyte @A {
asmsub STOP2() clobbers(X) -> bool @A {
; -- check if STOP key was pressed, returns true if so. More convenient to use than STOP() because that only sets the carry status flag.
%asm {{
jsr cbm.STOP
@@ -138,6 +138,16 @@ sub CLEARST() {
CLOSE(15)
}
asmsub kbdbuf_clear() {
; -- convenience helper routine to clear the keyboard buffer
%asm {{
- jsr GETIN
cmp #0
bne -
rts
}}
}
}
c64 {
@@ -377,8 +387,15 @@ asmsub cleanup_at_exit() {
sta $ff00 ; default bank 15
jsr cbm.CLRCHN ; reset i/o channels
jsr enable_runstop_and_charsetswitch
_exitcodeCarry = *+1
lda #0
lsr a
_exitcode = *+1
lda #0 ; exit code possibly modified in exit()
_exitcodeX = *+1
ldx #0
_exitcodeY = *+1
ldy #0
rts
}}
}
@@ -641,20 +658,14 @@ _loop lda P8ZP_SCRATCH_W1
rts ; nothing to copy
_copyshort
; decrease source and target pointers so we can simply index by Y
lda P8ZP_SCRATCH_W1
bne +
dec P8ZP_SCRATCH_W1+1
+ dec P8ZP_SCRATCH_W1
lda P8ZP_SCRATCH_W2
bne +
dec P8ZP_SCRATCH_W2+1
+ dec P8ZP_SCRATCH_W2
dey
beq +
- lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
dey
bne -
+ lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
rts
_longcopy
@@ -770,6 +781,33 @@ _longcopy
}}
}
asmsub exit2(ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y) {
; -- immediately exit the program with result values in the A, X and Y registers.
%asm {{
sta cleanup_at_exit._exitcode
stx cleanup_at_exit._exitcodeX
sty cleanup_at_exit._exitcodeY
ldx prog8_lib.orig_stackpointer
txs
jmp cleanup_at_exit
}}
}
asmsub exit3(ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y, bool carry @Pc) {
; -- immediately exit the program with result values in the A, X and Y registers, and the Carry flag in the status register.
%asm {{
sta cleanup_at_exit._exitcode
lda #0
rol a
sta cleanup_at_exit._exitcodeCarry
stx cleanup_at_exit._exitcodeX
sty cleanup_at_exit._exitcodeY
ldx prog8_lib.orig_stackpointer
txs
jmp cleanup_at_exit
}}
}
inline asmsub progend() -> uword @AY {
%asm {{
lda #<prog8_program_end
+13 -4
View File
@@ -4,6 +4,7 @@
%import syslib
%import conv
%import shared_textio_functions
txt {
@@ -14,26 +15,32 @@ const ubyte DEFAULT_HEIGHT = 25
sub clear_screen() {
txt.chrout(147)
chrout(147)
}
sub home() {
txt.chrout(19)
chrout(19)
}
sub nl() {
txt.chrout('\n')
chrout('\n')
}
sub spc() {
txt.chrout(' ')
chrout(' ')
}
sub bell() {
chrout(7)
}
asmsub column(ubyte col @A) clobbers(A, X, Y) {
; ---- set the cursor on the given column (starting with 0) on the current line
%asm {{
pha
sec
jsr cbm.PLOT
pla
tay
clc
jmp cbm.PLOT
@@ -51,8 +58,10 @@ asmsub get_column() -> ubyte @Y {
asmsub row(ubyte rownum @A) clobbers(A, X, Y) {
; ---- set the cursor on the given row (starting with 0) on the current line
%asm {{
pha
sec
jsr cbm.PLOT
pla
tax
clc
jmp cbm.PLOT
+16 -1
View File
@@ -98,6 +98,21 @@ cast_as_w_into_ay .proc ; also used for float 2 b
jmp cast_FAC1_as_w_into_ay
.pend
cast_as_bool_into_a .proc
; -- cast float at A/Y to bool into A
; clobbers X
jsr MOVFM
jmp cast_FAC1_as_bool_into_a
.pend
cast_FAC1_as_bool_into_a .proc
; -- cast fac1 to bool into A
; clobbers X
jsr SIGN
and #1
rts
.pend
cast_FAC1_as_uw_into_ya .proc ; also used for float 2 ub
; -- cast fac1 to uword into Y/A
; clobbers X
@@ -221,7 +236,7 @@ var_fac1_greatereq_f .proc
.pend
var_fac1_equal_f .proc
; -- are the floats numbers in FAC1 and the variable AY *not* identical? Result in A. Clobbers X.
; -- are the floats numbers in FAC1 and the variable AY identical? Result in A. Clobbers X.
jsr FCOMP
and #1
eor #1
+1 -1
View File
@@ -1,7 +1,7 @@
; Prog8 definitions for floating point handling on the Commodore-64
%option enable_floats, no_symbol_prefixing, ignore_unused
%import floats_functions
%import shared_floats_functions
floats {
; ---- this block contains C-64 floating point related functions ----
+3 -3
View File
@@ -73,7 +73,7 @@ graphics {
}
word @zp d = 0
ubyte positive_ix = true
bool positive_ix = true
if dx < 0 {
dx = -dx
positive_ix = false
@@ -173,7 +173,7 @@ graphics {
ubyte separate_pixels = lsb(xx) & 7
uword pixaddr = get_y_lookup(yy) + (xx&$fff8)
if separate_pixels {
if separate_pixels!=0 {
%asm {{
lda p8v_pixaddr
sta P8ZP_SCRATCH_W1
@@ -191,7 +191,7 @@ graphics {
length -= 8
}
if length {
if length!=0 {
%asm {{
lda p8v_length
and #7
+50 -12
View File
@@ -99,10 +99,10 @@ romsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; (via 810 ($32A)
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, bool dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, bool dir @ Pc) clobbers(A) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
asmsub STOP2() clobbers(X) -> ubyte @A {
asmsub STOP2() clobbers(X) -> bool @A {
; -- check if STOP key was pressed, returns true if so. More convenient to use than STOP() because that only sets the carry status flag.
%asm {{
jsr cbm.STOP
@@ -135,6 +135,16 @@ sub CLEARST() {
CLOSE(15)
}
asmsub kbdbuf_clear() {
; -- convenience helper routine to clear the keyboard buffer
%asm {{
- jsr GETIN
cmp #0
bne -
rts
}}
}
}
c64 {
@@ -376,8 +386,15 @@ asmsub cleanup_at_exit() {
sta $01 ; bank the kernal in
jsr cbm.CLRCHN ; reset i/o channels
jsr enable_runstop_and_charsetswitch
_exitcodeCarry = *+1
lda #0
lsr a
_exitcode = *+1
lda #0 ; exit code possibly modified in exit()
_exitcodeX = *+1
ldx #0
_exitcodeY = *+1
ldy #0
rts
}}
}
@@ -639,20 +656,14 @@ _loop lda P8ZP_SCRATCH_W1
rts ; nothing to copy
_copyshort
; decrease source and target pointers so we can simply index by Y
lda P8ZP_SCRATCH_W1
bne +
dec P8ZP_SCRATCH_W1+1
+ dec P8ZP_SCRATCH_W1
lda P8ZP_SCRATCH_W2
bne +
dec P8ZP_SCRATCH_W2+1
+ dec P8ZP_SCRATCH_W2
dey
beq +
- lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
dey
bne -
+ lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
rts
_longcopy
@@ -768,6 +779,33 @@ _longcopy
}}
}
asmsub exit2(ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y) {
; -- immediately exit the program with result values in the A, X and Y registers.
%asm {{
sta cleanup_at_exit._exitcode
stx cleanup_at_exit._exitcodeX
sty cleanup_at_exit._exitcodeY
ldx prog8_lib.orig_stackpointer
txs
jmp cleanup_at_exit
}}
}
asmsub exit3(ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y, bool carry @Pc) {
; -- immediately exit the program with result values in the A, X and Y registers, and the Carry flag in the status register.
%asm {{
sta cleanup_at_exit._exitcode
lda #0
rol a
sta cleanup_at_exit._exitcodeCarry
stx cleanup_at_exit._exitcodeX
sty cleanup_at_exit._exitcodeY
ldx prog8_lib.orig_stackpointer
txs
jmp cleanup_at_exit
}}
}
inline asmsub progend() -> uword @AY {
%asm {{
lda #<prog8_program_end
+19 -5
View File
@@ -4,7 +4,7 @@
%import syslib
%import conv
%import shared_textio_functions
txt {
@@ -15,26 +15,38 @@ const ubyte DEFAULT_HEIGHT = 25
sub clear_screen() {
txt.chrout(147)
chrout(147)
}
sub home() {
txt.chrout(19)
chrout(19)
}
sub nl() {
txt.chrout('\n')
chrout('\n')
}
sub spc() {
txt.chrout(' ')
chrout(' ')
}
sub bell() {
; beep
c64.MVOL = 11
c64.AD1 = %00110111
c64.SR1 = %00000000
c64.FREQ1 = 8500
c64.CR1 = %00010000
c64.CR1 = %00010001
}
asmsub column(ubyte col @A) clobbers(A, X, Y) {
; ---- set the cursor on the given column (starting with 0) on the current line
%asm {{
pha
sec
jsr cbm.PLOT
pla
tay
clc
jmp cbm.PLOT
@@ -52,8 +64,10 @@ asmsub get_column() -> ubyte @Y {
asmsub row(ubyte rownum @A) clobbers(A, X, Y) {
; ---- set the cursor on the given row (starting with 0) on the current line
%asm {{
pha
sec
jsr cbm.PLOT
pla
tax
clc
jmp cbm.PLOT
+148 -109
View File
@@ -8,81 +8,108 @@ conv {
str @shared string_out = "????????????????" ; result buffer for the string conversion routines
asmsub str_ub0 (ubyte value @ A) clobbers(A,X,Y) {
asmsub str_ub0 (ubyte value @ A) clobbers(X) -> str @AY {
; ---- convert the ubyte in A in decimal string form, with left padding 0s (3 positions total)
%asm {{
jsr conv.ubyte2decimal
sty string_out
sta string_out+1
stx string_out+2
lda #0
sta string_out+3
rts
jsr conv.ubyte2decimal
sty string_out
sta string_out+1
stx string_out+2
lda #0
sta string_out+3
lda #<string_out
ldy #>string_out
rts
}}
}
asmsub str_ub (ubyte value @ A) clobbers(A,X,Y) {
asmsub str_ub (ubyte value @ A) clobbers(X) -> str @AY {
; ---- convert the ubyte in A in decimal string form, without left padding 0s
%asm {{
ldy #0
sty P8ZP_SCRATCH_B1
jsr conv.ubyte2decimal
_output_byte_digits
; hundreds?
jsr ubyte2decimal ; result in Y/A/X (100s, 10s, 1s).
; hundreds?
cpy #'0'
beq +
pha
tya
ldy P8ZP_SCRATCH_B1
sta string_out,y
pla
inc P8ZP_SCRATCH_B1
sty string_out
sta string_out+1
stx string_out+2
lda #0
sta string_out+3
jmp _done
; tens?
+ ldy P8ZP_SCRATCH_B1
cmp #'0'
+ cmp #'0'
beq +
sta string_out,y
iny
+ ; ones.
txa
sta string_out,y
iny
lda #0
sta string_out,y
rts
sta string_out
stx string_out+1
lda #0
sta string_out+2
jmp _done
+ ; ones.
stx string_out
lda #0
sta string_out+1
_done lda #<string_out
ldy #>string_out
rts
}}
}
asmsub str_b (byte value @ A) clobbers(A,X,Y) {
asmsub str_b (byte value @ A) clobbers(X) -> str @AY {
; ---- convert the byte in A in decimal string form, without left padding 0s
%asm {{
ldy #0
sty P8ZP_SCRATCH_B1
cmp #0
bpl +
pha
lda #'-'
sta string_out
inc P8ZP_SCRATCH_B1
pla
+ jsr conv.byte2decimal
bra str_ub._output_byte_digits
ldy #0
cmp #0
bpl +
ldy #'-'
sty string_out
ldy #1
+ sty P8ZP_SCRATCH_REG
jsr conv.byte2decimal ; result in Y/A/X (100s, 10s, 1s). and in uword2decimal.decHundreds, decTens, decOnes.
; hundreds?
cpy #'0'
bne _out_hundreds
ldy P8ZP_SCRATCH_REG
cmp #'0'
bne _out_tens
beq _out_ones
_out_hundreds
ldy P8ZP_SCRATCH_REG
lda uword2decimal.decHundreds
sta string_out,y
iny
_out_tens
lda uword2decimal.decTens
sta string_out,y
iny
_out_ones
lda uword2decimal.decOnes
sta string_out,y
iny
lda #0
sta string_out,y
lda #<string_out
ldy #>string_out
rts
}}
}
asmsub str_ubhex (ubyte value @ A) clobbers(A,X,Y) {
asmsub str_ubhex (ubyte value @ A) clobbers(X) -> str @AY {
; ---- convert the ubyte in A in hex string form
%asm {{
jsr conv.ubyte2hex
sta string_out
sty string_out+1
lda #0
sta string_out+2
rts
jsr conv.ubyte2hex
sta string_out
sty string_out+1
lda #0
sta string_out+2
lda #<string_out
ldy #>string_out
rts
}}
}
asmsub str_ubbin (ubyte value @ A) clobbers(A,X,Y) {
asmsub str_ubbin (ubyte value @ A) clobbers(X) -> str @AY {
; ---- convert the ubyte in A in binary string form
%asm {{
sta P8ZP_SCRATCH_B1
@@ -90,18 +117,20 @@ asmsub str_ubbin (ubyte value @ A) clobbers(A,X,Y) {
sty string_out+8
ldy #7
- lsr P8ZP_SCRATCH_B1
bcc +
lda #'1'
bne _digit
+ lda #'0'
_digit sta string_out,y
dey
bcc +
lda #'1'
bne _digit
+ lda #'0'
_digit sta string_out,y
dey
bpl -
lda #<string_out
ldy #>string_out
rts
}}
}
asmsub str_uwbin (uword value @ AY) clobbers(A,X,Y) {
asmsub str_uwbin (uword value @ AY) clobbers(X) -> str @AY {
; ---- convert the uword in A/Y in binary string form
%asm {{
sta P8ZP_SCRATCH_REG
@@ -111,79 +140,88 @@ asmsub str_uwbin (uword value @ AY) clobbers(A,X,Y) {
sty string_out+16
ldy #7
- lsr P8ZP_SCRATCH_REG
bcc +
lda #'1'
bne _digit
+ lda #'0'
_digit sta string_out+8,y
dey
bcc +
lda #'1'
bne _digit
+ lda #'0'
_digit sta string_out+8,y
dey
bpl -
lda #<string_out
ldy #>string_out
rts
}}
}
asmsub str_uwhex (uword value @ AY) clobbers(A,Y) {
asmsub str_uwhex (uword value @ AY) -> str @AY {
; ---- convert the uword in A/Y in hexadecimal string form (4 digits)
%asm {{
pha
tya
jsr conv.ubyte2hex
sta string_out
sty string_out+1
pla
jsr conv.ubyte2hex
sta string_out+2
sty string_out+3
lda #0
sta string_out+4
rts
pha
tya
jsr conv.ubyte2hex
sta string_out
sty string_out+1
pla
jsr conv.ubyte2hex
sta string_out+2
sty string_out+3
lda #0
sta string_out+4
lda #<string_out
ldy #>string_out
rts
}}
}
asmsub str_uw0 (uword value @ AY) clobbers(A,X,Y) {
asmsub str_uw0 (uword value @ AY) clobbers(X) -> str @AY {
; ---- convert the uword in A/Y in decimal string form, with left padding 0s (5 positions total)
%asm {{
jsr conv.uword2decimal
ldy #0
- lda conv.uword2decimal.decTenThousands,y
sta string_out,y
beq +
iny
bne -
+ rts
- lda conv.uword2decimal.decTenThousands,y
sta string_out,y
beq +
iny
bne -
+
lda #<string_out
ldy #>string_out
rts
}}
}
asmsub str_uw (uword value @ AY) clobbers(A,X,Y) {
asmsub str_uw (uword value @ AY) clobbers(X) -> str @AY {
; ---- convert the uword in A/Y in decimal string form, without left padding 0s
%asm {{
jsr conv.uword2decimal
ldx #0
_output_digits
ldy #0
- lda conv.uword2decimal.decTenThousands,y
beq _allzero
cmp #'0'
bne _gotdigit
iny
bne -
- lda conv.uword2decimal.decTenThousands,y
beq _allzero
cmp #'0'
bne _gotdigit
iny
bne -
_gotdigit sta string_out,x
inx
iny
lda conv.uword2decimal.decTenThousands,y
bne _gotdigit
_end lda #0
sta string_out,x
rts
inx
iny
lda conv.uword2decimal.decTenThousands,y
bne _gotdigit
_end lda #0
sta string_out,x
lda #<string_out
ldy #>string_out
rts
_allzero lda #'0'
sta string_out,x
inx
bne _end
sta string_out,x
inx
bne _end
}}
}
asmsub str_w (word value @ AY) clobbers(A,X,Y) {
asmsub str_w (word value @ AY) clobbers(X) -> str @AY {
; ---- convert the (signed) word in A/Y in decimal string form, without left padding 0's
%asm {{
cpy #0
@@ -191,18 +229,19 @@ asmsub str_w (word value @ AY) clobbers(A,X,Y) {
pha
lda #'-'
sta string_out
tya
eor #255
tay
pla
eor #255
clc
adc #1
bcc +
iny
tya
eor #255
tay
pla
eor #255
clc
adc #1
bcc +
iny
+ jsr conv.uword2decimal
ldx #1
bne str_uw._output_digits
rts
}}
}
+14 -14
View File
@@ -126,7 +126,7 @@ bmx {
; If you're saving the whole screen width, you can leave screenwidth at 0.
; Returns: success status. If false, error_message points to the error message string.
error_message = 0
if compression {
if compression!=0 {
error_message = "compression not supported"
return false
}
@@ -184,7 +184,7 @@ save_end:
for cx16.r0L in 0 to sizeof(header)-1 {
header[cx16.r0L] = cbm.CHRIN()
}
return not cbm.READST()
return cbm.READST()==0
}
sub read_palette() -> bool {
@@ -197,7 +197,7 @@ save_end:
do {
cx16.r4L = cbm.CHRIN()
cx16.r4H = cbm.CHRIN()
if cx16.r3 {
if cx16.r3!=0 {
pokew(cx16.r3, cx16.r4) ; into memory
cx16.r3+=2
} else {
@@ -206,7 +206,7 @@ save_end:
}
cx16.r2L--
} until cx16.r2L==0
return cbm.READST()==0 or cbm.READST()&$40 ; no error or eof?
return cbm.READST()==0 or cbm.READST()&$40!=0 ; no error or eof?
}
sub read_bitmap_padded(ubyte vbank, uword vaddr, uword screenwidth) -> bool {
@@ -220,7 +220,7 @@ save_end:
if_cs
vbank++
}
return cbm.READST()==0 or cbm.READST()&$40 ; no error or eof?
return cbm.READST()==0 or cbm.READST()&$40!=0 ; no error or eof?
}
sub read_bitmap(ubyte vbank, uword vaddr) -> bool {
@@ -229,11 +229,11 @@ save_end:
cx16.vaddr(vbank, vaddr, 0, 1)
repeat height
read_scanline(cx16.r3)
return cbm.READST()==0 or cbm.READST()&$40 ; no error or eof?
return cbm.READST()==0 or cbm.READST()&$40!=0 ; no error or eof?
}
sub read_scanline(uword size) {
while size {
while size!=0 {
cx16.r0 = cx16.MACPTR(min(255, size) as ubyte, &cx16.VERA_DATA0, true)
if_cs {
; no MACPTR support
@@ -251,7 +251,7 @@ save_end:
for cx16.r0L in 0 to sizeof(header)-1 {
cbm.CHROUT(header[cx16.r0L])
}
return not cbm.READST()
return cbm.READST()==0
}
sub write_palette() -> bool {
@@ -262,7 +262,7 @@ save_end:
cx16.r2L = lsb(palette_entries)
cx16.vaddr(1, $fa00+palette_start*2, 0, 1)
do {
if cx16.r3 {
if cx16.r3!=0 {
cbm.CHROUT(@(cx16.r3)) ; from memory
cx16.r3++
cbm.CHROUT(@(cx16.r3))
@@ -273,7 +273,7 @@ save_end:
}
cx16.r2L--
} until cx16.r2L==0
return not cbm.READST()
return cbm.READST()==0
}
sub write_bitmap(ubyte vbank, uword vaddr, uword screenwidth) -> bool {
@@ -282,19 +282,19 @@ save_end:
cx16.vaddr(vbank, vaddr, 0, 1)
cx16.r3 = bytes_per_scanline(width) ; num bytes per image scanline
cx16.r2 = 0
if screenwidth
if screenwidth!=0
cx16.r2 = bytes_per_scanline(screenwidth-width) ; num bytes padding per screen scanline
repeat height {
write_scanline(cx16.r3)
repeat cx16.r2
cx16.r0L = cx16.VERA_DATA0
}
return not cbm.READST()
return cbm.READST()==0
sub write_scanline(uword size) {
while size {
while size!=0 {
cx16.r0L = lsb(size)
if msb(size)
if msb(size)!=0
cx16.r0L = 0 ; 256 bytes
cx16.r0 = cx16.MCIOUT(cx16.r0L, &cx16.VERA_DATA0, true)
if_cs {
+25 -26
View File
@@ -79,7 +79,7 @@ io_error:
cbm.CLRCHN() ; restore default i/o devices
cbm.CLOSE(READ_IO_CHANNEL)
if status and status & $40 == 0 { ; bit 6=end of file
if status!=0 and status & $40 == 0 { ; bit 6=end of file
txt.print("\ni/o error, status: ")
txt.print_ub(status)
txt.nl()
@@ -121,7 +121,7 @@ io_error:
io_error:
cbm.CLRCHN()
cbm.CLOSE(READ_IO_CHANNEL)
if status and status & $40 == 0
if status!=0 and status & $40 == 0
return 0
return list_filename
}
@@ -210,14 +210,14 @@ io_error:
ubyte blocks_lsb = cbm.CHRIN()
ubyte blocks_msb = cbm.CHRIN()
if cbm.READST()
if cbm.READST()!=0
goto close_end
list_blocks = mkword(blocks_msb, blocks_lsb)
; read until the filename starts after the first "
while cbm.CHRIN()!='\"' {
if cbm.READST()
if cbm.READST()!=0
goto close_end
}
@@ -240,7 +240,7 @@ io_error:
list_filetype[0] = cx16.r15L
list_filetype[1] = cbm.CHRIN()
list_filetype[2] = cbm.CHRIN()
while cbm.CHRIN() {
while cbm.CHRIN()!=0 {
; read the rest of the entry until the end
}
@@ -248,7 +248,7 @@ io_error:
void cbm.CHRIN()
if not list_skip_disk_name {
if not list_pattern
if list_pattern==0
return true
if string.pattern_match(list_filename, list_pattern)
return true
@@ -308,14 +308,14 @@ close_end:
; -- read from the currently open file, up to the given number of bytes.
; returns the actual number of bytes read. (checks for End-of-file and error conditions)
; NOTE: cannot be used to load into VRAM. Use vload() or call cx16.MACPTR() yourself with the vera data register as address.
if not iteration_in_progress or not num_bytes
if not iteration_in_progress or num_bytes==0
return 0
reset_read_channel()
list_blocks = 0 ; we reuse this variable for the total number of bytes read
uword readsize
while num_bytes {
while num_bytes!=0 {
readsize = 255
if num_bytes<readsize
readsize = num_bytes
@@ -327,7 +327,7 @@ close_end:
if msb(bufferpointer) == $c0
bufferpointer = mkword($a0, lsb(bufferpointer)) ; wrap over bank boundary
num_bytes -= readsize
if cbm.READST() & $40 {
if cbm.READST() & $40 !=0 {
f_close() ; end of file, close it
break
}
@@ -341,10 +341,10 @@ byte_read_loop: ; fallback if MACPTR isn't supported on the device
lda bufferpointer+1
sta m_in_buffer+2
}}
while num_bytes {
if cbm.READST() {
while num_bytes!=0 {
if cbm.READST()!=0 {
f_close()
if cbm.READST() & $40 ; eof?
if cbm.READST() & $40 !=0 ; eof?
return list_blocks ; number of bytes read
return 0 ; error.
}
@@ -371,7 +371,7 @@ m_in_buffer sta $ffff
reset_read_channel()
uword total_read = 0
while not cbm.READST() {
while cbm.READST()==0 {
cx16.r0 = f_read(bufferpointer, 256)
total_read += cx16.r0
bufferpointer += cx16.r0
@@ -444,7 +444,7 @@ _end rts
cbm.SETLFS(WRITE_IO_CHANNEL, drivenumber, WRITE_IO_CHANNEL)
void cbm.OPEN() ; open 13,8,13,"filename"
if_cc {
return not cbm.READST()
return cbm.READST()==0
}
cbm.CLOSE(WRITE_IO_CHANNEL)
f_close_w()
@@ -466,7 +466,7 @@ _end rts
if cbm.READST()!=0
return false
} until num_bytes==0
return not cbm.READST()
return cbm.READST()==0
no_mciout:
; the device doesn't support MCIOUT, use a normal per-byte write loop
@@ -474,7 +474,7 @@ no_mciout:
cbm.CHROUT(@(bufferpointer))
bufferpointer++
}
return not cbm.READST()
return cbm.READST()==0
}
return true
}
@@ -493,8 +493,7 @@ no_mciout:
str device_not_present_error = "device not present #xx"
if cbm.READST()==128 {
device_not_present_error[len(device_not_present_error)-2] = 0
conv.str_ub(drivenumber)
void string.copy(conv.string_out, &device_not_present_error+len(device_not_present_error)-2)
void string.copy(conv.str_ub(drivenumber), &device_not_present_error+len(device_not_present_error)-2)
return device_not_present_error
}
@@ -506,7 +505,7 @@ no_mciout:
goto io_error
void cbm.CHKIN(15) ; use #15 as input channel
while not cbm.READST() {
while cbm.READST()==0 {
cx16.r5L = cbm.CHRIN()
if cx16.r5L=='\r' or cx16.r5L=='\n'
break
@@ -562,9 +561,9 @@ io_error:
}}
if_cc
cx16.r0L = cbm.READST()==0
cx16.r0L = cbm.READST()==0 as ubyte
return cx16.r0L
return cx16.r0L as bool
}
; Use kernal LOAD routine to load the given program file in memory.
@@ -596,7 +595,7 @@ io_error:
cbm.SETNAM(string.length(filenameptr), filenameptr)
ubyte secondary = 1
cx16.r1 = 0
if address_override
if address_override!=0
secondary = 0
if headerless
secondary |= %00000010 ; activate cx16 kernal headerless load support
@@ -659,7 +658,7 @@ io_error:
return $2000 * (cx16.getrambank() - startbank) + endaddress - startaddress
}
asmsub vload(str name @R0, ubyte bank @A, uword startaddress @R1) clobbers(X, Y) -> ubyte @A {
asmsub vload(str name @R0, ubyte bank @A, uword startaddress @R1) clobbers(X, Y) -> bool @A {
; -- like the basic command VLOAD "filename",drivenumber,bank,address
; loads a file into Vera's video memory in the given bank:address, returns success in A
; the file has to have the usual 2 byte header (which will be skipped)
@@ -698,7 +697,7 @@ internal_vload:
}}
}
asmsub vload_raw(str name @R0, ubyte bank @A, uword startaddress @R1) clobbers(X, Y) -> ubyte @A {
asmsub vload_raw(str name @R0, ubyte bank @A, uword startaddress @R1) clobbers(X, Y) -> bool @A {
; -- like the basic command BVLOAD "filename",drivenumber,bank,address
; loads a file into Vera's video memory in the given bank:address, returns success in A
; the file is read fully including the first two bytes.
@@ -755,7 +754,7 @@ internal_vload:
repeat 6 {
void cbm.CHRIN()
}
while cbm.CHRIN() {
while cbm.CHRIN()!=0 {
; skip first line (drive label)
}
while cbm.CHRIN()!='"' {
@@ -786,7 +785,7 @@ internal_vload:
io_error:
cbm.CLRCHN()
cbm.CLOSE(READ_IO_CHANNEL)
if status and status & $40 == 0
if status!=0 and status & $40 == 0
return 0
if @(cx16.r12)==0 {
cx16.r12--
+1 -1
View File
@@ -1,7 +1,7 @@
; Prog8 definitions for floating point handling on the CommanderX16
%option enable_floats, no_symbol_prefixing, ignore_unused
%import floats_functions
%import shared_floats_functions
floats {
; ---- this block contains C-64 compatible floating point related functions ----
+17 -15
View File
@@ -55,14 +55,16 @@ gfx2 {
}
else -> {
; back to default text mode
cx16.r15L = cx16.VERA_DC_VIDEO & %00000111 ; retain chroma + output mode
cbm.CINT()
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11111000) | cx16.r15L
if active_mode!=0 {
cx16.r15L = cx16.VERA_DC_VIDEO & %00000111 ; retain chroma + output mode
cbm.CINT()
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11111000) | cx16.r15L
}
}
}
init_mode(mode)
if active_mode
if active_mode!=0
clear_screen(0)
}
@@ -305,17 +307,17 @@ gfx2 {
}
word @zp d = 0
cx16.r1L = true ; 'positive_ix'
cx16.r1L = 1 ;; true ; 'positive_ix'
if dx < 0 {
dx = -dx
cx16.r1L = false
cx16.r1L = 0 ;; false
}
word @zp dx2 = dx*2
word @zp dy2 = dy*2
cx16.r14 = x1 ; internal plot X
if dx >= dy {
if cx16.r1L {
if cx16.r1L!=0 {
repeat {
plot(cx16.r14, y1, color)
if cx16.r14==x2
@@ -342,7 +344,7 @@ gfx2 {
}
}
else {
if cx16.r1L {
if cx16.r1L!=0 {
repeat {
plot(cx16.r14, y1, color)
if y1 == y2
@@ -705,7 +707,7 @@ gfx2 {
push_stack(xx, xx, yy, 1)
push_stack(xx, xx, yy + 1, -1)
word left = 0
while cx16.r12L {
while cx16.r12L!=0 {
pop_stack()
xx = x1
; possible speed optimization: if mode==1 (256c) use vera autodecrement instead of pget(), but code bloat not worth it?
@@ -725,15 +727,15 @@ gfx2 {
xx = x1 + 1
do {
cx16.r9 = xx
cx16.r9s = xx
; possible speed optimization: if mode==1 (256c) use vera autoincrement instead of pget(), but code bloat not worth it?
while xx <= width-1 {
if pget(xx as uword, yy as uword) != cx16.r11L
break
xx++
}
if cx16.r9!=xx
horizontal_line(cx16.r9, yy as uword, (xx as uword)-cx16.r9, cx16.r10L)
if cx16.r9s!=xx
horizontal_line(cx16.r9, yy as uword, xx-cx16.r9s as uword, cx16.r10L)
push_stack(left, xx - 1, yy, dy)
if xx > x2 + 1
@@ -845,7 +847,7 @@ skip:
when active_mode {
1 -> {
; lores 256c
while @(sctextptr) {
while @(sctextptr)!=0 {
chardataptr = charset_addr + (@(sctextptr) as uword)*8
cx16.vaddr(charset_bank, chardataptr, 1, 1)
repeat 8 {
@@ -874,9 +876,9 @@ skip:
; hires 4c
; we're going to use a few cx16 registers to make sure every variable is in zeropage in the inner loop.
cx16.r11L = color
while @(sctextptr) {
while @(sctextptr)!=0 {
chardataptr = charset_addr + (@(sctextptr) as uword)*8
cx16.vaddr(charset_bank, chardataptr, 1, true) ; for reading the chardata from Vera data channel 1
cx16.vaddr(charset_bank, chardataptr, 1, 1) ; for reading the chardata from Vera data channel 1
position(xx, yy) ; only calculated once, we update vera address in the loop instead
cx16.VERA_ADDR_H &= $0f ; no auto increment
repeat 8 {
+4 -4
View File
@@ -57,20 +57,20 @@ graphics {
}
sub fillrect(uword xx, uword yy, uword width, uword height) {
cx16.GRAPH_draw_rect(xx, yy, width, height, 0, 1)
cx16.GRAPH_draw_rect(xx, yy, width, height, 0, true)
}
sub rect(uword xx, uword yy, uword width, uword height) {
cx16.GRAPH_draw_rect(xx, yy, width, height, 0, 0)
cx16.GRAPH_draw_rect(xx, yy, width, height, 0, false)
}
sub horizontal_line(uword xx, uword yy, uword length) {
if length
if length!=0
cx16.GRAPH_draw_line(xx, yy, xx+length-1, yy)
}
sub vertical_line(uword xx, uword yy, uword height) {
if height
if height!=0
cx16.GRAPH_draw_line(xx, yy, xx, yy+height-1)
}
+136 -55
View File
@@ -7,7 +7,6 @@
; NOTE: a lot of the code here is similar or the same to that in gfx2
; NOTE: For sake of speed, NO BOUNDS CHECKING is performed in most routines!
; You'll have to make sure yourself that you're not writing outside of bitmap boundaries!
;
monogfx {
@@ -16,7 +15,10 @@ monogfx {
; read-only control variables:
uword width = 0
uword height = 0
bool dont_stipple_flag = true ; set to false to enable stippling mode
ubyte mode
const ubyte MODE_NORMAL = %00000000
const ubyte MODE_STIPPLE = %00000001
const ubyte MODE_INVERT = %00000010
sub lores() {
; enable 320*240 bitmap mode
@@ -29,6 +31,7 @@ monogfx {
cx16.VERA_L1_TILEBASE = 0
width = 320
height = 240
mode = MODE_NORMAL
clear_screen(0)
}
@@ -43,6 +46,7 @@ monogfx {
cx16.VERA_L1_TILEBASE = %00000001
width = 640
height = 480
mode = MODE_NORMAL
clear_screen(0)
}
@@ -53,8 +57,11 @@ monogfx {
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11111000) | cx16.r15L
}
sub drawmode(ubyte dm) {
mode = dm
}
sub clear_screen(ubyte color) {
stipple(false)
position(0, 0)
when width {
320 -> {
@@ -69,10 +76,6 @@ monogfx {
position(0, 0)
}
sub stipple(bool enable) {
dont_stipple_flag = not enable
}
sub rect(uword xx, uword yy, uword rwidth, uword rheight, bool draw) {
if rwidth==0 or rheight==0
return
@@ -122,8 +125,9 @@ monogfx {
bne -
sta cx16.r0L ; new left byte
+
lda p8v_dont_stipple_flag
bne _dontstipple
lda p8v_mode
lsr a
bcc _dontstipple
; determine stipple pattern
lda p8v_yy
and #1
@@ -162,24 +166,36 @@ _clear
}
ubyte separate_pixels = (8-lsb(xx)) & 7
if separate_pixels {
if dont_stipple_flag {
position(xx,yy)
cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off
if draw
cx16.VERA_DATA0 |= masked_starts[separate_pixels]
else
cx16.VERA_DATA0 &= ~masked_starts[separate_pixels]
xx += separate_pixels
} else {
repeat separate_pixels {
plot(xx, yy, draw)
xx++
if separate_pixels!=0 {
when mode {
MODE_NORMAL -> {
position(xx,yy)
cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off
if draw
cx16.VERA_DATA0 |= masked_starts[separate_pixels]
else
cx16.VERA_DATA0 &= ~masked_starts[separate_pixels]
xx += separate_pixels
}
MODE_STIPPLE -> {
repeat separate_pixels {
plot(xx, yy, draw)
xx++
}
}
MODE_INVERT -> {
position(xx,yy)
cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off
if draw
cx16.VERA_DATA0 ^= masked_starts[separate_pixels]
else
cx16.VERA_DATA0 &= masked_starts[separate_pixels]
xx += separate_pixels
}
}
length -= separate_pixels
}
if length {
if length!=0 {
position(xx, yy)
separate_pixels = lsb(length) & 7
xx += length & $fff8
@@ -194,10 +210,28 @@ _clear
bne +
ldy #0 ; black
bra _loop
+ lda p8v_dont_stipple_flag
beq _stipple
ldy #255 ; don't stipple
+ lda p8v_mode
lsr a
bcs _stipple
lsr a
bcs _inverted
ldy #255 ; normal drawing mode
bra _loop
_inverted lda #0
jsr cx16.vaddr_clone
_invertedloop lda p8v_length
ora p8v_length+1
beq _done
lda cx16.VERA_DATA1
eor #255
sta cx16.VERA_DATA0
lda p8v_length
bne +
dec p8v_length+1
+ dec p8v_length
bra _invertedloop
_stipple lda p8v_yy
and #1 ; determine stipple pattern to use
bne +
@@ -216,16 +250,26 @@ _loop lda p8v_length
_done
}}
if dont_stipple_flag {
when mode {
MODE_NORMAL -> {
cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off
if draw
cx16.VERA_DATA0 |= masked_ends[separate_pixels]
else
cx16.VERA_DATA0 &= ~masked_ends[separate_pixels]
}
MODE_STIPPLE -> {
repeat separate_pixels {
plot(xx, yy, draw)
xx++
}
}
MODE_INVERT -> {
cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off
if draw
cx16.VERA_DATA0 |= masked_ends[separate_pixels]
cx16.VERA_DATA0 ^= masked_ends[separate_pixels]
else
cx16.VERA_DATA0 &= ~masked_ends[separate_pixels]
} else {
repeat separate_pixels {
plot(xx, yy, draw)
xx++
cx16.VERA_DATA0 &= masked_ends[separate_pixels]
}
}
}
@@ -253,7 +297,16 @@ _done
sub vertical_line(uword xx, uword yy, uword lheight, bool draw) {
cx16.r15L = monogfx.plot.maskbits[xx as ubyte & 7] ; bitmask
if draw {
if dont_stipple_flag {
%asm {{
lda p8v_mode
and #p8c_MODE_INVERT
beq +
lda #$45 ; eor ZP modifying code
bne ++
+ lda #$05 ; ora ZP modifying code
+ sta drawmode
}}
if mode!=MODE_STIPPLE {
; draw continuous line.
position2(xx,yy,true)
if width==320
@@ -263,7 +316,7 @@ _done
repeat lheight {
%asm {{
lda cx16.VERA_DATA0
ora cx16.r15L
drawmode: ora cx16.r15L
sta cx16.VERA_DATA1
}}
}
@@ -340,17 +393,17 @@ _done
}
word @zp d = 0
cx16.r1L = true ; 'positive_ix'
cx16.r1L = 1 ;; true ; 'positive_ix'
if dx < 0 {
dx = -dx
cx16.r1L = false
cx16.r1L = 0 ;; false
}
word @zp dx2 = dx*2
word @zp dy2 = dy*2
cx16.r14 = x1 ; internal plot X
if dx >= dy {
if cx16.r1L {
if cx16.r1L!=0 {
repeat {
plot(cx16.r14, y1, draw)
if cx16.r14==x2
@@ -377,7 +430,7 @@ _done
}
}
else {
if cx16.r1L {
if cx16.r1L!=0 {
repeat {
plot(cx16.r14, y1, draw)
if y1 == y2
@@ -554,12 +607,19 @@ _done
if draw {
; solid color or perhaps stipple
%asm {{
lda p8v_mode
lsr a
bcs +
lsr a
bcs p8l_invert
bra p8l_nostipple
+ ; stipple mode
lda p8v_xx
eor p8v_yy
ora p8v_dont_stipple_flag
and #1
}}
if_nz {
nostipple:
prepare()
%asm {{
tsb cx16.VERA_DATA0
@@ -572,6 +632,16 @@ _done
trb cx16.VERA_DATA0
}}
}
return
invert:
prepare()
%asm {{
lda cx16.VERA_DATA0
eor p8v_maskbits,y
sta cx16.VERA_DATA0
}}
return
sub prepare() {
%asm {{
@@ -606,7 +676,7 @@ _done
plot(xx, yy, draw)
}
sub pget(uword @zp xx, uword yy) -> ubyte {
sub pget(uword @zp xx, uword yy) -> bool {
%asm {{
lda p8v_xx
and #7
@@ -708,7 +778,7 @@ _done
}}
yy+=dy
}
cx16.r11L = pget(xx as uword, yy as uword) ; old_color
cx16.r11L = pget(xx as uword, yy as uword) as ubyte ; old_color
if cx16.r11L == cx16.r10L
return
if xx<0 or xx>width-1 or yy<0 or yy>height-1
@@ -716,16 +786,16 @@ _done
push_stack(xx, xx, yy, 1)
push_stack(xx, xx, yy + 1, -1)
word left = 0
while cx16.r12L {
while cx16.r12L!=0 {
pop_stack()
xx = x1
while xx >= 0 {
if pget(xx as uword, yy as uword) != cx16.r11L
if pget(xx as uword, yy as uword) as ubyte != cx16.r11L
break
xx--
}
if x1!=xx
horizontal_line(xx as uword+1, yy as uword, x1-xx as uword, cx16.r10L)
horizontal_line(xx as uword+1, yy as uword, x1-xx as uword, cx16.r10L as bool)
else
goto skip
@@ -735,14 +805,14 @@ _done
xx = x1 + 1
do {
cx16.r9 = xx
cx16.r9s = xx
while xx <= width-1 {
if pget(xx as uword, yy as uword) != cx16.r11L
if pget(xx as uword, yy as uword) as ubyte != cx16.r11L
break
xx++
}
if cx16.r9!=xx
horizontal_line(cx16.r9, yy as uword, (xx as uword)-cx16.r9, cx16.r10L)
if cx16.r9s!=xx
horizontal_line(cx16.r9, yy as uword, xx-cx16.r9s as uword, cx16.r10L as bool)
push_stack(left, xx - 1, yy, dy)
if xx > x2 + 1
@@ -750,7 +820,7 @@ _done
skip:
xx++
while xx <= x2 {
if pget(xx as uword, yy as uword) == cx16.r11L
if pget(xx as uword, yy as uword) as ubyte == cx16.r11L
break
xx++
}
@@ -783,7 +853,7 @@ skip:
cx16.screen_set_charset(charset, 0)
}
sub text(uword @zp xx, uword yy, bool draw, uword sctextptr) {
sub text(uword @zp xx, uword yy, bool draw, str sctextptr) {
; -- Write some text at the given pixel position. The text string must be in screencode encoding (not petscii!).
; You must also have called text_charset() first to select and prepare the character set to use.
uword chardataptr
@@ -791,7 +861,18 @@ skip:
ubyte[8] @shared char_bitmap_bytes_right
cx16.r3 = sctextptr
while @(cx16.r3) {
%asm {{
lda p8v_mode
cmp #p8c_MODE_INVERT
beq +
lda #$0d ; ORA abs modifying code
bne ++
+ lda #$4d ; EOR abs modifying code
+ sta cdraw_mod1
sta cdraw_mod2
}}
while @(cx16.r3)!=0 {
chardataptr = charset_addr + @(cx16.r3) * $0008
; copy the character bitmap into RAM
cx16.vaddr_autoincr(charset_bank, chardataptr, 0, 1)
@@ -824,7 +905,7 @@ skip:
%asm {{
ldy #0
- lda p8v_char_bitmap_bytes_left,y
ora cx16.VERA_DATA1
cdraw_mod1 ora cx16.VERA_DATA1
sta cx16.VERA_DATA0
iny
cpy #8
@@ -843,14 +924,14 @@ skip:
}}
}
; right part of shifted char
if lsb(xx) & 7 {
if lsb(xx) & 7 !=0 {
position2(xx+8, yy, true)
set_autoincrs()
if draw {
%asm {{
ldy #0
- lda p8v_char_bitmap_bytes_right,y
ora cx16.VERA_DATA1
cdraw_mod2 ora cx16.VERA_DATA1
sta cx16.VERA_DATA0
iny
cpy #8
+2 -2
View File
@@ -134,7 +134,7 @@ psg {
}
1 -> {
; sustain
if envelope_sustains[cx16.r1L] {
if envelope_sustains[cx16.r1L]!=0 {
envelope_sustains[cx16.r1L]--
} else {
envelope_states[cx16.r1L] = 2 ; start release
@@ -143,7 +143,7 @@ psg {
2 -> {
; release
cx16.r0 = envelope_volumes[cx16.r1L] - envelope_releases[cx16.r1L] * $0040
if msb(cx16.r0) & %11000000 {
if msb(cx16.r0) & %11000000 !=0 {
cx16.r0 = 0
envelope_releases[cx16.r1L] = 0
}
+7 -7
View File
@@ -47,7 +47,7 @@ sprites {
sprite_reg = VERA_SPRITEREGS + spritenum*$0008
cx16.r0L = cx16.vpeek(1, sprite_reg)
cx16.r0H = cx16.vpeek(1, sprite_reg+1)
cx16.r1L = cx16.r0H & %00001000 !=0 ; bank
cx16.r1L = cx16.r0H & %00001000 !=0 as ubyte ; bank
cx16.r0 <<= 5 ; address
}
@@ -96,8 +96,8 @@ sprites {
sub move(ubyte spritenum, word dx, word dy) {
; move a sprite based on its current position
sprite_reg = VERA_SPRITEREGS + 2 + spritenum*$0008
cx16.r1 = mkword(cx16.vpeek(1, sprite_reg+1), cx16.vpeek(1, sprite_reg)) as word + dx
cx16.r2 = mkword(cx16.vpeek(1, sprite_reg+3), cx16.vpeek(1, sprite_reg+2)) as word + dy
cx16.r1s = mkword(cx16.vpeek(1, sprite_reg+1), cx16.vpeek(1, sprite_reg)) as word + dx
cx16.r2s = mkword(cx16.vpeek(1, sprite_reg+3), cx16.vpeek(1, sprite_reg+2)) as word + dy
cx16.vpoke(1, sprite_reg, cx16.r1L)
cx16.vpoke(1, sprite_reg+1, cx16.r1H)
cx16.vpoke(1, sprite_reg+2, cx16.r2L)
@@ -107,7 +107,7 @@ sprites {
sub movex(ubyte spritenum, word dx) {
; move a sprite horizontally based on its current position
sprite_reg = VERA_SPRITEREGS + 2 + spritenum*$0008
cx16.r1 = mkword(cx16.vpeek(1, sprite_reg+1), cx16.vpeek(1, sprite_reg)) as word + dx
cx16.r1s = mkword(cx16.vpeek(1, sprite_reg+1), cx16.vpeek(1, sprite_reg)) as word + dx
cx16.vpoke(1, sprite_reg, cx16.r1L)
cx16.vpoke(1, sprite_reg+1, cx16.r1H)
}
@@ -115,7 +115,7 @@ sprites {
sub movey(ubyte spritenum, word dy) {
; move a sprite vertically based on its current position
sprite_reg = VERA_SPRITEREGS + 4 + spritenum*$0008
cx16.r1 = mkword(cx16.vpeek(1, sprite_reg+1), cx16.vpeek(1, sprite_reg)) as word + dy
cx16.r1s = mkword(cx16.vpeek(1, sprite_reg+1), cx16.vpeek(1, sprite_reg)) as word + dy
cx16.vpoke(1, sprite_reg, cx16.r1L)
cx16.vpoke(1, sprite_reg+1, cx16.r1H)
}
@@ -135,11 +135,11 @@ sprites {
}
sub flipx(ubyte spritenum, bool flipped) {
cx16.vpoke_mask(1, VERA_SPRITEREGS + 6 + spritenum*$0008, %11111110, flipped)
cx16.vpoke_mask(1, VERA_SPRITEREGS + 6 + spritenum*$0008, %11111110, flipped as ubyte)
}
sub flipy(ubyte spritenum, bool flipped) {
cx16.vpoke_mask(1, VERA_SPRITEREGS + 6 + spritenum*$0008, %11111101, flipped<<1)
cx16.vpoke_mask(1, VERA_SPRITEREGS + 6 + spritenum*$0008, %11111101, (flipped as ubyte)<<1)
}
sub set_palette_offset(ubyte spritenum, ubyte offset) {
+69 -26
View File
@@ -48,13 +48,13 @@ romsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; (via 810 ($32A)
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, bool dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, bool dir @ Pc) clobbers(A) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
; ---- utility
asmsub STOP2() clobbers(X) -> ubyte @A {
; -- check if STOP key was pressed, returns true if so. More convenient to use than STOP() because that only sets the carry status flag.
asmsub STOP2() clobbers(X) -> bool @A {
; -- check if STOP key was pressed, returns true if so. More convenient to use than STOP() because that only sets the zero status flag.
%asm {{
jsr cbm.STOP
beq +
@@ -90,6 +90,16 @@ sub CLEARST() {
CLOSE(15)
}
asmsub kbdbuf_clear() {
; -- convenience helper routine to clear the keyboard buffer
%asm {{
- jsr GETIN
cmp #0
bne -
rts
}}
}
}
cx16 {
@@ -436,7 +446,7 @@ romsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uwor
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, uword @R3 ; result registers see clock_set_date_time()
; keyboard, mouse, joystick
; note: also see the kbdbuf_clear() helper routine below!
; note: also see the cbm.kbdbuf_clear() helper routine
romsub $febd = kbdbuf_peek() -> ubyte @A, ubyte @X ; key in A, queue length in X
romsub $febd = kbdbuf_peek2() -> uword @AX ; alternative to above to not have the hassle to deal with multiple return values
romsub $fec0 = kbdbuf_get_modifiers() -> ubyte @A
@@ -534,15 +544,6 @@ asmsub get_screen_mode() -> byte @A, byte @X, byte @Y {
}}
}
asmsub kbdbuf_clear() {
; -- convenience helper routine to clear the keyboard buffer
%asm {{
- jsr cbm.GETIN
bne -
rts
}}
}
asmsub mouse_config2(byte shape @A) clobbers (A, X, Y) {
; -- convenience wrapper function that handles the screen resolution for mouse_config() for you
%asm {{
@@ -556,7 +557,7 @@ asmsub mouse_config2(byte shape @A) clobbers (A, X, Y) {
asmsub mouse_pos() clobbers(X) -> ubyte @A {
; -- short wrapper around mouse_get() kernal routine:
; -- gets the position of the mouse cursor in cx16.r0 and cx16.r1 (x/y coordinate), returns mouse button status.
; -- gets the position of the mouse cursor in cx16.r0 and cx16.r1 (x/y coordinate), returns mouse button status in A.
%asm {{
ldx #cx16.r0
jmp cx16.mouse_get
@@ -1213,6 +1214,20 @@ _continue iny
}}
}
sub reset_system() {
; Soft-reset the system back to initial power-on Basic prompt.
sys.reset_system()
}
sub poweroff_system() {
; use the SMC to shutdown the computer
void cx16.i2c_write_byte($42, $01, $00)
}
sub set_led_brightness(ubyte brightness) {
void cx16.i2c_write_byte($42, $05, brightness)
}
}
sys {
@@ -1289,8 +1304,15 @@ asmsub cleanup_at_exit() {
sta $01 ; rom bank 4 (basic)
stz $2d ; hack to reset machine code monitor bank to 0
jsr cbm.CLRCHN ; reset i/o channels
_exitcodeCarry = *+1
lda #0
lsr a
_exitcode = *+1
lda #0 ; exit code possibly modified in exit()
_exitcodeX = *+1
ldx #0
_exitcodeY = *+1
ldy #0
rts
}}
}
@@ -1405,13 +1427,13 @@ asmsub set_rasterline(uword line @AY) {
asmsub reset_system() {
; Soft-reset the system back to initial power-on Basic prompt.
; We do this via the SMC so that a true reset is performed that also resets the Vera fully.
; (note: this is an asmsub on purpose! don't change into a normal sub)
%asm {{
sei
ldx #$42
ldy #2
lda #0
jsr cx16.i2c_write_byte
bra *
jmp cx16.i2c_write_byte
}}
}
@@ -1492,20 +1514,14 @@ _loop lda P8ZP_SCRATCH_W1
rts ; nothing to copy
_copyshort
; decrease source and target pointers so we can simply index by Y
lda cx16.r0
bne +
dec cx16.r0+1
+ dec cx16.r0
lda cx16.r1
bne +
dec cx16.r1+1
+ dec cx16.r1
dey
beq +
- lda (cx16.r0),y
sta (cx16.r1),y
dey
bne -
+ lda (cx16.r0),y
sta (cx16.r1),y
rts
_longcopy
@@ -1661,6 +1677,33 @@ save_SCRATCH_ZPWORD2 .word 0
}}
}
asmsub exit2(ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y) {
; -- immediately exit the program with result values in the A, X and Y registers.
%asm {{
sta cleanup_at_exit._exitcode
stx cleanup_at_exit._exitcodeX
sty cleanup_at_exit._exitcodeY
ldx prog8_lib.orig_stackpointer
txs
jmp cleanup_at_exit
}}
}
asmsub exit3(ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y, bool carry @Pc) {
; -- immediately exit the program with result values in the A, X and Y registers, and the Carry flag in the status register.
%asm {{
sta cleanup_at_exit._exitcode
lda #0
rol a
sta cleanup_at_exit._exitcodeCarry
stx cleanup_at_exit._exitcodeX
sty cleanup_at_exit._exitcodeY
ldx prog8_lib.orig_stackpointer
txs
jmp cleanup_at_exit
}}
}
inline asmsub progend() -> uword @AY {
%asm {{
lda #<prog8_program_end
+13 -5
View File
@@ -4,7 +4,7 @@
%import syslib
%import conv
%import shared_textio_functions
txt {
@@ -18,26 +18,32 @@ const uword VERA_TEXTMATRIX_ADDR = $b000
sub clear_screen() {
txt.chrout(147)
chrout(147)
}
sub home() {
txt.chrout(19)
chrout(19)
}
sub nl() {
txt.chrout('\n')
chrout('\n')
}
sub spc() {
txt.chrout(' ')
chrout(' ')
}
sub bell() {
chrout(7)
}
asmsub column(ubyte col @A) clobbers(A, X, Y) {
; ---- set the cursor on the given column (starting with 0) on the current line
%asm {{
pha
sec
jsr cbm.PLOT
pla
tay
clc
jmp cbm.PLOT
@@ -54,8 +60,10 @@ asmsub get_column() -> ubyte @Y {
asmsub row(ubyte rownum @A) clobbers(A, X, Y) {
; ---- set the cursor on the given row (starting with 0) on the current line
%asm {{
pha
sec
jsr cbm.PLOT
pla
tax
clc
jmp cbm.PLOT
+4 -4
View File
@@ -1,7 +1,7 @@
; Somewhat experimental Vera FX support.
; Docs:
; https://github.com/X16Community/x16-docs/blob/master/VERA%20FX%20Reference.md
; https://docs.google.com/document/d/1q34uWOiM3Be2pnaHRVgSdHySI-qsiQWPTo_gfE54PTg/edit
; https://github.com/X16Community/x16-docs/blob/master/X16%20Reference%20-%2010%20-%20VERA%20FX%20Reference.md
; https://docs.google.com/document/d/1q34uWOiM3Be2pnaHRVgSdHySI-qsiQWPTo_gfE54PTg
verafx {
%option no_symbol_prefixing, ignore_unused
@@ -15,10 +15,10 @@ verafx {
; Vera version number is valid.
; Vera fx is available on Vera version 0.3.1 and later,
; so no need to even check VERA_DC_VER1, which contains 0 (or higher)
cx16.r1L = mkword(cx16.VERA_DC_VER2, cx16.VERA_DC_VER3) >= $0301
cx16.r1L = mkword(cx16.VERA_DC_VER2, cx16.VERA_DC_VER3) >= $0301 as ubyte
}
cx16.VERA_CTRL = cx16.r0L
return cx16.r1L
return cx16.r1L as bool
}
sub clear(ubyte vbank, uword vaddr, ubyte data, uword num_longwords) {
+18 -19
View File
@@ -68,7 +68,7 @@ io_error:
cbm.CLRCHN() ; restore default i/o devices
cbm.CLOSE(READ_IO_CHANNEL)
if status and status & $40 == 0 { ; bit 6=end of file
if status!=0 and status & $40 == 0 { ; bit 6=end of file
txt.print("\ni/o error, status: ")
txt.print_ub(status)
txt.nl()
@@ -83,7 +83,7 @@ io_error:
cbm.SETNAM(1, "$")
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
ubyte okay = false
bool okay = false
void cbm.OPEN() ; open 12,8,0,"$"
if_cs
goto io_error
@@ -201,14 +201,14 @@ io_error:
ubyte blocks_lsb = cbm.CHRIN()
ubyte blocks_msb = cbm.CHRIN()
if cbm.READST()
if cbm.READST()!=0
goto close_end
list_blocks = mkword(blocks_msb, blocks_lsb)
; read until the filename starts after the first "
while cbm.CHRIN()!='\"' {
if cbm.READST()
if cbm.READST()!=0
goto close_end
}
@@ -231,7 +231,7 @@ io_error:
list_filetype[0] = cx16.r15L
list_filetype[1] = cbm.CHRIN()
list_filetype[2] = cbm.CHRIN()
while cbm.CHRIN() {
while cbm.CHRIN()!=0 {
; read the rest of the entry until the end
}
@@ -239,7 +239,7 @@ io_error:
void cbm.CHRIN()
if not list_skip_disk_name {
if not list_pattern
if list_pattern==0
return true
if string.pattern_match(list_filename, list_pattern)
return true
@@ -297,7 +297,7 @@ close_end:
sub f_read(uword bufferpointer, uword num_bytes) -> uword {
; -- read from the currently open file, up to the given number of bytes.
; returns the actual number of bytes read. (checks for End-of-file and error conditions)
if not iteration_in_progress or not num_bytes
if not iteration_in_progress or num_bytes==0
return 0
reset_read_channel()
@@ -309,10 +309,10 @@ close_end:
lda bufferpointer+1
sta m_in_buffer+2
}}
while num_bytes {
if cbm.READST() {
while num_bytes!=0 {
if cbm.READST()!=0 {
f_close()
if cbm.READST() & $40 ; eof?
if cbm.READST() & $40 !=0 ; eof?
return list_blocks ; number of bytes read
return 0 ; error.
}
@@ -337,7 +337,7 @@ m_in_buffer sta $ffff
reset_read_channel()
uword total_read = 0
while not cbm.READST() {
while cbm.READST()==0 {
cx16.r0 = f_read(bufferpointer, 256)
total_read += cx16.r0
bufferpointer += cx16.r0
@@ -396,7 +396,7 @@ _end rts
cbm.SETLFS(WRITE_IO_CHANNEL, drivenumber, 1)
void cbm.OPEN() ; open 13,8,1,"filename"
if_cc
return not cbm.READST()
return cbm.READST()==0
cbm.CLOSE(WRITE_IO_CHANNEL)
f_close_w()
return false
@@ -410,7 +410,7 @@ _end rts
cbm.CHROUT(@(bufferpointer))
bufferpointer++
}
return not cbm.READST()
return cbm.READST()==0
}
return true
}
@@ -429,8 +429,7 @@ _end rts
str device_not_present_error = "device not present #xx"
if cbm.READST()==128 {
device_not_present_error[len(device_not_present_error)-2] = 0
conv.str_ub(drivenumber)
void string.copy(conv.string_out, &device_not_present_error+len(device_not_present_error)-2)
void string.copy(conv.str_ub(drivenumber), &device_not_present_error+len(device_not_present_error)-2)
return device_not_present_error
}
uword messageptr = &list_filename
@@ -441,7 +440,7 @@ _end rts
goto io_error
void cbm.CHKIN(15) ; use #15 as input channel
while not cbm.READST() {
while cbm.READST()==0 {
cx16.r5L = cbm.CHRIN()
if cx16.r5L=='\r' or cx16.r5L=='\n'
break
@@ -481,9 +480,9 @@ io_error:
}}
if_cc
cx16.r0L = cbm.READST()==0
cx16.r0L = cbm.READST()==0 as ubyte
return cx16.r0L
return cx16.r0L as bool
}
; Use kernal LOAD routine to load the given program file in memory.
@@ -496,7 +495,7 @@ io_error:
cbm.SETNAM(string.length(filenameptr), filenameptr)
ubyte secondary = 1
cx16.r1 = 0
if address_override
if address_override!=0
secondary = 0
cbm.SETLFS(1, drivenumber, secondary)
%asm {{
+144 -36
View File
@@ -75,7 +75,7 @@ _sinecosR8 .char trunc(127.0 * sin(range(180+45) * rad(360.0/180.0)))
}}
}
asmsub rnd() -> ubyte @A {
asmsub rnd() clobbers(Y) -> ubyte @A {
%asm {{
jmp math.randbyte
}}
@@ -87,6 +87,24 @@ _sinecosR8 .char trunc(127.0 * sin(range(180+45) * rad(360.0/180.0)))
}}
}
sub randrange(ubyte n) -> ubyte {
; -- return random number uniformly distributed from 0 to n-1 (compensates for divisibility bias)
cx16.r0H = 255 / n * n
do {
cx16.r0L = math.rnd()
} until cx16.r0L < cx16.r0H
return cx16.r0L % n
}
sub randrangew(uword n) -> uword {
; -- return random number uniformly distributed from 0 to n-1 (compensates for divisibility bias)
cx16.r1 = 65535 / n * n
do {
cx16.r0 = math.rndw()
} until cx16.r0 < cx16.r1
return cx16.r0 % n
}
asmsub rndseed(uword seed1 @AY, uword seed2 @R0) clobbers(A,Y) {
; -- set new pseudo RNG's seed values. Defaults are: $00c2, $1137
%asm {{
@@ -486,47 +504,137 @@ log2_tab
sub crc16(uword data, uword length) -> uword {
; calculates the CRC16 (XMODEM) checksum of the buffer.
cx16.r1 = data ; make sure pointer is in zp (on cx16)
cx16.r0 = 0 ; the crc value
repeat length {
cx16.r0H ^= @(cx16.r1)
repeat 8 {
if cx16.r0H & $80 {
cx16.r0 <<= 1
cx16.r0 ^= $1021
}
else
cx16.r0<<=1
}
cx16.r1++
; There are also "streaming" crc16_start/update/end routines below, that allow you to calculate crc16 for data that doesn't fit in a single memory block.
crc16_start()
cx16.r13 = data
cx16.r14 = data+length
while cx16.r13!=cx16.r14 {
crc16_update(@(cx16.r13))
cx16.r13++
}
return cx16.r0
return crc16_end()
}
sub crc16_start() {
; start the "streaming" crc16
; note: tracks the crc16 checksum in cx16.r15!
; if your code uses that, it must save/restore it before calling this routine
cx16.r15 = 0
}
asmsub crc16_update(ubyte value @A) {
; update the "streaming" crc16 with next byte value
; note: tracks the crc16 checksum in cx16.r15!
; if your code uses that, it must save/restore it before calling this routine
%asm {{
eor cx16.r15H
sta cx16.r15H
ldy #8
- lda cx16.r15H
asl cx16.r15L
rol cx16.r15H
and #$80
beq +
lda cx16.r15H
eor #$10
sta cx16.r15H
lda cx16.r15L
eor #$21
sta cx16.r15L
+ dey
bne -
rts
}}
; orignal prog8 code was:
; cx16.r15H ^= value
; repeat 8 {
; if cx16.r15H & $80 !=0 {
; cx16.r15 <<=1
; cx16.r15 ^= $1021
; } else
; cx16.r15<<=1
; }
}
sub crc16_end() -> uword {
; finalize the "streaming" crc16, returns resulting crc16 value
return cx16.r15
}
sub crc32(uword data, uword length) {
; Calculates the CRC-32 (POSIX) checksum of the buffer.
; because prog8 doesn't have 32 bits integers, we have to split up the calculation over 2 words.
; result stored in cx16.r0 (low word) and cx16.r1 (high word)
cx16.r2 = data ; make sure pointer is in zp (on cx16)
cx16.r1 = 0
cx16.r0 = 0
repeat length {
cx16.r1H ^= @(cx16.r2)
repeat 8 {
if cx16.r1H & $80 {
cx16.r0 <<= 1
rol(cx16.r1)
cx16.r1 ^= $04c1
cx16.r0 ^= $1db7
}
else {
cx16.r0 <<= 1
rol(cx16.r1)
}
}
cx16.r2++
; result stored in cx16.r14 (low word) and cx16.r15 (high word)
; There are also "streaming" crc32_start/update/end routines below, that allow you to calculate crc32 for data that doesn't fit in a single memory block.
crc32_start()
cx16.r12 = data
cx16.r13 = data+length
while cx16.r12!=cx16.r13 {
crc32_update(@(cx16.r12))
cx16.r12++
}
cx16.r1 ^= $ffff
cx16.r0 ^= $ffff
crc32_end()
}
sub crc32_start() {
; start the "streaming" crc32
; note: tracks the crc32 checksum in cx16.r14 and cx16.r15!
; if your code uses these, it must save/restore them before calling this routine
cx16.r14 = cx16.r15 = 0
}
asmsub crc32_update(ubyte value @A) {
; update the "streaming" crc32 with next byte value
; note: tracks the crc32 checksum in cx16.r14 and cx16.r15!
; if your code uses these, it must save/restore them before calling this routine
%asm {{
eor cx16.r15H
sta cx16.r15H
ldy #8
- lda cx16.r15H
asl cx16.r14L
rol cx16.r14H
rol cx16.r15L
rol cx16.r15H
and #$80
beq +
lda cx16.r15H
eor #$04
sta cx16.r15H
lda cx16.r15L
eor #$c1
sta cx16.r15L
lda cx16.r14H
eor #$1d
sta cx16.r14H
lda cx16.r14L
eor #$b7
sta cx16.r14L
+ dey
bne -
rts
}}
; original prog8 code:
; cx16.r15H ^= value
; repeat 8 {
; if cx16.r15H & $80 !=0 {
; cx16.r14 <<= 1
; rol(cx16.r15)
; cx16.r15 ^= $04c1
; cx16.r14 ^= $1db7
; }
; else {
; cx16.r14 <<= 1
; rol(cx16.r15)
; }
; }
}
sub crc32_end() {
; finalize the "streaming" crc32
; result stored in cx16.r14 (low word) and cx16.r15 (high word)
cx16.r15 ^= $ffff
cx16.r14 ^= $ffff
}
}
+48 -11
View File
@@ -34,7 +34,7 @@ romsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; get a characte
romsub $FFE7 = CLALL() clobbers(A,X) ; close all files
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
asmsub STOP2() clobbers(X) -> ubyte @A {
asmsub STOP2() clobbers(X) -> bool @A {
; -- check if STOP key was pressed, returns true if so. More convenient to use than STOP() because that only sets the carry status flag.
%asm {{
jsr cbm.STOP
@@ -75,6 +75,15 @@ asmsub RDTIM16() clobbers(X) -> uword @AY {
}}
}
asmsub kbdbuf_clear() {
; -- convenience helper routine to clear the keyboard buffer
%asm {{
- jsr GETIN
cmp #0
bne -
rts
}}
}
}
@@ -110,8 +119,15 @@ asmsub init_system_phase2() {
asmsub cleanup_at_exit() {
; executed when the main subroutine does rts
%asm {{
_exitcodeCarry = *+1
lda #0
lsr a
_exitcode = *+1
lda #0 ; exit code possibly modified in exit()
_exitcodeX = *+1
ldx #0
_exitcodeY = *+1
ldy #0
rts
}}
}
@@ -194,20 +210,14 @@ _loop lda P8ZP_SCRATCH_W1
rts ; nothing to copy
_copyshort
; decrease source and target pointers so we can simply index by Y
lda P8ZP_SCRATCH_W1
bne +
dec P8ZP_SCRATCH_W1+1
+ dec P8ZP_SCRATCH_W1
lda P8ZP_SCRATCH_W2
bne +
dec P8ZP_SCRATCH_W2+1
+ dec P8ZP_SCRATCH_W2
dey
beq +
- lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
dey
bne -
+ lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
rts
_longcopy
@@ -357,6 +367,33 @@ save_SCRATCH_ZPWORD2 .word 0
}}
}
asmsub exit2(ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y) {
; -- immediately exit the program with result values in the A, X and Y registers.
%asm {{
sta cleanup_at_exit._exitcode
stx cleanup_at_exit._exitcodeX
sty cleanup_at_exit._exitcodeY
ldx prog8_lib.orig_stackpointer
txs
jmp cleanup_at_exit
}}
}
asmsub exit3(ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y, bool carry @Pc) {
; -- immediately exit the program with result values in the A, X and Y registers, and the Carry flag in the status register.
%asm {{
sta cleanup_at_exit._exitcode
lda #0
rol a
sta cleanup_at_exit._exitcodeCarry
stx cleanup_at_exit._exitcodeX
sty cleanup_at_exit._exitcodeY
ldx prog8_lib.orig_stackpointer
txs
jmp cleanup_at_exit
}}
}
inline asmsub progend() -> uword @AY {
%asm {{
lda #<prog8_program_end
+8 -43
View File
@@ -4,7 +4,7 @@
%import syslib
%import conv
%import shared_textio_functions
txt {
%option no_symbol_prefixing, ignore_unused
@@ -14,58 +14,23 @@ const ubyte DEFAULT_HEIGHT = 25
sub clear_screen() {
txt.chrout(147)
chrout(147)
}
sub home() {
txt.chrout(19)
chrout(19)
}
sub nl() {
txt.chrout('\n')
chrout('\n')
}
sub spc() {
txt.chrout(' ')
chrout(' ')
}
asmsub get_column() -> ubyte @Y {
%asm {{
sec
jmp cbm.PLOT
}}
}
asmsub row(ubyte rownum @A) clobbers(A, X, Y) {
; ---- set the cursor on the given row (starting with 0) on the current line
%asm {{
sec
jsr cbm.PLOT
tax
clc
jmp cbm.PLOT
}}
}
asmsub get_row() -> ubyte @X {
%asm {{
sec
jmp cbm.PLOT
}}
}
sub get_cursor(uword colptr, uword rowptr) {
%asm {{
sec
jsr cbm.PLOT
tya
ldy #$00
sta (colptr),y
txa
sta (rowptr),y
}}
sub bell() {
chrout(7)
}
@@ -73,7 +38,7 @@ asmsub fill_screen (ubyte character @ A, ubyte color @ Y) clobbers(A) {
; ---- fill the character screen with the given fill character. color is ignored on PET
%asm {{
jmp clear_screenchars
}}
}}
}
+99
View File
@@ -330,3 +330,102 @@ containment_wordarray .proc
_found lda #1
rts
.pend
arraycopy_split_to_normal_words .proc
; P8ZP_SCRATCH_W1 = start of lsb array
; P8ZP_SCRATCH_W2 = start of msb array
; AY = start of normal word target array
; X = number of elements to copy
sta _modlsb+1
sty _modlsb+2
clc
adc #1
bne +
iny
+ sta _modmsb+1
sty _modmsb+2
ldy #0
- lda (P8ZP_SCRATCH_W1),y
_modlsb sta $ffff ; modified lsb store
lda _modlsb+1
clc
adc #2
sta _modlsb+1
bcc +
inc _modlsb+2
+ lda (P8ZP_SCRATCH_W2),y
_modmsb sta $ffff ; modified msb store
lda _modmsb+1
clc
adc #2
sta _modmsb+1
bcc +
inc _modmsb+2
+ iny
dex
bne -
rts
.pend
arraycopy_normal_to_split_words .proc
; P8ZP_SCRATCH_W1 = start of target lsb array
; P8ZP_SCRATCH_W2 = start of target msb array
; AY = start of normal word source array
; X = number of elements to copy
sta _modsrclsb+1
sty _modsrclsb+2
clc
adc #1
bne +
iny
+ sta _modsrcmsb+1
sty _modsrcmsb+2
ldy #0
_modsrclsb lda $ffff ; modified lsb read
sta (P8ZP_SCRATCH_W1),y
lda _modsrclsb+1
clc
adc #2
sta _modsrclsb+1
bcc +
inc _modsrclsb+2
+
_modsrcmsb lda $ffff ; modnfied msb read
sta (P8ZP_SCRATCH_W2),y
lda _modsrcmsb+1
clc
adc #2
sta _modsrcmsb+1
bcc +
inc _modsrcmsb+2
+ iny
dex
bne _modsrclsb
rts
.pend
memcopy_small .proc
; copy up to a single page (256 bytes) of memory.
; note: only works for NON-OVERLAPPING memory regions!
; P8ZP_SCRATCH_W1 = from address
; P8ZP_SCRATCH_W2 = destination address
; Y = number of bytes to copy (where 0 means 256)
cpy #0
beq _fullpage
dey
beq _lastbyte
_loop lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
dey
bne _loop
_lastbyte lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
rts
_fullpage lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
dey
bne _fullpage
rts
.pend
@@ -0,0 +1,119 @@
string {
; the string functions shared across compiler targets
%option merge, no_symbol_prefixing, ignore_unused
sub strip(str s) {
; -- gets rid of whitespace and other non-visible characters at the edges of the string
rstrip(s)
lstrip(s)
}
sub rstrip(str s) {
; -- gets rid of whitespace and other non-visible characters at the end of the string
if s[0]==0
return
cx16.r0L = string.length(s)
do {
cx16.r0L--
cx16.r1L = s[cx16.r0L]
} until cx16.r0L==0 or string.isprint(cx16.r1L) and not string.isspace(cx16.r1L)
s[cx16.r0L+1] = 0
}
sub lstrip(str s) {
; -- gets rid of whitespace and other non-visible characters at the start of the string (destructive)
cx16.r0 = lstripped(s)
if cx16.r0 != s
void string.copy(cx16.r0, s)
}
sub lstripped(str s) -> str {
; -- returns pointer to first non-whitespace and non-visible character at the start of the string (non-destructive lstrip)
if s[0]==0
return s
cx16.r0L = 255
do {
cx16.r0L++
cx16.r1L = s[cx16.r0L]
} until cx16.r1L==0 or string.isprint(cx16.r1L) and not string.isspace(cx16.r1L)
return s+cx16.r0L
}
sub trim(str s) {
; -- gets rid of whitespace characters at the edges of the string
rtrim(s)
ltrim(s)
}
sub rtrim(str s) {
; -- gets rid of whitespace characters at the end of the string
if s[0]==0
return
cx16.r0L = string.length(s)
do {
cx16.r0L--
cx16.r1L = s[cx16.r0L]
} until cx16.r0L==0 or not string.isspace(cx16.r1L)
s[cx16.r0L+1] = 0
}
sub ltrim(str s) {
; -- gets rid of whitespace characters at the start of the string (destructive)
cx16.r0 = ltrimmed(s)
if cx16.r0 != s
void string.copy(cx16.r0, s)
}
sub ltrimmed(str s) -> str {
; -- return pointer to first non-whitespace character at the start of the string (non-destructive ltrim)
if s[0]==0
return s
cx16.r0L = 255
do {
cx16.r0L++
cx16.r1L = s[cx16.r0L]
} until not string.isspace(cx16.r1L)
return s+cx16.r0L
}
sub startswith(str st, str prefix) -> bool {
ubyte prefix_len = string.length(prefix)
ubyte str_len = string.length(st)
if prefix_len > str_len
return false
cx16.r9L = st[prefix_len]
st[prefix_len] = 0
cx16.r9H = string.compare(st, prefix) as ubyte
st[prefix_len] = cx16.r9L
return cx16.r9H==0
}
sub endswith(str st, str suffix) -> bool {
ubyte suffix_len = string.length(suffix)
ubyte str_len = string.length(st)
if suffix_len > str_len
return false
return string.compare(st + str_len - suffix_len, suffix) == 0
}
sub findstr(str haystack, str needle) -> ubyte {
; searches for needle in haystack.
; returns index in haystack where it first occurs, and Carry set,
; or if needle doesn't occur in haystack it returns Carry clear and 255 (an invalid index.)
cx16.r2L = string.length(haystack)
cx16.r3L = string.length(needle)
if cx16.r3L <= cx16.r2L {
cx16.r2L = cx16.r2L-cx16.r3L+1
cx16.r3 = haystack
repeat cx16.r2L {
if string.startswith(cx16.r3, needle) {
sys.set_carry()
return cx16.r3-haystack as ubyte
}
cx16.r3++
}
}
sys.clear_carry()
return 255
}
}
@@ -0,0 +1,46 @@
txt {
; the textio functions shared across compiler targets
%option merge, no_symbol_prefixing, ignore_unused
asmsub petscii2scr(ubyte petscii_char @A) -> ubyte @A {
; -- convert petscii character to screencode
%asm {{
sta P8ZP_SCRATCH_REG
lsr a
lsr a
lsr a
lsr a
lsr a
tax
lda _offsets,x
eor P8ZP_SCRATCH_REG
rts
_offsets .byte 128, 0, 64, 32, 64, 192, 128, 128
}}
}
asmsub petscii2scr_str(str petscii_string @AY) {
; -- convert petscii string to screencodes string
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
- lda (P8ZP_SCRATCH_W1),y
beq +
jsr petscii2scr
sta (P8ZP_SCRATCH_W1),y
iny
bne -
+ rts
}}
}
sub print_bool(bool value) {
if value
txt.print("true")
else
txt.print("false")
}
}
+5 -24
View File
@@ -1,5 +1,7 @@
; 0-terminated string manipulation routines.
%import shared_string_functions
string {
%option no_symbol_prefixing, ignore_unused
@@ -127,7 +129,7 @@ _startloop dey
asmsub find(uword string @AY, ubyte character @X) -> ubyte @A, bool @Pc {
; Locates the first position of the given character in the string,
; returns Carry set if found + index in A, or A=0 + Carry clear if not found.
; returns Carry set if found + index in A, or Carry clear if not found (and A will be 255, an invalid index).
%asm {{
; need to copy the the cx16 virtual registers to zeropage to make this run on C64...
sta P8ZP_SCRATCH_W1
@@ -140,7 +142,7 @@ _startloop dey
beq _found
iny
bne -
_notfound lda #0
_notfound lda #255
clc
rts
_found tya
@@ -275,27 +277,7 @@ _done rts
}}
}
sub startswith(str st, str prefix) -> bool {
ubyte prefix_len = length(prefix)
ubyte str_len = length(st)
if prefix_len > str_len
return false
cx16.r9L = st[prefix_len]
st[prefix_len] = 0
cx16.r9H = compare(st, prefix) as ubyte
st[prefix_len] = cx16.r9L
return cx16.r9H==0
}
sub endswith(str st, str suffix) -> bool {
ubyte suffix_len = length(suffix)
ubyte str_len = length(st)
if suffix_len > str_len
return false
return compare(st + str_len - suffix_len, suffix) == 0
}
asmsub pattern_match(str string @AY, str pattern @R0) clobbers(Y) -> ubyte @A {
asmsub pattern_match(str string @AY, str pattern @R0) clobbers(Y) -> bool @A {
%asm {{
; pattern matching of a string.
; Input: cx16.r0: A NUL-terminated, <255-length pattern
@@ -364,7 +346,6 @@ fail clc ; yes, no match found, return with c=0
}}
}
asmsub hash(str string @R0) -> ubyte @A {
; experimental 8 bit hashing function.
; hash(-1)=179; clear carry; hash(i) = ROL hash(i-1) XOR string[i]
+26 -16
View File
@@ -8,7 +8,7 @@ conv {
str string_out = "????????????????" ; result buffer for the string conversion routines
sub str_ub0(ubyte value) {
sub str_ub0(ubyte value) -> str {
; ---- convert the ubyte in A in decimal string form, with left padding 0s (3 positions total)
ubyte hundreds = value / 100
value -= hundreds*100
@@ -18,14 +18,16 @@ sub str_ub0(ubyte value) {
string_out[1] = tens+'0'
string_out[2] = value+'0'
string_out[3] = 0
return string_out
}
sub str_ub(ubyte value) {
sub str_ub(ubyte value) -> str {
; ---- convert the ubyte in A in decimal string form, without left padding 0s
internal_str_ub(value, string_out)
return string_out
}
sub str_b(byte value) {
sub str_b(byte value) -> str {
; ---- convert the byte in A in decimal string form, without left padding 0s
uword out_ptr = &string_out
if value<0 {
@@ -34,6 +36,7 @@ sub str_b(byte value) {
value = -value
}
internal_str_ub(value as ubyte, out_ptr)
return string_out
}
sub internal_str_ub(ubyte value, uword out_ptr) {
@@ -41,9 +44,9 @@ sub internal_str_ub(ubyte value, uword out_ptr) {
value -= hundreds*100
ubyte tens = value / 10
value -= tens*10
if hundreds
if hundreds!=0
goto output_hundreds
if tens
if tens!=0
goto output_tens
goto output_ones
output_hundreds:
@@ -60,14 +63,15 @@ output_ones:
str hex_digits = "0123456789abcdef"
sub str_ubhex (ubyte value) {
sub str_ubhex (ubyte value) -> str {
; ---- convert the ubyte in A in hex string form
string_out[0] = hex_digits[value>>4]
string_out[1] = hex_digits[value&15]
string_out[2] = 0
return string_out
}
sub str_ubbin (ubyte value) {
sub str_ubbin (ubyte value) -> str {
; ---- convert the ubyte in A in binary string form
uword out_ptr = &string_out
repeat 8 {
@@ -79,9 +83,10 @@ sub str_ubbin (ubyte value) {
out_ptr++
}
@(out_ptr) = 0
return string_out
}
sub str_uwbin (uword value) {
sub str_uwbin (uword value) -> str {
; ---- convert the uword in A/Y in binary string form
uword out_ptr = &string_out
repeat 16 {
@@ -93,9 +98,10 @@ sub str_uwbin (uword value) {
out_ptr++
}
@(out_ptr) = 0
return string_out
}
sub str_uwhex (uword value) {
sub str_uwhex (uword value) -> str {
; ---- convert the uword in A/Y in hexadecimal string form (4 digits)
ubyte bits = msb(value)
string_out[0] = hex_digits[bits>>4]
@@ -104,9 +110,10 @@ sub str_uwhex (uword value) {
string_out[2] = hex_digits[bits>>4]
string_out[3] = hex_digits[bits&15]
string_out[4] = 0
return string_out
}
sub str_uw0 (uword value) {
sub str_uw0 (uword value) -> str {
; ---- convert the uword in A/Y in decimal string form, with left padding 0s (5 positions total)
uword value2 = value/10
ubyte digits = value-value2*10 as ubyte
@@ -124,14 +131,16 @@ sub str_uw0 (uword value) {
string_out[3] = tens+'0'
string_out[4] = digits+'0'
string_out[5] = 0
return string_out
}
sub str_uw (uword value) {
sub str_uw (uword value) -> str {
; ---- convert the uword in A/Y in decimal string form, without left padding 0s
internal_str_uw(value, string_out)
return string_out
}
sub str_w (word value) {
sub str_w (word value) -> str {
; ---- convert the (signed) word in A/Y in decimal string form, without left padding 0's
uword out_ptr = &string_out
if value<0 {
@@ -140,6 +149,7 @@ sub str_w (word value) {
value = -value
}
internal_str_uw(value as uword, out_ptr)
return string_out
}
sub internal_str_uw(uword value, uword out_ptr) {
@@ -153,13 +163,13 @@ sub internal_str_uw(uword value, uword out_ptr) {
ubyte thousands = value4-value5*10 as ubyte
uword value6 = value5/10
ubyte tenthousands = value5-value6*10 as ubyte
if tenthousands
if tenthousands!=0
goto output_tenthousands
if thousands
if thousands!=0
goto output_thousands
if hundreds
if hundreds!=0
goto output_hundreds
if tens
if tens!=0
goto output_tens
goto output_ones
output_tenthousands:
+214 -1
View File
@@ -1 +1,214 @@
; there is no diskio yet for the VM target.
; file I/O routines. (partially implemented)
%import textio
%import syslib
diskio {
%option no_symbol_prefixing, ignore_unused
sub directory() -> bool {
; -- Prints the directory contents to the screen. Returns success.
%ir {{
loadm.w r65534,diskio.load.filenameptr
loadm.w r65535,diskio.load.address_override
syscall 61 (): r0.b
returnr.b r0
}}
}
sub list_filenames(uword pattern_ptr, uword filenames_buffer, uword filenames_buf_size) -> ubyte {
; -- fill the provided buffer with the names of the files on the disk (until buffer is full).
; Files in the buffer are separated by a 0 byte. You can provide an optional pattern to match against.
; After the last filename one additional 0 byte is placed to indicate the end of the list.
; Returns number of files (it skips 'dir' entries i.e. subdirectories).
; Also sets carry on exit: Carry clear = all files returned, Carry set = directory has more files that didn't fit in the buffer.
txt.print("@TODO: list_filenames\n")
sys.clear_carry()
return 0
}
; ----- iterative file lister functions (uses the read io channel) -----
sub lf_start_list(uword pattern_ptr) -> bool {
; -- start an iterative file listing with optional pattern matching.
; note: only a single iteration loop can be active at a time!
txt.print("@TODO: lf_start_list\n")
return false
}
sub lf_next_entry() -> bool {
; -- retrieve the next entry from an iterative file listing session.
; results will be found in list_blocks, list_filename, and list_filetype.
; if it returns false though, there are no more entries (or an error occurred).
txt.print("@TODO: lf_next_entry\n")
return false
}
sub lf_end_list() {
txt.print("@TODO: lf_end_list\n")
}
; ----- iterative file loader functions (uses the input io channel) -----
sub f_open(uword filenameptr) -> bool {
; -- open a file for iterative reading with f_read
; note: only a single iteration loop can be active at a time!
; Returns true if the file is successfully opened and readable.
; No need to check status(), unlike f_open_w() !
; NOTE: the default input isn't yet set to this logical file, you must use reset_read_channel() to do this,
; if you're going to read from it yourself instead of using f_read()!
txt.print("@TODO: f_open\n")
return false
}
sub f_read(uword bufferpointer, uword num_bytes) -> uword {
; -- read from the currently open file, up to the given number of bytes.
; returns the actual number of bytes read. (checks for End-of-file and error conditions)
txt.print("@TODO: f_read\n")
return 0
}
sub f_read_all(uword bufferpointer) -> uword {
; -- read the full contents of the file, returns number of bytes read.
txt.print("@TODO: f_read_all\n")
return 0
}
sub f_readline(uword bufptr) -> ubyte {
; Routine to read text lines from a text file. Lines must be less than 255 characters.
; Reads characters from the input file UNTIL a newline or return character (or EOF).
; The line read will be 0-terminated in the buffer (and not contain the end of line character).
; The length of the line is returned in Y. Note that an empty line is okay and is length 0!
; I/O error status should be checked by the caller itself via READST() routine.
txt.print("@TODO: f_readline\n")
return 0
}
sub f_close() {
; -- end an iterative file loading session (close channels).
txt.print("@TODO: f_close\n")
}
; ----- iterative file writing functions (uses write io channel) -----
sub f_open_w(uword filenameptr) -> bool {
; -- open a file for iterative writing with f_write
; WARNING: returns true if the open command was received by the device,
; but this can still mean the file wasn't successfully opened for writing!
; (for example, if it already exists). This is different than f_open()!
; To be 100% sure if this call was successful, you have to use status()
; and check the drive's status message!
txt.print("@TODO: f_open_w\n")
return false
}
sub f_write(uword bufferpointer, uword num_bytes) -> bool {
; -- write the given number of bytes to the currently open file
txt.print("@TODO: f_write\n")
return false
}
sub f_close_w() {
; -- end an iterative file writing session (close channels).
txt.print("@TODO: f_close_w\n")
}
; ---- other functions ----
sub chdir(str path) {
; -- change current directory.
txt.print("@TODO: chdir\n")
}
sub mkdir(str name) {
; -- make a new subdirectory.
txt.print("@TODO: mkdir\n")
}
sub rmdir(str name) {
; -- remove a subdirectory.
txt.print("@TODO: rmdir\n")
}
sub curdir() -> uword {
; return current directory name or 0 if error
txt.print("@TODO: curdir\n")
return 0
}
sub status() -> str {
; -- retrieve the disk drive's current status message
return "unknown"
}
sub save(uword filenameptr, uword start_address, uword savesize) -> bool {
%ir {{
load.b r65532,0
loadm.w r65533,diskio.save.filenameptr
loadm.w r65534,diskio.save.start_address
loadm.w r65535,diskio.save.savesize
syscall 58 (r65532.b, r65533.w, r65534.w, r65535.w): r0.b
returnr.b r0
}}
}
; like save() but omits the 2 byte prg header.
sub save_raw(uword filenameptr, uword startaddress, uword savesize) -> bool {
%ir {{
load.b r65532,1
loadm.w r65533,diskio.save.filenameptr
loadm.w r65534,diskio.save.start_address
loadm.w r65535,diskio.save.savesize
syscall 58 (r65532.b, r65533.w, r65534.w, r65535.w): r0.b
returnr.b r0
}}
}
; Use kernal LOAD routine to load the given program file in memory.
; This is similar to Basic's LOAD "filename",drive / LOAD "filename",drive,1
; If you don't give an address_override, the location in memory is taken from the 2-byte file header.
; If you specify a custom address_override, the first 2 bytes in the file are ignored
; and the rest is loaded at the given location in memory.
; Returns the end load address+1 if successful or 0 if a load error occurred.
sub load(uword filenameptr, uword address_override) -> uword {
%ir {{
loadm.w r65534,diskio.load.filenameptr
loadm.w r65535,diskio.load.address_override
syscall 56 (r65534.w, r65535.w): r0.w
returnr.w r0
}}
}
; Identical to load(), but DOES INCLUDE the first 2 bytes in the file.
; No program header is assumed in the file. Everything is loaded.
; See comments on load() for more details.
sub load_raw(uword filenameptr, uword start_address) -> uword {
%ir {{
loadm.w r65534,diskio.load_raw.filenameptr
loadm.w r65535,diskio.load_raw.start_address
syscall 57 (r65534.w, r65535.w): r0.w
returnr.w r0
}}
}
sub delete(uword filenameptr) {
; -- delete a file on the drive
%ir {{
loadm.w r65535,diskio.delete.filenameptr
syscall 59 (r65535.w)
}}
}
sub rename(uword oldfileptr, uword newfileptr) {
; -- rename a file on the drive
%ir {{
loadm.w r65534,diskio.rename.oldfileptr
loadm.w r65535,diskio.rename.newfileptr
syscall 60 (r65534.w, r65535.w)
}}
}
}
+95 -33
View File
@@ -176,6 +176,24 @@ math {
}}
}
sub randrange(ubyte n) -> ubyte {
; -- return random number uniformly distributed from 0 to n-1 (compensates for divisibility bias)
cx16.r0H = 255 / n * n
do {
cx16.r0L = math.rnd()
} until cx16.r0L < cx16.r0H
return cx16.r0L % n
}
sub randrangew(uword n) -> uword {
; -- return random number uniformly distributed from 0 to n-1 (compensates for divisibility bias)
cx16.r1 = 65535 / n * n
do {
cx16.r0 = math.rndw()
} until cx16.r0 < cx16.r1
return cx16.r0 % n
}
sub rndseed(uword seed1, uword seed2) {
; -- reset the pseudo RNG's seed values. Defaults are: $a55a, $7653.
%ir {{
@@ -190,7 +208,7 @@ math {
ubyte result = 7
ubyte compare = $80
repeat {
if value&compare
if value&compare!=0
return result
result--
if_z
@@ -203,7 +221,7 @@ math {
ubyte result = 15
uword compare = $8000
repeat {
if value&compare
if value&compare!=0
return result
result--
if_z
@@ -295,43 +313,87 @@ math {
sub crc16(uword data, uword length) -> uword {
; calculates the CRC16 (XMODEM) checksum of the buffer.
cx16.r0 = 0 ; the crc value
repeat length {
cx16.r0H ^= @(data)
repeat 8 {
if cx16.r0H & $80
cx16.r0 = (cx16.r0<<1)^$1021
else
cx16.r0<<=1
}
data++
; There are also "streaming" crc16_start/update/end routines below, that allow you to calculate crc32 for data that doesn't fit in a single memory block.
crc16_start()
cx16.r13 = data
cx16.r14 = data+length
while cx16.r13!=cx16.r14 {
crc16_update(@(cx16.r13))
cx16.r13++
}
return cx16.r0
return crc16_end()
}
sub crc16_start() {
; start the "streaming" crc16
; note: tracks the crc16 checksum in cx16.r15!
; if your code uses that, it must save/restore it before calling this routine
cx16.r15 = 0
}
sub crc16_update(ubyte value) {
; update the "streaming" crc16 with next byte value
; note: tracks the crc16 checksum in cx16.r15!
; if your code uses that, it must save/restore it before calling this routine
cx16.r15H ^= value
repeat 8 {
if cx16.r15H & $80 !=0
cx16.r15 = (cx16.r15<<1)^$1021
else
cx16.r15<<=1
}
}
sub crc16_end() -> uword {
; finalize the "streaming" crc16, returns resulting crc16 value
return cx16.r15
}
sub crc32(uword data, uword length) {
; Calculates the CRC-32 (POSIX) checksum of the buffer.
; because prog8 doesn't have 32 bits integers, we have to split up the calculation over 2 words.
; result stored in cx16.r0 (low word) and cx16.r1 (high word)
cx16.r1 = 0
cx16.r0 = 0
repeat length {
cx16.r1H ^= @(data)
repeat 8 {
if cx16.r1H & $80 {
cx16.r0 <<= 1
rol(cx16.r1)
cx16.r1 ^= $04c1
cx16.r0 ^= $1db7
}
else {
cx16.r0 <<= 1
rol(cx16.r1)
}
}
data++
; result stored in cx16.r14 (low word) and cx16.r15 (high word)
; There are also "streaming" crc32_start/update/end routines below, that allow you to calculate crc32 for data that doesn't fit in a single memory block.
crc32_start()
cx16.r12 = data
cx16.r13 = data+length
while cx16.r12!=cx16.r13 {
crc32_update(@(cx16.r12))
cx16.r12++
}
cx16.r1 ^= $ffff
cx16.r0 ^= $ffff
crc32_end()
}
sub crc32_start() {
; start the "streaming" crc32
; note: tracks the crc32 checksum in cx16.r14 and cx16.r15!
; if your code uses these, it must save/restore them before calling this routine
cx16.r14 = cx16.r15 = 0
}
sub crc32_update(ubyte value) {
; update the "streaming" crc32 with next byte value
; note: tracks the crc32 checksum in cx16.r14 and cx16.r15!
; if your code uses these, it must save/restore them before calling this routine
cx16.r15H ^= value
repeat 8 {
if cx16.r15H & $80 !=0 {
cx16.r14 <<= 1
rol(cx16.r15)
cx16.r15 ^= $04c1
cx16.r14 ^= $1db7
}
else {
cx16.r14 <<= 1
rol(cx16.r15)
}
}
}
sub crc32_end() {
; finalize the "streaming" crc32
; result stored in cx16.r14 (low word) and cx16.r15 (high word)
cx16.r15 ^= $ffff
cx16.r14 ^= $ffff
}
}
+39 -29
View File
@@ -12,13 +12,17 @@ monogfx {
; read-only control variables:
uword width = 0
uword height = 0
bool dont_stipple_flag = true ; set to false to enable stippling mode
ubyte mode
const ubyte MODE_NORMAL = %00000000
const ubyte MODE_STIPPLE = %00000001
const ubyte MODE_INVERT = %00000010
sub lores() {
; enable 320*240 bitmap mode
sys.gfx_enable(0)
width = 320
height = 240
mode = MODE_NORMAL
clear_screen(0)
}
@@ -27,6 +31,7 @@ monogfx {
sys.gfx_enable(1)
width = 640
height = 480
mode = MODE_NORMAL
clear_screen(0)
}
@@ -34,15 +39,14 @@ monogfx {
; back to normal text mode
}
sub clear_screen(ubyte color) {
stipple(false)
if color
color=255
sys.gfx_clear(color)
sub drawmode(ubyte dm) {
mode = dm
}
sub stipple(bool enable) {
dont_stipple_flag = not enable
sub clear_screen(ubyte color) {
if color!=0
color=255
sys.gfx_clear(color)
}
sub rect(uword xx, uword yy, uword rwidth, uword rheight, bool draw) {
@@ -126,17 +130,17 @@ monogfx {
}
word @zp d = 0
cx16.r1L = true ; 'positive_ix'
cx16.r1L = 1 ; true ; 'positive_ix'
if dx < 0 {
dx = -dx
cx16.r1L = false
cx16.r1L = 0 ; false
}
word @zp dx2 = dx*2
word @zp dy2 = dy*2
cx16.r14 = x1 ; internal plot X
if dx >= dy {
if cx16.r1L {
if cx16.r1L!=0 {
repeat {
plot(cx16.r14, y1, draw)
if cx16.r14==x2
@@ -163,7 +167,7 @@ monogfx {
}
}
else {
if cx16.r1L {
if cx16.r1L!=0 {
repeat {
plot(cx16.r14, y1, draw)
if y1 == y2
@@ -337,13 +341,19 @@ monogfx {
sub plot(uword @zp xx, uword @zp yy, bool @zp draw) {
if draw {
if dont_stipple_flag
sys.gfx_plot(xx, yy, 255)
else {
if (xx ^ yy)&1
when mode {
MODE_NORMAL -> {
sys.gfx_plot(xx, yy, 255)
else
sys.gfx_plot(xx, yy, 0)
}
MODE_STIPPLE -> {
if (xx ^ yy)&1 !=0
sys.gfx_plot(xx, yy, 255)
else
sys.gfx_plot(xx, yy, 0)
}
MODE_INVERT -> {
sys.gfx_plot(xx, yy, 255 ^ sys.gfx_getpixel(xx, yy))
}
}
}
else
@@ -359,8 +369,8 @@ monogfx {
plot(xx, yy, draw)
}
sub pget(uword @zp xx, uword yy) -> ubyte {
return sys.gfx_getpixel(xx, yy)
sub pget(uword @zp xx, uword yy) -> bool {
return sys.gfx_getpixel(xx, yy) as bool
}
sub fill(uword x, uword y, bool draw) {
@@ -399,7 +409,7 @@ monogfx {
dy = stack_dy[cx16.r12L]
yy+=dy
}
cx16.r11L = pget(xx as uword, yy as uword) ; old_color
cx16.r11L = pget(xx as uword, yy as uword) as ubyte ; old_color
if cx16.r11L == cx16.r10L
return
if xx<0 or xx>width-1 or yy<0 or yy>height-1
@@ -407,16 +417,16 @@ monogfx {
push_stack(xx, xx, yy, 1)
push_stack(xx, xx, yy + 1, -1)
word left = 0
while cx16.r12L {
while cx16.r12L!=0 {
pop_stack()
xx = x1
while xx >= 0 {
if pget(xx as uword, yy as uword) != cx16.r11L
if pget(xx as uword, yy as uword) as ubyte != cx16.r11L
break
xx--
}
if x1!=xx
horizontal_line(xx as uword+1, yy as uword, x1-xx as uword, cx16.r10L)
horizontal_line(xx as uword+1, yy as uword, x1-xx as uword, cx16.r10L as bool)
else
goto skip
@@ -426,14 +436,14 @@ monogfx {
xx = x1 + 1
do {
cx16.r9 = xx
cx16.r9 = xx as uword
while xx <= width-1 {
if pget(xx as uword, yy as uword) != cx16.r11L
if pget(xx as uword, yy as uword) as ubyte != cx16.r11L
break
xx++
}
if cx16.r9!=xx
horizontal_line(cx16.r9, yy as uword, (xx as uword)-cx16.r9, cx16.r10L)
horizontal_line(cx16.r9, yy as uword, (xx as uword)-cx16.r9, cx16.r10L as bool)
push_stack(left, xx - 1, yy, dy)
if xx > x2 + 1
@@ -441,7 +451,7 @@ monogfx {
skip:
xx++
while xx <= x2 {
if pget(xx as uword, yy as uword) == cx16.r11L
if pget(xx as uword, yy as uword) as ubyte == cx16.r11L
break
xx++
}
@@ -457,7 +467,7 @@ skip:
; TODO vm bitmap charset
}
sub text(uword @zp xx, uword yy, bool draw, uword sctextptr) {
sub text(uword @zp xx, uword yy, bool draw, str sctextptr) {
; -- Write some text at the given pixel position. The text string must be in screencode encoding (not petscii!).
; You must also have called text_charset() first to select and prepare the character set to use.
; TODO vm bitmap charset
Loaded 100 of 244 files, more files were not shown because too many files have changed in this diff. Show more