mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 20:02:27 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47dbafacd4 | ||
|
|
5b6811d073 | ||
|
|
7516116bb7 | ||
|
|
e6014ea4dd | ||
|
|
362abfe284 | ||
|
|
ad4880997a | ||
|
|
592becc126 | ||
|
|
c38765301e | ||
|
|
5f27426f59 | ||
|
|
d924f8bff8 | ||
|
|
d14c61b160 | ||
|
|
fe2b67998c | ||
|
|
04df3c9f7f | ||
|
|
de3d0b40dc | ||
|
|
4db4a5f1b2 | ||
|
|
5a0524ff4d | ||
|
|
5b7801eea1 | ||
|
|
fbe231793b | ||
|
|
6a9269111e | ||
|
|
a94cfd34f5 | ||
|
|
28eae5a0fd | ||
|
|
1818738fc8 | ||
|
|
7e1e7a0780 | ||
|
|
1fc79ff6dd | ||
|
|
3535c1acda | ||
|
|
33c8caac8f | ||
|
|
51d708bbdd | ||
|
|
a5a918df84 | ||
|
|
820541e427 | ||
|
|
e63a8f0c01 | ||
|
|
c11a9b8709 | ||
|
|
80f39e8097 | ||
|
|
2a8b65e29c | ||
|
|
4bdf50145e | ||
|
|
3a9919a377 | ||
|
|
eef8ae00b8 | ||
|
|
ed15fac691 | ||
|
|
f739e679e4 | ||
|
|
fc0fae8caf | ||
|
|
f46896fd74 | ||
|
|
52649a8e4f | ||
|
|
bdfb01f6a0 | ||
|
|
1137e57393 | ||
|
|
267ea13e8c | ||
|
|
04f7b772a3 | ||
|
|
42c7569791 | ||
|
|
6d29b00a80 | ||
|
|
9f1bd2d7d6 | ||
|
|
9826d7c494 | ||
|
|
c6bf57b390 | ||
|
|
bfcf07c1a2 | ||
|
|
4d7e96d423 | ||
|
|
449461e412 | ||
|
|
607275ec66 | ||
|
|
e55cde2a81 | ||
|
|
84afb374e6 | ||
|
|
da1620807f | ||
|
|
f39ef8f565 | ||
|
|
fe8b6e820c | ||
|
|
f29d24e96a | ||
|
|
620ffe54ec | ||
|
|
ceaa4cd07d | ||
|
|
af17f903ee | ||
|
|
c532e28841 | ||
|
|
dba0846866 | ||
|
|
bed629998a | ||
|
|
bc2ede76bf | ||
|
|
2a1fec2ed2 | ||
|
|
004048e5a7 | ||
|
|
b941d6f1e4 | ||
|
|
37b346740b | ||
|
|
f5e332daf7 | ||
|
|
fe9a9fc5cb | ||
|
|
cc57477b99 | ||
|
|
a1574a7187 | ||
|
|
a5110b1f96 | ||
|
|
006713fe13 | ||
|
|
7868e672e0 | ||
|
|
e1a133c2c0 | ||
|
|
c77cd0da39 | ||
|
|
577333f2c4 | ||
|
|
7d8cdcbfea | ||
|
|
c5c4c6f111 | ||
|
|
73be754680 | ||
|
|
acd841dbb6 | ||
|
|
6b52ba9397 | ||
|
|
10d12f73d6 | ||
|
|
cd9119655c | ||
|
|
41afeccd51 | ||
|
|
6b87cbb703 | ||
|
|
32afcbfe42 | ||
|
|
bc2b38daf4 | ||
|
|
f40b7b62bb | ||
|
|
1ca3f64bf0 | ||
|
|
92527b4c1d | ||
|
|
c48012c385 | ||
|
|
a282b17286 | ||
|
|
58d9463f16 | ||
|
|
047decd552 | ||
|
|
82e0877e64 | ||
|
|
040d75dafa | ||
|
|
4e1686f6e3 | ||
|
|
b5e691f367 | ||
|
|
325f55f22d | ||
|
|
9724f2db7d | ||
|
|
5f20f321f0 | ||
|
|
d4b087ea3f | ||
|
|
8ff10724d1 | ||
|
|
1581381467 | ||
|
|
96b5a30f60 | ||
|
|
0e17a0474a | ||
|
|
b27368175d | ||
|
|
aba36f7c92 | ||
|
|
a3fa946300 | ||
|
|
01bbc2234e | ||
|
|
58e1864144 | ||
|
|
88458f5355 | ||
|
|
a4f697bae1 | ||
|
|
8201408f16 | ||
|
|
8b8caa1c2e | ||
|
|
4dc50cb551 | ||
|
|
5522a305ab | ||
|
|
d7f72056fc | ||
|
|
64c9c9b7fe | ||
|
|
98e1c843e4 | ||
|
|
906d9d858c | ||
|
|
16c1309df1 | ||
|
|
6eacf1bddd | ||
|
|
6c8c8e11cc | ||
|
|
e941d2665a | ||
|
|
68669dbef0 | ||
|
|
6a48de9a9f | ||
|
|
9d6d98930b | ||
|
|
3cc858db12 | ||
|
|
386a391fd9 | ||
|
|
d33aed4ed5 | ||
|
|
73ec8c31ad | ||
|
|
24944ad49e | ||
|
|
26ed231f61 | ||
|
|
8485b8429f | ||
|
|
358215e4dd |
No files matched your search
@@ -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
|
||||
@@ -30,7 +30,6 @@ parsetab.py
|
||||
compiler/lib/
|
||||
|
||||
.gradle
|
||||
**/BuildVersion.kt
|
||||
/prog8compiler.jar
|
||||
sd*.img
|
||||
*.d64
|
||||
|
||||
+9
@@ -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
@@ -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>
|
||||
Generated
-10
@@ -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
@@ -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 />
|
||||
|
||||
Generated
-11
@@ -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>
|
||||
Generated
-18
@@ -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>
|
||||
Generated
-2
@@ -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
@@ -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
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
@@ -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' -> {
|
||||
|
||||
@@ -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!!)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
+444
-271
File diff suppressed because it is too large.
Load diff
@@ -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") }
|
||||
|
||||
@@ -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"
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
@@ -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") }
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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)))
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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 ----
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,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 ----
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}}
|
||||
}}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
File renamed without changes.
@@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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]
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
}}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in new issue
Block a user