Compare commits

...

83 Commits

Author SHA1 Message Date
793596614e attempt to fix ReadTheDocs build issue 2021-11-07 00:37:31 +01:00
136280100c attempt to fix ReadTheDocs build issue 2021-11-07 00:23:44 +01:00
29f1e4d2c9 attempt to fix ReadTheDocs build issue 2021-11-07 00:18:51 +01:00
72a7e61fd0 version 7.2 2021-11-06 23:42:13 +01:00
381cfca67f Merge branch 'v7.2'
# Conflicts:
#	compiler/res/version.txt
2021-11-06 23:41:39 +01:00
f40620aa25 "not x" as a condition (if, while, until) is optimized into "x==0", this avoids calculating the value 2021-11-06 23:25:32 +01:00
57a9fed42b todo 2021-11-06 19:09:33 +01:00
18d820da94 correct assignment type 2021-11-06 18:52:54 +01:00
26e66f046f implement some more missing codegen for inplace Prefix expressions 2021-11-06 18:48:42 +01:00
4270c04856 don't crash but give proper error on "-X" expression where X is not a signed type 2021-11-06 18:06:01 +01:00
74456d1135 optimized prefix-expression in to use stack evaluation less 2021-11-06 17:57:00 +01:00
62dc824bc0 tweaks 2021-11-06 17:14:07 +01:00
1605791f1b float swap() no longer uses evaluation stack but a single temp var instead + FAC1 2021-11-06 03:36:14 +01:00
37a46aa2cf complex memory assignment also tries to avoid estack evaluation (but not done yet) 2021-11-06 00:03:19 +01:00
1d2d217b94 non-optimized typecast assignments now attempt to not use evalstack 2021-11-05 23:25:07 +01:00
23961f695d fixed some parse tree node position end-columns. cleanup some todo's 2021-11-05 22:48:28 +01:00
730b208617 relaxed some type checks on certain word register assignment
preparing to optimize asmsub arg passing for complex expressions
2021-11-04 23:57:25 +01:00
f09c04eeac fix invalid asm addressing mode for certain value-to-evalstack transfers 2021-11-04 22:44:31 +01:00
be73739c62 todo 2021-11-03 23:08:11 +01:00
eea3fb48a8 add command line option 'optfloatx' to explicitly re-enable float expr optimization as this can increase code size significantly.
The output size of the various example programs using floating point, when not using this optimization, has been reduced significantly.
The resulting code runs a (tiny) bit slower though.
2021-11-03 22:52:08 +01:00
b4fa72c058 fix parent node linkage for reading array parameter 2021-11-03 21:57:31 +01:00
b0a865b0f1 update todo 2021-11-02 23:55:50 +01:00
7f49731618 fix: don't initialize block vars twice, fix: make sure the prog8_init_vars generated routine is correctly called when needed 2021-11-02 23:13:28 +01:00
3410aea788 fix regression: don't add 0 initializer when variable is assigned to anyway (or is loopvar in a for-loop) 2021-11-02 21:23:59 +01:00
bc0a133bb1 doc 2021-11-02 20:24:45 +01:00
7e287a5359 proper parent node linkage in generated const values out of typecast expressions. Fixes crash mentioned in #72 2021-11-02 00:47:01 +01:00
1110bd0851 fix vardecl initialization value to not use stack eval anymore but separate assignment
(this causes the optimized assignment code gen to be used instead)
but some programs now end up larger in output size
2021-11-01 00:24:15 +01:00
1b576f826d remove unneeded sibling methods 2021-10-31 16:50:15 +01:00
fe17566370 improved reporting of slow stack based evaluation code 2021-10-31 14:18:49 +01:00
e3c00669c1 fixed improved asm generation for conditions that compare signed word to zero 2021-10-31 02:39:45 +02:00
33d17afc32 improved asm generation for conditions that compare byte/word to zero 2021-10-31 01:58:16 +02:00
2388359a99 improved asm generation for conditions that compare ubyte/uword to zero 2021-10-31 01:39:37 +02:00
2df0c9503c improved asm generation for conditions that compare floats to zero 2021-10-31 01:28:08 +02:00
61fa3bc77c comparisonjump tweak 2021-10-31 00:57:22 +02:00
03ac9b6956 various cleanups, slight update to dbus 2021-10-30 19:30:19 +02:00
dfbef8495d got rid of ParsingFailedError 2021-10-30 17:05:23 +02:00
7b17c49d8f update petscii tables with improvements to box drawing chars. fixes #68 2021-10-30 16:45:23 +02:00
4b3f31c2ee added option to suppress assembler output (and enabled this in unit tests) 2021-10-30 15:26:40 +02:00
9ccc65bf8f more petscii tests 2021-10-30 15:15:11 +02:00
f9e22add03 fix crash when using array as paramater type 2021-10-30 15:15:00 +02:00
846951cda7 kotlin 1.5.31 2021-10-30 12:26:05 +02:00
97836e18b2 simplified gradle config, automatically run installDist task after build 2021-10-30 12:01:52 +02:00
7b69df4db2 todos 2021-10-30 00:38:48 +02:00
3767b4bbe7 'Program' is not an ast Node 2021-10-30 00:25:34 +02:00
d7d2eefa4f implemented CharLiteral.constValue() 2021-10-30 00:05:55 +02:00
6737f28d1e moved unittests of compilerInterfaces into compiler module itself 2021-10-29 23:46:51 +02:00
3da9404c2d removed memsizer arg from all builtin functions 2021-10-29 23:38:31 +02:00
4d5bd0fa32 simplify ZeroPage reserved locations handling a bit 2021-10-29 17:34:42 +02:00
1137da37c3 reshuffle ErrorReporter 2021-10-29 17:02:03 +02:00
495a18805c move asmgen test to codeGeneration module 2021-10-29 16:20:53 +02:00
a226b82d0b cleanup imports 2021-10-29 05:30:12 +02:00
0b5ddcdc9b split out the code generator into own project submodule 2021-10-29 05:00:30 +02:00
82da8f4946 adding tests to the new project's submodules 2021-10-29 03:36:42 +02:00
5ff481ce3c make sure tmp folders exist for unit tests 2021-10-29 03:04:16 +02:00
f21dcaa6fb split out the code optimizers into own project submodule 2021-10-29 02:42:10 +02:00
2c940de598 better name 2021-10-29 01:06:01 +02:00
ce75b776bb refactor loadAsmIncludeFile response 2021-10-29 01:01:24 +02:00
7d22b9b9f9 simplified name conflict check for sub params 2021-10-29 00:20:33 +02:00
6cb8b3b5cd removed unneeded scope param from lookup() 2021-10-29 00:01:28 +02:00
2bf4017f2b fix nested label lookups in anon scopes
fixed non-global qualified names lookup
2021-10-28 23:48:01 +02:00
08d2f8568b refactoring symbol lookups 2021-10-27 23:48:12 +02:00
ac5f45d2d4 fix nested label lookups in anon scopes (partly) 2021-10-27 02:41:24 +02:00
3cc7ad7d20 slightly improve error message for unknown module import 2021-10-27 00:38:36 +02:00
d4513364fb fix compiler crash when file on command line doesn't exist 2021-10-27 00:23:54 +02:00
9684f4e42a add unit tests for AnonScope refactoring, cleaned up imports 2021-10-27 00:05:46 +02:00
f4186981fd todo 2021-10-26 23:30:48 +02:00
141689e697 change many uses of .definingScope to just the parent node 2021-10-26 23:25:16 +02:00
743c8b44a2 AnonymousScope refactor: it's no longer a INameScope
because it doesn't contain scoped variables (these are moved to the subroutine's scope)
2021-10-26 23:01:51 +02:00
5e1459564a no longer take AddressOf a str-variable that is a subroutine's parameter with str type (it's just an address/uword already) 2021-10-25 23:49:01 +02:00
69a8813a3d first steps to add support for str parameter type 2021-10-24 20:57:10 +02:00
17175df835 more precise error messages checks 2021-10-24 19:14:46 +02:00
6b32535cb6 don't complain about uninitialized str var if it's not a var 2021-10-24 15:13:38 +02:00
2815a14bb5 (7.2) can now test for specific error messages, and specify to omit invoking assembler in tests 2021-10-22 01:25:26 +02:00
f4dfa60790 (7.2) tests for pass by ref parameters 2021-10-22 00:41:34 +02:00
35e88dd529 (7.2) correctly parse datatype of array parameters 2021-10-21 22:06:21 +02:00
4d5094a517 (7.2) cleanup Petscii converter errorhandling, add unit tests for error scenarios 2021-10-20 23:48:20 +02:00
dd5abae721 move testcase to proper location 2021-10-20 23:08:40 +02:00
8f2fb20934 Merge branch 'v7.1' into v7.2 2021-10-20 22:51:14 +02:00
74555a32ed Merge branch 'v7.1' into v7.2 2021-10-20 22:37:43 +02:00
1a111b706e Merge branch 'v7.1' into v7.2 2021-10-19 23:59:31 +02:00
4668932bac todo 2021-10-19 23:38:07 +02:00
e6c41eac93 Merge branch 'v7.1' into v7.2 2021-10-19 23:22:38 +02:00
14aad2358f version 7.2 started 2021-10-16 18:46:08 +02:00
135 changed files with 3474 additions and 1866 deletions

6
.idea/kotlinc.xml generated
View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Kotlin2JvmCompilerArguments">
<option name="jvmTarget" value="11" />
</component>
</project>

View File

@ -1,22 +1,22 @@
<component name="libraryTable"> <component name="libraryTable">
<library name="github.hypfvieh.dbus.java" type="repository"> <library name="github.hypfvieh.dbus.java" type="repository">
<properties maven-id="com.github.hypfvieh:dbus-java:3.3.0" /> <properties maven-id="com.github.hypfvieh:dbus-java:3.3.1" />
<CLASSES> <CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/github/hypfvieh/dbus-java/3.3.0/dbus-java-3.3.0.jar!/" /> <root url="jar://$MAVEN_REPOSITORY$/com/github/hypfvieh/dbus-java/3.3.1/dbus-java-3.3.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-unixsocket/0.38.5/jnr-unixsocket-0.38.5.jar!/" /> <root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-unixsocket/0.38.6/jnr-unixsocket-0.38.6.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-ffi/2.2.1/jnr-ffi-2.2.1.jar!/" /> <root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-ffi/2.2.2/jnr-ffi-2.2.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jffi/1.3.1/jffi-1.3.1.jar!/" /> <root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jffi/1.3.1/jffi-1.3.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jffi/1.3.1/jffi-1.3.1-native.jar!/" /> <root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jffi/1.3.1/jffi-1.3.1-native.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm/9.0/asm-9.0.jar!/" /> <root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm/9.1/asm-9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-commons/9.0/asm-commons-9.0.jar!/" /> <root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-commons/9.1/asm-commons-9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-analysis/9.0/asm-analysis-9.0.jar!/" /> <root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-analysis/9.1/asm-analysis-9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-tree/9.0/asm-tree-9.0.jar!/" /> <root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-tree/9.1/asm-tree-9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-util/9.0/asm-util-9.0.jar!/" /> <root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-util/9.1/asm-util-9.1.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-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-x86asm/1.0.2/jnr-x86asm-1.0.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-constants/0.10.1/jnr-constants-0.10.1.jar!/" /> <root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-constants/0.10.1/jnr-constants-0.10.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-enxio/0.32.3/jnr-enxio-0.32.3.jar!/" /> <root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-enxio/0.32.4/jnr-enxio-0.32.4.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-posix/3.1.4/jnr-posix-3.1.4.jar!/" /> <root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-posix/3.1.5/jnr-posix-3.1.5.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar!/" /> <root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar!/" />
</CLASSES> </CLASSES>
<JAVADOC /> <JAVADOC />

3
.idea/modules.xml generated
View File

@ -2,8 +2,11 @@
<project version="4"> <project version="4">
<component name="ProjectModuleManager"> <component name="ProjectModuleManager">
<modules> <modules>
<module fileurl="file://$PROJECT_DIR$/codeGeneration/codeGeneration.iml" filepath="$PROJECT_DIR$/codeGeneration/codeGeneration.iml" />
<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$/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$/compilerAst/compilerAst.iml" filepath="$PROJECT_DIR$/compilerAst/compilerAst.iml" />
<module fileurl="file://$PROJECT_DIR$/compilerInterfaces/compilerInterfaces.iml" filepath="$PROJECT_DIR$/compilerInterfaces/compilerInterfaces.iml" />
<module fileurl="file://$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.iml" filepath="$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.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$/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$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />

29
.readthedocs.yaml Normal file
View File

@ -0,0 +1,29 @@
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Set the version of Python and other tools you might need
build:
os: ubuntu-20.04
tools:
python: "3.9"
# You can also specify other tool versions:
# nodejs: "16"
# rust: "1.55"
# golang: "1.17"
# 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
# Optionally declare the Python requirements required to build your docs
python:
install:
- requirements: docs/requirements.txt

View File

@ -1,3 +1,10 @@
plugins { plugins {
id "org.jetbrains.kotlin.jvm" version "1.5.30" apply false id "org.jetbrains.kotlin.jvm" version "$kotlinVersion" apply false
}
allprojects {
repositories {
mavenLocal()
mavenCentral()
}
} }

View File

@ -0,0 +1,56 @@
plugins {
id 'java'
id 'application'
id "org.jetbrains.kotlin.jvm"
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
}
dependencies {
implementation project(':compilerInterfaces')
implementation project(':compilerAst')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.12"
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2'
testImplementation 'org.hamcrest:hamcrest:2.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2'
}
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
}
resources {
srcDirs = ["${project.projectDir}/res"]
}
}
test {
java {
srcDirs = ["${project.projectDir}/test"]
}
}
}
test {
// Enable JUnit 5 (Gradle 4.6+).
useJUnitPlatform()
// Always run tests, even when nothing changed.
dependsOn 'cleanTest'
// Show test results.
testLogging {
events "skipped", "failed"
}
}

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="module" module-name="compilerInterfaces" />
<orderEntry type="module" module-name="compilerAst" />
<orderEntry type="library" scope="TEST" name="hamcrest" level="project" />
<orderEntry type="library" name="junit.jupiter" level="project" />
</component>
</module>

View File

@ -0,0 +1,3 @@
package prog8.compiler.target
class AssemblyError(msg: String) : RuntimeException(msg)

View File

@ -0,0 +1,35 @@
package prog8.compiler.target
import com.github.michaelbull.result.fold
import prog8.ast.base.ByteDatatypes
import prog8.ast.base.DataType
import prog8.ast.base.PassByReferenceDatatypes
import prog8.ast.base.WordDatatypes
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.cbm.Petscii
import prog8.compilerinterface.ICompilationTarget
object C64Target: ICompilationTarget {
override val name = "c64"
override val machine = C64MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
val coded = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
return coded.fold(
failure = { throw it },
success = { it }
)
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
else -> -9999999
}
}
}

View File

@ -0,0 +1,36 @@
package prog8.compiler.target
import com.github.michaelbull.result.fold
import prog8.ast.base.ByteDatatypes
import prog8.ast.base.DataType
import prog8.ast.base.PassByReferenceDatatypes
import prog8.ast.base.WordDatatypes
import prog8.compiler.target.cbm.Petscii
import prog8.compiler.target.cx16.CX16MachineDefinition
import prog8.compilerinterface.ICompilationTarget
object Cx16Target: ICompilationTarget {
override val name = "cx16"
override val machine = CX16MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
val coded= if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
return coded.fold(
failure = { throw it },
success = { it }
)
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
else -> -9999999
}
}
}

View File

@ -1,16 +1,13 @@
package prog8.compiler.target.c64 package prog8.compiler.target.c64
import prog8.compiler.*
import prog8.compiler.target.CpuType
import prog8.compiler.target.IMachineDefinition
import prog8.compiler.target.IMachineFloat
import prog8.compiler.target.cbm.viceMonListPostfix import prog8.compiler.target.cbm.viceMonListPostfix
import prog8.compilerinterface.*
import java.io.IOException import java.io.IOException
import java.nio.file.Path import java.nio.file.Path
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
import kotlin.math.pow import kotlin.math.pow
internal object C64MachineDefinition: IMachineDefinition { object C64MachineDefinition: IMachineDefinition {
override val cpu = CpuType.CPU6502 override val cpu = CpuType.CPU6502
@ -30,7 +27,7 @@ internal object C64MachineDefinition: IMachineDefinition {
override fun getFloat(num: Number) = Mflpt5.fromNumber(num) override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
override fun importLibs(compilerOptions: CompilationOptions,compilationTargetName: String): List<String> { override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG) return if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
listOf("syslib") listOf("syslib")
else else
@ -77,7 +74,7 @@ internal object C64MachineDefinition: IMachineDefinition {
"sta", "stx", "sty", "tas", "tax", "tay", "tsx", "txa", "txs", "tya", "xaa") "sta", "stx", "sty", "tas", "tax", "tay", "tsx", "txa", "txs", "tya", "xaa")
internal class C64Zeropage(options: CompilationOptions) : Zeropage(options) { class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0x02 // temp storage for a single byte override val SCRATCH_B1 = 0x02 // temp storage for a single byte
override val SCRATCH_REG = 0x03 // temp storage for a register, must be B1+1 override val SCRATCH_REG = 0x03 // temp storage for a register, must be B1+1
@ -87,12 +84,11 @@ internal object C64MachineDefinition: IMachineDefinition {
init { init {
if (options.floats && options.zeropage !in arrayOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE )) if (options.floats && options.zeropage !in arrayOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
throw CompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'") throw InternalCompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
if (options.zeropage == ZeropageType.FULL) { if (options.zeropage == ZeropageType.FULL) {
free.addAll(0x04..0xf9) free.addAll(0x04..0xf9)
free.add(0xff) free.add(0xff)
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ
} else { } else {
if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) { if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
@ -125,7 +121,7 @@ internal object C64MachineDefinition: IMachineDefinition {
)) ))
} }
if(options.zeropage!=ZeropageType.DONTUSE) { if(options.zeropage!= ZeropageType.DONTUSE) {
// add the free Zp addresses // add the free Zp addresses
// these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O* // these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O*
free.addAll(listOf(0x04, 0x05, 0x06, 0x0a, 0x0e, free.addAll(listOf(0x04, 0x05, 0x06, 0x0a, 0x0e,
@ -136,17 +132,13 @@ internal object C64MachineDefinition: IMachineDefinition {
free.clear() free.clear()
} }
} }
require(SCRATCH_B1 !in free)
require(SCRATCH_REG !in free)
require(SCRATCH_W1 !in free)
require(SCRATCH_W2 !in free)
for (reserved in options.zpReserved) removeReservedFromFreePool()
reserve(reserved)
} }
} }
internal data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short): IMachineFloat { data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short):
IMachineFloat {
companion object { companion object {
val zero = Mflpt5(0, 0, 0, 0, 0) val zero = Mflpt5(0, 0, 0, 0, 0)
@ -157,7 +149,7 @@ internal object C64MachineDefinition: IMachineDefinition {
val flt = num.toDouble() val flt = num.toDouble()
if (flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE) if (flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE)
throw CompilerException("floating point number out of 5-byte mflpt range: $this") throw InternalCompilerException("floating point number out of 5-byte mflpt range: $this")
if (flt == 0.0) if (flt == 0.0)
return zero return zero
@ -178,7 +170,7 @@ internal object C64MachineDefinition: IMachineDefinition {
return when { return when {
exponent < 0 -> zero // underflow, use zero instead exponent < 0 -> zero // underflow, use zero instead
exponent > 255 -> throw CompilerException("floating point overflow: $this") exponent > 255 -> throw InternalCompilerException("floating point overflow: $this")
exponent == 0 -> zero exponent == 0 -> zero
else -> { else -> {
val mantLong = mantissa.toLong() val mantLong = mantissa.toLong()

View File

@ -1,10 +1,17 @@
package prog8.compiler.target.cbm package prog8.compiler.target.cbm
import prog8.compiler.CompilationOptions import com.github.michaelbull.result.Ok
import prog8.compiler.OutputType import com.github.michaelbull.result.Result
import prog8.compiler.target.IAssemblyProgram import com.github.michaelbull.result.mapError
import prog8.compiler.target.generatedLabelPrefix import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.IAssemblyProgram
import prog8.compilerinterface.OutputType
import prog8.compilerinterface.generatedLabelPrefix
import prog8.parser.SourceCode
import java.io.File
import java.nio.file.Path import java.nio.file.Path
import kotlin.io.path.Path
import kotlin.io.path.isRegularFile
internal const val viceMonListPostfix = "vice-mon-list" internal const val viceMonListPostfix = "vice-mon-list"
@ -20,12 +27,15 @@ class AssemblyProgram(
private val binFile = outputDir.resolve("$name.bin") private val binFile = outputDir.resolve("$name.bin")
private val viceMonListFile = outputDir.resolve("$name.$viceMonListPostfix") private val viceMonListFile = outputDir.resolve("$name.$viceMonListPostfix")
override fun assemble(options: CompilationOptions): Int { override fun assemble(quiet: Boolean, options: CompilationOptions): Int {
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps (default = do this silently) // add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps (default = do this silently)
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch", val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror", "-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
"--dump-labels", "--vice-labels", "-l", viceMonListFile.toString(), "--no-monitor") "--dump-labels", "--vice-labels", "-l", viceMonListFile.toString(), "--no-monitor")
if(quiet)
command.add("--quiet")
val outFile = when (options.output) { val outFile = when (options.output) {
OutputType.PRG -> { OutputType.PRG -> {
command.add("--cbm-prg") command.add("--cbm-prg")
@ -76,3 +86,18 @@ class AssemblyProgram(
viceMonListFile.toFile().appendText(breakpoints.joinToString("\n") + "\n") viceMonListFile.toFile().appendText(breakpoints.joinToString("\n") + "\n")
} }
} }
internal fun loadAsmIncludeFile(filename: String, source: SourceCode): Result<String, NoSuchFileException> {
return if (filename.startsWith(SourceCode.libraryFilePrefix)) {
return com.github.michaelbull.result.runCatching {
SourceCode.Resource("/prog8lib/${filename.substring(SourceCode.libraryFilePrefix.length)}").readText()
}.mapError { NoSuchFileException(File(filename)) }
} else {
val sib = Path(source.origin).resolveSibling(filename)
if (sib.isRegularFile())
Ok(SourceCode.File(sib).readText())
else
Ok(SourceCode.File(Path(filename)).readText())
}
}

View File

@ -11,7 +11,7 @@ object Petscii {
// decoding: from Petscii/Screencodes (0-255) to unicode // decoding: from Petscii/Screencodes (0-255) to unicode
// character tables used from https://github.com/dj51d/cbmcodecs // character tables used from https://github.com/dj51d/cbmcodecs
private val decodingPetsciiLowercase = arrayOf( private val decodingPetsciiLowercase = charArrayOf(
'\u0000', // 0x00 -> \u0000 '\u0000', // 0x00 -> \u0000
'\ufffe', // 0x01 -> UNDEFINED '\ufffe', // 0x01 -> UNDEFINED
'\ufffe', // 0x02 -> UNDEFINED '\ufffe', // 0x02 -> UNDEFINED
@ -270,7 +270,7 @@ object Petscii {
'\u2592' // ▒ 0xFF -> MEDIUM SHADE '\u2592' // ▒ 0xFF -> MEDIUM SHADE
) )
private val decodingPetsciiUppercase = arrayOf( private val decodingPetsciiUppercase = charArrayOf(
'\u0000', // 0x00 -> \u0000 '\u0000', // 0x00 -> \u0000
'\ufffe', // 0x01 -> UNDEFINED '\ufffe', // 0x01 -> UNDEFINED
'\ufffe', // 0x02 -> UNDEFINED '\ufffe', // 0x02 -> UNDEFINED
@ -369,13 +369,13 @@ object Petscii {
'\u2190', // ← 0x5F -> LEFTWARDS ARROW '\u2190', // ← 0x5F -> LEFTWARDS ARROW
'\u2500', // ─ 0x60 -> BOX DRAWINGS LIGHT HORIZONTAL '\u2500', // ─ 0x60 -> BOX DRAWINGS LIGHT HORIZONTAL
'\u2660', // ♠ 0x61 -> BLACK SPADE SUIT '\u2660', // ♠ 0x61 -> BLACK SPADE SUIT
'\u2502', // │ 0x62 -> BOX DRAWINGS LIGHT VERTICAL '\uf13c', // │ 0x62 -> BOX DRAWINGS LIGHT VERTICAL ONE EIGHTH LEFT (CUS)
'\u2500', // ─ 0x63 -> BOX DRAWINGS LIGHT HORIZONTAL '\uf13b', // ─ 0x63 -> BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)
'\uf122', //  0x64 -> BOX DRAWINGS LIGHT HORIZONTAL ONE QUARTER UP (CUS) '\uf122', //  0x64 -> BOX DRAWINGS LIGHT HORIZONTAL TWO EIGHTHS UP (CUS)
'\uf123', //  0x65 -> BOX DRAWINGS LIGHT HORIZONTAL TWO QUARTERS UP (CUS) '\uf123', //  0x65 -> BOX DRAWINGS LIGHT HORIZONTAL THREE EIGHTHS UP (CUS)
'\uf124', //  0x66 -> BOX DRAWINGS LIGHT HORIZONTAL ONE QUARTER DOWN (CUS) '\uf124', //  0x66 -> BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH DOWN (CUS)
'\uf126', //  0x67 -> BOX DRAWINGS LIGHT VERTICAL ONE QUARTER LEFT (CUS) '\uf126', //  0x67 -> BOX DRAWINGS LIGHT VERTICAL TWO EIGHTHS LEFT (CUS)
'\uf128', //  0x68 -> BOX DRAWINGS LIGHT VERTICAL ONE QUARTER RIGHT (CUS) '\uf128', //  0x68 -> BOX DRAWINGS LIGHT VERTICAL ONE EIGHTH RIGHT (CUS)
'\u256e', // ╮ 0x69 -> BOX DRAWINGS LIGHT ARC DOWN AND LEFT '\u256e', // ╮ 0x69 -> BOX DRAWINGS LIGHT ARC DOWN AND LEFT
'\u2570', // ╰ 0x6A -> BOX DRAWINGS LIGHT ARC UP AND RIGHT '\u2570', // ╰ 0x6A -> BOX DRAWINGS LIGHT ARC UP AND RIGHT
'\u256f', // ╯ 0x6B -> BOX DRAWINGS LIGHT ARC UP AND LEFT '\u256f', // ╯ 0x6B -> BOX DRAWINGS LIGHT ARC UP AND LEFT
@ -385,14 +385,14 @@ object Petscii {
'\uf12b', //  0x6F -> ONE EIGHTH BLOCK DOWN AND RIGHT (CUS) '\uf12b', //  0x6F -> ONE EIGHTH BLOCK DOWN AND RIGHT (CUS)
'\uf12c', //  0x70 -> ONE EIGHTH BLOCK DOWN AND LEFT (CUS) '\uf12c', //  0x70 -> ONE EIGHTH BLOCK DOWN AND LEFT (CUS)
'\u25cf', // ● 0x71 -> BLACK CIRCLE '\u25cf', // ● 0x71 -> BLACK CIRCLE
'\uf125', //  0x72 -> BOX DRAWINGS LIGHT HORIZONTAL TWO QUARTERS DOWN (CUS) '\uf125', //  0x72 -> BOX DRAWINGS LIGHT HORIZONTAL TWO EIGHTHS DOWN (CUS)
'\u2665', // ♥ 0x73 -> BLACK HEART SUIT '\u2665', // ♥ 0x73 -> BLACK HEART SUIT
'\uf127', //  0x74 -> BOX DRAWINGS LIGHT VERTICAL TWO QUARTERS LEFT (CUS) '\uf127', //  0x74 -> BOX DRAWINGS LIGHT VERTICAL THREE EIGHTHS LEFT (CUS)
'\u256d', // ╭ 0x75 -> BOX DRAWINGS LIGHT ARC DOWN AND RIGHT '\u256d', // ╭ 0x75 -> BOX DRAWINGS LIGHT ARC DOWN AND RIGHT
'\u2573', // 0x76 -> BOX DRAWINGS LIGHT DIAGONAL CROSS '\u2573', // 0x76 -> BOX DRAWINGS LIGHT DIAGONAL CROSS
'\u25cb', // ○ 0x77 -> WHITE CIRCLE '\u25cb', // ○ 0x77 -> WHITE CIRCLE
'\u2663', // ♣ 0x78 -> BLACK CLUB SUIT '\u2663', // ♣ 0x78 -> BLACK CLUB SUIT
'\uf129', //  0x79 -> BOX DRAWINGS LIGHT VERTICAL TWO QUARTERS RIGHT (CUS) '\uf129', //  0x79 -> BOX DRAWINGS LIGHT VERTICAL TWO EIGHTS RIGHT (CUS)
'\u2666', // ♦ 0x7A -> BLACK DIAMOND SUIT '\u2666', // ♦ 0x7A -> BLACK DIAMOND SUIT
'\u253c', // ┼ 0x7B -> BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL '\u253c', // ┼ 0x7B -> BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
'\uf12e', //  0x7C -> LEFT HALF BLOCK MEDIUM SHADE (CUS) '\uf12e', //  0x7C -> LEFT HALF BLOCK MEDIUM SHADE (CUS)
@ -465,13 +465,13 @@ object Petscii {
'\u259a', // ▚ 0xBF -> QUADRANT UPPER LEFT AND LOWER RIGHT '\u259a', // ▚ 0xBF -> QUADRANT UPPER LEFT AND LOWER RIGHT
'\u2500', // ─ 0xC0 -> BOX DRAWINGS LIGHT HORIZONTAL '\u2500', // ─ 0xC0 -> BOX DRAWINGS LIGHT HORIZONTAL
'\u2660', // ♠ 0xC1 -> BLACK SPADE SUIT '\u2660', // ♠ 0xC1 -> BLACK SPADE SUIT
'\u2502', // │ 0xC2 -> BOX DRAWINGS LIGHT VERTICAL '\uf13c', // │ 0xC2 -> BOX DRAWINGS LIGHT VERTICAL ONE EIGHTH LEFT (CUS)
'\u2500', // ─ 0xC3 -> BOX DRAWINGS LIGHT HORIZONTAL '\uf13b', // ─ 0xC3 -> BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)
'\uf122', //  0xC4 -> BOX DRAWINGS LIGHT HORIZONTAL ONE QUARTER UP (CUS) '\uf122', //  0xC4 -> BOX DRAWINGS LIGHT HORIZONTAL TWO EIGHTHS UP (CUS)
'\uf123', //  0xC5 -> BOX DRAWINGS LIGHT HORIZONTAL TWO QUARTERS UP (CUS) '\uf123', //  0xC5 -> BOX DRAWINGS LIGHT HORIZONTAL THREE EIGHTHS UP (CUS)
'\uf124', //  0xC6 -> BOX DRAWINGS LIGHT HORIZONTAL ONE QUARTER DOWN (CUS) '\uf124', //  0xC6 -> BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH DOWN (CUS)
'\uf126', //  0xC7 -> BOX DRAWINGS LIGHT VERTICAL ONE QUARTER LEFT (CUS) '\uf126', //  0xC7 -> BOX DRAWINGS LIGHT VERTICAL TWO EIGHTHS LEFT (CUS)
'\uf128', //  0xC8 -> BOX DRAWINGS LIGHT VERTICAL ONE QUARTER RIGHT (CUS) '\uf128', //  0xC8 -> BOX DRAWINGS LIGHT VERTICAL ONE EIGHTH RIGHT (CUS)
'\u256e', // ╮ 0xC9 -> BOX DRAWINGS LIGHT ARC DOWN AND LEFT '\u256e', // ╮ 0xC9 -> BOX DRAWINGS LIGHT ARC DOWN AND LEFT
'\u2570', // ╰ 0xCA -> BOX DRAWINGS LIGHT ARC UP AND RIGHT '\u2570', // ╰ 0xCA -> BOX DRAWINGS LIGHT ARC UP AND RIGHT
'\u256f', // ╯ 0xCB -> BOX DRAWINGS LIGHT ARC UP AND LEFT '\u256f', // ╯ 0xCB -> BOX DRAWINGS LIGHT ARC UP AND LEFT
@ -481,14 +481,14 @@ object Petscii {
'\uf12b', //  0xCF -> ONE EIGHTH BLOCK DOWN AND RIGHT (CUS) '\uf12b', //  0xCF -> ONE EIGHTH BLOCK DOWN AND RIGHT (CUS)
'\uf12c', //  0xD0 -> ONE EIGHTH BLOCK DOWN AND LEFT (CUS) '\uf12c', //  0xD0 -> ONE EIGHTH BLOCK DOWN AND LEFT (CUS)
'\u25cf', // ● 0xD1 -> BLACK CIRCLE '\u25cf', // ● 0xD1 -> BLACK CIRCLE
'\uf125', //  0xD2 -> BOX DRAWINGS LIGHT HORIZONTAL TWO QUARTERS DOWN (CUS) '\uf125', //  0xD2 -> BOX DRAWINGS LIGHT HORIZONTAL TWO EIGHTS DOWN (CUS)
'\u2665', // ♥ 0xD3 -> BLACK HEART SUIT '\u2665', // ♥ 0xD3 -> BLACK HEART SUIT
'\uf127', //  0xD4 -> BOX DRAWINGS LIGHT VERTICAL TWO QUARTERS LEFT (CUS) '\uf127', //  0xD4 -> BOX DRAWINGS LIGHT VERTICAL THREE EIGHTS LEFT (CUS)
'\u256d', // ╭ 0xD5 -> BOX DRAWINGS LIGHT ARC DOWN AND LEFT '\u256d', // ╭ 0xD5 -> BOX DRAWINGS LIGHT ARC DOWN AND LEFT
'\u2573', // 0xD6 -> BOX DRAWINGS LIGHT DIAGONAL CROSS '\u2573', // 0xD6 -> BOX DRAWINGS LIGHT DIAGONAL CROSS
'\u25cb', // ○ 0xD7 -> WHITE CIRCLE '\u25cb', // ○ 0xD7 -> WHITE CIRCLE
'\u2663', // ♣ 0xD8 -> BLACK CLUB SUIT '\u2663', // ♣ 0xD8 -> BLACK CLUB SUIT
'\uf129', //  0xD9 -> BOX DRAWINGS LIGHT VERTICAL TWO QUARTERS RIGHT (CUS) '\uf129', //  0xD9 -> BOX DRAWINGS LIGHT VERTICAL TWO EIGHTS RIGHT (CUS)
'\u2666', // ♦ 0xDA -> BLACK DIAMOND SUIT '\u2666', // ♦ 0xDA -> BLACK DIAMOND SUIT
'\u253c', // ┼ 0xDB -> BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL '\u253c', // ┼ 0xDB -> BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
'\uf12e', //  0xDC -> LEFT HALF BLOCK MEDIUM SHADE (CUS) '\uf12e', //  0xDC -> LEFT HALF BLOCK MEDIUM SHADE (CUS)
@ -529,7 +529,7 @@ object Petscii {
'\u03c0' // π 0xFF -> GREEK SMALL LETTER PI '\u03c0' // π 0xFF -> GREEK SMALL LETTER PI
) )
private val decodingScreencodeLowercase = arrayOf( private val decodingScreencodeLowercase = charArrayOf(
'@' , // @ 0x00 -> COMMERCIAL AT '@' , // @ 0x00 -> COMMERCIAL AT
'a' , // a 0x01 -> LATIN SMALL LETTER A 'a' , // a 0x01 -> LATIN SMALL LETTER A
'b' , // b 0x02 -> LATIN SMALL LETTER B 'b' , // b 0x02 -> LATIN SMALL LETTER B
@ -788,7 +788,7 @@ object Petscii {
'\ufffe' // 0xFF -> UNDEFINED '\ufffe' // 0xFF -> UNDEFINED
) )
private val decodingScreencodeUppercase = arrayOf( private val decodingScreencodeUppercase = charArrayOf(
'@' , // @ 0x00 -> COMMERCIAL AT '@' , // @ 0x00 -> COMMERCIAL AT
'A' , // A 0x01 -> LATIN CAPITAL LETTER A 'A' , // A 0x01 -> LATIN CAPITAL LETTER A
'B' , // B 0x02 -> LATIN CAPITAL LETTER B 'B' , // B 0x02 -> LATIN CAPITAL LETTER B
@ -855,13 +855,13 @@ object Petscii {
'?' , // ? 0x3F -> QUESTION MARK '?' , // ? 0x3F -> QUESTION MARK
'\u2500', // ─ 0x40 -> BOX DRAWINGS LIGHT HORIZONTAL '\u2500', // ─ 0x40 -> BOX DRAWINGS LIGHT HORIZONTAL
'\u2660', // ♠ 0x41 -> BLACK SPADE SUIT '\u2660', // ♠ 0x41 -> BLACK SPADE SUIT
'\u2502', // │ 0x42 -> BOX DRAWINGS LIGHT VERTICAL '\uf13c', // │ 0x42 -> BOX DRAWINGS LIGHT VERTICAL ONE EIGHTH LEFT (CUS)
'\u2500', // ─ 0x43 -> BOX DRAWINGS LIGHT HORIZONTAL '\uf13b', // ─ 0x43 -> BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)
'\uf122', //  0x44 -> BOX DRAWINGS LIGHT HORIZONTAL ONE QUARTER UP (CUS) '\uf122', //  0x44 -> BOX DRAWINGS LIGHT HORIZONTAL TWO EIGHTHS UP (CUS)
'\uf123', //  0x45 -> BOX DRAWINGS LIGHT HORIZONTAL TWO QUARTERS UP (CUS) '\uf123', //  0x45 -> BOX DRAWINGS LIGHT HORIZONTAL THREE EIGHTHS UP (CUS
'\uf124', //  0x46 -> BOX DRAWINGS LIGHT HORIZONTAL ONE QUARTER DOWN (CUS) '\uf124', //  0x46 -> BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH DOWN (CUS)
'\uf126', //  0x47 -> BOX DRAWINGS LIGHT VERTICAL ONE QUARTER LEFT (CUS) '\uf126', //  0x47 -> BOX DRAWINGS LIGHT VERTICAL TWO EIGHTHS LEFT (CUS)
'\uf128', //  0x48 -> BOX DRAWINGS LIGHT VERTICAL ONE QUARTER RIGHT (CUS) '\uf128', //  0x48 -> BOX DRAWINGS LIGHT VERTICAL ONE EIGHTH RIGHT (CUS)
'\u256e', // ╮ 0x49 -> BOX DRAWINGS LIGHT ARC DOWN AND LEFT '\u256e', // ╮ 0x49 -> BOX DRAWINGS LIGHT ARC DOWN AND LEFT
'\u2570', // ╰ 0x4A -> BOX DRAWINGS LIGHT ARC UP AND RIGHT '\u2570', // ╰ 0x4A -> BOX DRAWINGS LIGHT ARC UP AND RIGHT
'\u256f', // ╯ 0x4B -> BOX DRAWINGS LIGHT ARC UP AND LEFT '\u256f', // ╯ 0x4B -> BOX DRAWINGS LIGHT ARC UP AND LEFT
@ -871,14 +871,14 @@ object Petscii {
'\uf12b', //  0x4F -> ONE EIGHTH BLOCK DOWN AND RIGHT (CUS) '\uf12b', //  0x4F -> ONE EIGHTH BLOCK DOWN AND RIGHT (CUS)
'\uf12c', //  0x50 -> ONE EIGHTH BLOCK DOWN AND LEFT (CUS) '\uf12c', //  0x50 -> ONE EIGHTH BLOCK DOWN AND LEFT (CUS)
'\u25cf', // ● 0x51 -> BLACK CIRCLE '\u25cf', // ● 0x51 -> BLACK CIRCLE
'\uf125', //  0x52 -> BOX DRAWINGS LIGHT HORIZONTAL TWO QUARTERS DOWN (CUS) '\uf125', //  0x52 -> BOX DRAWINGS LIGHT HORIZONTAL TWO EIGHTS DOWN (CUS)
'\u2665', // ♥ 0x53 -> BLACK HEART SUIT '\u2665', // ♥ 0x53 -> BLACK HEART SUIT
'\uf127', //  0x54 -> BOX DRAWINGS LIGHT VERTICAL TWO QUARTERS LEFT (CUS) '\uf127', //  0x54 -> BOX DRAWINGS LIGHT VERTICAL THREE EIGHTS LEFT (CUS)
'\u256d', // ╭ 0x55 -> BOX DRAWINGS LIGHT ARC DOWN AND RIGHT '\u256d', // ╭ 0x55 -> BOX DRAWINGS LIGHT ARC DOWN AND RIGHT
'\u2573', // 0x56 -> BOX DRAWINGS LIGHT DIAGONAL CROSS '\u2573', // 0x56 -> BOX DRAWINGS LIGHT DIAGONAL CROSS
'\u25cb', // ○ 0x57 -> WHITE CIRCLE '\u25cb', // ○ 0x57 -> WHITE CIRCLE
'\u2663', // ♣ 0x58 -> BLACK CLUB SUIT '\u2663', // ♣ 0x58 -> BLACK CLUB SUIT
'\uf129', //  0x59 -> BOX DRAWINGS LIGHT VERTICAL TWO QUARTERS RIGHT (CUS) '\uf129', //  0x59 -> BOX DRAWINGS LIGHT VERTICAL TWO EIGHTS RIGHT (CUS)
'\u2666', // ♦ 0x5A -> BLACK DIAMOND SUIT '\u2666', // ♦ 0x5A -> BLACK DIAMOND SUIT
'\u253c', // ┼ 0x5B -> BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL '\u253c', // ┼ 0x5B -> BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
'\uf12e', //  0x5C -> LEFT HALF BLOCK MEDIUM SHADE (CUS) '\uf12e', //  0x5C -> LEFT HALF BLOCK MEDIUM SHADE (CUS)
@ -1098,12 +1098,9 @@ object Petscii {
fun decodePetscii(petscii: Iterable<Short>, lowercase: Boolean = false): String { fun decodePetscii(petscii: Iterable<Short>, lowercase: Boolean = false): String {
return petscii.map { return petscii.map {
val code = it.toInt() val code = it.toInt()
try { if(code<0 || code>= decodingPetsciiLowercase.size)
if(lowercase) decodingPetsciiLowercase[code] else decodingPetsciiUppercase[code] throw CharConversionException("petscii $code out of range 0..${decodingPetsciiLowercase.size-1}")
} catch(x: CharConversionException) { if(lowercase) decodingPetsciiLowercase[code] else decodingPetsciiUppercase[code]
// TODO this CharConversionException can never occur?? also clean up ICompilationTarget.decodeString?
if(lowercase) decodingPetsciiUppercase[code] else decodingPetsciiLowercase[code]
}
}.joinToString("") }.joinToString("")
} }
@ -1140,17 +1137,15 @@ object Petscii {
fun decodeScreencode(screencode: Iterable<Short>, lowercase: Boolean = false): String { fun decodeScreencode(screencode: Iterable<Short>, lowercase: Boolean = false): String {
return screencode.map { return screencode.map {
val code = it.toInt() val code = it.toInt()
try { if(code<0 || code>= decodingScreencodeLowercase.size)
if (lowercase) decodingScreencodeLowercase[code] else decodingScreencodeUppercase[code] throw CharConversionException("screencode $code out of range 0..${decodingScreencodeLowercase.size-1}")
} catch (x: CharConversionException) { if (lowercase) decodingScreencodeLowercase[code] else decodingScreencodeUppercase[code]
// TODO this CharConversionException can never occur?? also clean up ICompilationTarget.decodeString?
if (lowercase) decodingScreencodeUppercase[code] else decodingScreencodeLowercase[code]
}
}.joinToString("") }.joinToString("")
} }
fun petscii2scr(petscii_code: Short, inverseVideo: Boolean): Result<Short, CharConversionException> { fun petscii2scr(petscii_code: Short, inverseVideo: Boolean): Result<Short, CharConversionException> {
val code = when { val code = when {
petscii_code < 0 -> return Err(CharConversionException("petscii code out of range"))
petscii_code <= 0x1f -> petscii_code + 128 petscii_code <= 0x1f -> petscii_code + 128
petscii_code <= 0x3f -> petscii_code.toInt() petscii_code <= 0x3f -> petscii_code.toInt()
petscii_code <= 0x5f -> petscii_code - 64 petscii_code <= 0x5f -> petscii_code - 64
@ -1168,6 +1163,7 @@ object Petscii {
fun scr2petscii(screencode: Short): Result<Short, CharConversionException> { fun scr2petscii(screencode: Short): Result<Short, CharConversionException> {
val petscii = when { val petscii = when {
screencode < 0 -> return Err(CharConversionException("screencode out of range"))
screencode <= 0x1f -> screencode + 64 screencode <= 0x1f -> screencode + 64
screencode <= 0x3f -> screencode.toInt() screencode <= 0x3f -> screencode.toInt()
screencode <= 0x5d -> screencode +123 screencode <= 0x5d -> screencode +123

View File

@ -1,30 +1,32 @@
package prog8.compiler.target.cpu6502.codegen package prog8.compiler.target.cpu6502.codegen
import com.github.michaelbull.result.* import com.github.michaelbull.result.fold
import prog8.ast.* import prog8.ast.*
import prog8.ast.antlr.escape import prog8.ast.antlr.escape
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.* import prog8.compiler.target.AssemblyError
import prog8.compiler.functions.BuiltinFunctions import prog8.compiler.target.C64Target
import prog8.compiler.functions.FSignature import prog8.compiler.target.Cx16Target
import prog8.compiler.target.*
import prog8.compiler.target.cbm.AssemblyProgram import prog8.compiler.target.cbm.AssemblyProgram
import prog8.compiler.target.cbm.loadAsmIncludeFile
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignSource
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignTarget
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignment import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignment
import prog8.compiler.target.cpu6502.codegen.assignment.AssignmentAsmGen import prog8.compiler.target.cpu6502.codegen.assignment.AssignmentAsmGen
import prog8.optimizer.CallGraph import prog8.compiler.target.cpu6502.codegen.assignment.SourceStorageKind
import prog8.compilerinterface.*
import prog8.parser.SourceCode import prog8.parser.SourceCode
import java.nio.file.Path import java.nio.file.Path
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalDateTime import java.time.LocalDateTime
import java.util.* import java.util.*
import kotlin.io.path.Path import kotlin.io.path.Path
import kotlin.io.path.absolute
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
internal class AsmGen(private val program: Program, class AsmGen(private val program: Program,
val errors: IErrorReporter, val errors: IErrorReporter,
val zeropage: Zeropage, val zeropage: Zeropage,
val options: CompilationOptions, val options: CompilationOptions,
@ -47,9 +49,9 @@ internal class AsmGen(private val program: Program,
private val assignmentAsmGen = AssignmentAsmGen(program, this, expressionsAsmGen) private val assignmentAsmGen = AssignmentAsmGen(program, this, expressionsAsmGen)
private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this, assignmentAsmGen) private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this, assignmentAsmGen)
internal val loopEndLabels = ArrayDeque<String>() internal val loopEndLabels = ArrayDeque<String>()
private val blockLevelVarInits = mutableMapOf<Block, MutableSet<VarDecl>>()
internal val slabs = mutableMapOf<String, Int>() internal val slabs = mutableMapOf<String, Int>()
internal val removals = mutableListOf<Pair<Statement, INameScope>>() internal val removals = mutableListOf<Pair<Statement, IStatementContainer>>()
private val blockVariableInitializers = program.allBlocks.associateWith { it.statements.filterIsInstance<Assignment>() }
override fun compileToAssembly(): IAssemblyProgram { override fun compileToAssembly(): IAssemblyProgram {
assemblyLines.clear() assemblyLines.clear()
@ -61,8 +63,11 @@ internal class AsmGen(private val program: Program,
val allBlocks = program.allBlocks val allBlocks = program.allBlocks
if(allBlocks.first().name != "main") if(allBlocks.first().name != "main")
throw AssemblyError("first block should be 'main'") throw AssemblyError("first block should be 'main'")
for(b in program.allBlocks)
for(b in program.allBlocks) {
b.statements.removeAll(blockVariableInitializers.getValue(b)) // no longer output the initialization assignments as regular statements in the block!
block2asm(b) block2asm(b)
}
for(removal in removals.toList()) { for(removal in removals.toList()) {
removal.second.remove(removal.first) removal.second.remove(removal.first)
@ -219,26 +224,17 @@ internal class AsmGen(private val program: Program,
stmts.forEach { translate(it) } stmts.forEach { translate(it) }
subroutine.forEach { translateSubroutine(it as Subroutine) } subroutine.forEach { translateSubroutine(it as Subroutine) }
// if any global vars need to be initialized, generate a subroutine that does this // generate subroutine to initialize block-level variables
// it will be called from program init. val initializers = blockVariableInitializers.getValue(block)
if(block in blockLevelVarInits) { if(initializers.isNotEmpty()) {
out("prog8_init_vars\t.proc\n") out("prog8_init_vars\t.proc\n")
blockLevelVarInits.getValue(block).forEach { decl -> initializers.forEach { assign -> translate(assign) }
val scopedFullName = decl.makeScopedName(decl.name).split('.')
require(scopedFullName.first()==block.name)
assignInitialValueToVar(decl, scopedFullName.drop(1))
}
out(" rts\n .pend") out(" rts\n .pend")
} }
out(if("force_output" in block.options()) "\n\t.bend\n" else "\n\t.pend\n") out(if("force_output" in block.options()) "\n\t.bend\n" else "\n\t.pend\n")
} }
private fun assignInitialValueToVar(decl: VarDecl, variableName: List<String>) {
val asmName = asmVariableName(variableName)
assignmentAsmGen.assignExpressionToVariable(decl.value!!, asmName, decl.datatype, decl.definingSubroutine)
}
private var generatedLabelSequenceNumber: Int = 0 private var generatedLabelSequenceNumber: Int = 0
internal fun makeLabel(postfix: String): String { internal fun makeLabel(postfix: String): String {
@ -275,7 +271,6 @@ internal class AsmGen(private val program: Program,
&& variable.datatype != DataType.FLOAT && variable.datatype != DataType.FLOAT
&& options.zeropage != ZeropageType.DONTUSE) { && options.zeropage != ZeropageType.DONTUSE) {
try { try {
val errors = ErrorReporter()
val address = zeropage.allocate(fullName, variable.datatype, null, errors) val address = zeropage.allocate(fullName, variable.datatype, null, errors)
errors.report() errors.report()
out("${variable.name} = $address\t; auto zp ${variable.datatype}") out("${variable.name} = $address\t; auto zp ${variable.datatype}")
@ -500,7 +495,7 @@ internal class AsmGen(private val program: Program,
return newName return newName
} }
internal fun asmSymbolName(identifier: IdentifierReference): String { fun asmSymbolName(identifier: IdentifierReference): String {
if(identifier.nameInSource.size==2 && identifier.nameInSource[0]=="prog8_slabs") if(identifier.nameInSource.size==2 && identifier.nameInSource[0]=="prog8_slabs")
return identifier.nameInSource.joinToString(".") return identifier.nameInSource.joinToString(".")
@ -531,7 +526,7 @@ internal class AsmGen(private val program: Program,
} }
} }
internal fun asmVariableName(identifier: IdentifierReference) = fun asmVariableName(identifier: IdentifierReference) =
fixNameSymbols(identifier.nameInSource.joinToString(".")) fixNameSymbols(identifier.nameInSource.joinToString("."))
private fun getScopedSymbolNameForTarget(actualName: String, target: Statement): MutableList<String> { private fun getScopedSymbolNameForTarget(actualName: String, target: Statement): MutableList<String> {
@ -837,12 +832,10 @@ internal class AsmGen(private val program: Program,
} }
} }
@Deprecated("avoid calling this as it generates slow evalstack based code")
internal fun translateExpression(expression: Expression) = internal fun translateExpression(expression: Expression) =
expressionsAsmGen.translateExpression(expression) expressionsAsmGen.translateExpression(expression)
internal fun translateExpression(indexer: ArrayIndex) =
expressionsAsmGen.translateExpression(indexer)
internal fun translateBuiltinFunctionCallExpression(functionCall: FunctionCall, signature: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?) = internal fun translateBuiltinFunctionCallExpression(functionCall: FunctionCall, signature: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?) =
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature, resultToStack, resultRegister) builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature, resultToStack, resultRegister)
@ -858,14 +851,53 @@ internal class AsmGen(private val program: Program,
internal fun translateNormalAssignment(assign: AsmAssignment) = internal fun translateNormalAssignment(assign: AsmAssignment) =
assignmentAsmGen.translateNormalAssignment(assign) assignmentAsmGen.translateNormalAssignment(assign)
internal fun assignExpressionToRegister(expr: Expression, register: RegisterOrPair) = internal fun assignExpressionToRegister(expr: Expression, register: RegisterOrPair, signed: Boolean=false) =
assignmentAsmGen.assignExpressionToRegister(expr, register) assignmentAsmGen.assignExpressionToRegister(expr, register, signed)
internal fun assignExpressionToVariable(expr: Expression, asmVarName: String, dt: DataType, scope: Subroutine?) = internal fun assignExpressionToVariable(expr: Expression, asmVarName: String, dt: DataType, scope: Subroutine?) =
assignmentAsmGen.assignExpressionToVariable(expr, asmVarName, dt, scope) assignmentAsmGen.assignExpressionToVariable(expr, asmVarName, dt, scope)
internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair) = internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair, signed: Boolean=false) =
assignmentAsmGen.assignVariableToRegister(asmVarName, register) assignmentAsmGen.assignVariableToRegister(asmVarName, register, signed)
internal fun assignRegister(reg: RegisterOrPair, target: AsmAssignTarget) {
when(reg) {
RegisterOrPair.A,
RegisterOrPair.X,
RegisterOrPair.Y -> assignmentAsmGen.assignRegisterByte(target, reg.asCpuRegister())
RegisterOrPair.AX,
RegisterOrPair.AY,
RegisterOrPair.XY,
in Cx16VirtualRegisters -> assignmentAsmGen.assignRegisterpairWord(target, reg)
RegisterOrPair.FAC1 -> assignmentAsmGen.assignFAC1float(target)
RegisterOrPair.FAC2 -> TODO("no support yet to assign FAC2 directly to something")
else -> throw AssemblyError("invalid register")
}
}
internal fun assignExpressionTo(value: Expression, target: AsmAssignTarget) {
// don't use asmgen.translateExpression() to avoid evalstack
when (target.datatype) {
in ByteDatatypes -> {
assignExpressionToRegister(value, RegisterOrPair.A)
assignRegister(RegisterOrPair.A, target)
}
in WordDatatypes, in PassByReferenceDatatypes -> {
assignExpressionToRegister(value, RegisterOrPair.AY)
translateNormalAssignment(
AsmAssignment(
AsmAssignSource(SourceStorageKind.REGISTER, program, this, target.datatype, register=RegisterOrPair.AY),
target, false, program.memsizer, value.position
)
)
}
DataType.FLOAT -> {
assignExpressionToRegister(value, RegisterOrPair.FAC1)
assignRegister(RegisterOrPair.FAC1, target)
}
else -> throw AssemblyError("weird dt ${target.datatype}")
}
}
private fun translateSubroutine(sub: Subroutine) { private fun translateSubroutine(sub: Subroutine) {
@ -909,9 +941,9 @@ internal class AsmGen(private val program: Program,
if(sub.name=="start" && sub.definingBlock.name=="main") { if(sub.name=="start" && sub.definingBlock.name=="main") {
out("; program startup initialization") out("; program startup initialization")
out(" cld") out(" cld")
program.allBlocks.forEach { blockVariableInitializers.forEach {
if(it.statements.filterIsInstance<VarDecl>().any { vd->vd.value!=null && vd.type==VarDeclType.VAR && vd.datatype in NumericDatatypes}) if(it.value.isNotEmpty())
out(" jsr ${it.name}.prog8_init_vars") out(" jsr ${it.key.name}.prog8_init_vars")
} }
out(""" out("""
tsx tsx
@ -984,15 +1016,14 @@ internal class AsmGen(private val program: Program,
} }
private fun translate(stmt: IfStatement) { private fun translate(stmt: IfStatement) {
checkBooleanExpression(stmt.condition) // we require the condition to be of the form 'x <comparison> <value>' requireComparisonExpression(stmt.condition) // IfStatement: condition must be of form 'x <comparison> <value>'
val booleanCondition = stmt.condition as BinaryExpression val booleanCondition = stmt.condition as BinaryExpression
// DISABLED FOR NOW: // DISABLED FOR NOW:
// if(!booleanCondition.left.isSimple || !booleanCondition.right.isSimple) // if(!booleanCondition.left.isSimple || !booleanCondition.right.isSimple)
// throw AssemblyError("both operands for if comparison expression should have been simplified") // throw AssemblyError("both operands for if comparison expression should have been simplified")
if (stmt.elsepart.containsNoCodeNorVars) { if (stmt.elsepart.isEmpty()) {
// empty else
val endLabel = makeLabel("if_end") val endLabel = makeLabel("if_end")
expressionsAsmGen.translateComparisonExpressionWithJumpIfFalse(booleanCondition, endLabel) expressionsAsmGen.translateComparisonExpressionWithJumpIfFalse(booleanCondition, endLabel)
translate(stmt.truepart) translate(stmt.truepart)
@ -1011,9 +1042,9 @@ internal class AsmGen(private val program: Program,
} }
} }
private fun checkBooleanExpression(condition: Expression) { private fun requireComparisonExpression(condition: Expression) {
if(condition !is BinaryExpression || condition.operator !in comparisonOperators) if(condition !is BinaryExpression || condition.operator !in comparisonOperators)
throw AssemblyError("expected boolean expression $condition") throw AssemblyError("expected boolean comparison expression $condition")
} }
private fun translate(stmt: RepeatLoop) { private fun translate(stmt: RepeatLoop) {
@ -1164,7 +1195,7 @@ $repeatLabel lda $counterVar
} }
private fun translate(stmt: WhileLoop) { private fun translate(stmt: WhileLoop) {
checkBooleanExpression(stmt.condition) // we require the condition to be of the form 'x <comparison> <value>' requireComparisonExpression(stmt.condition) // WhileLoop: condition must be of form 'x <comparison> <value>'
val booleanCondition = stmt.condition as BinaryExpression val booleanCondition = stmt.condition as BinaryExpression
val whileLabel = makeLabel("while") val whileLabel = makeLabel("while")
val endLabel = makeLabel("whileend") val endLabel = makeLabel("whileend")
@ -1178,7 +1209,7 @@ $repeatLabel lda $counterVar
} }
private fun translate(stmt: UntilLoop) { private fun translate(stmt: UntilLoop) {
checkBooleanExpression(stmt.condition) // we require the condition to be of the form 'x <comparison> <value>' requireComparisonExpression(stmt.condition) // UntilLoop: condition must be of form 'x <comparison> <value>'
val booleanCondition = stmt.condition as BinaryExpression val booleanCondition = stmt.condition as BinaryExpression
val repeatLabel = makeLabel("repeat") val repeatLabel = makeLabel("repeat")
val endLabel = makeLabel("repeatend") val endLabel = makeLabel("repeatend")
@ -1245,7 +1276,7 @@ $repeatLabel lda $counterVar
} }
private fun translate(stmt: BranchStatement) { private fun translate(stmt: BranchStatement) {
if(stmt.truepart.containsNoCodeNorVars && stmt.elsepart.containsCodeOrVars) if(stmt.truepart.isEmpty() && stmt.elsepart.isNotEmpty())
throw AssemblyError("only else part contains code, shoud have been switched already") throw AssemblyError("only else part contains code, shoud have been switched already")
val jump = stmt.truepart.statements.first() as? Jump val jump = stmt.truepart.statements.first() as? Jump
@ -1257,7 +1288,7 @@ $repeatLabel lda $counterVar
} else { } else {
val truePartIsJustBreak = stmt.truepart.statements.firstOrNull() is Break val truePartIsJustBreak = stmt.truepart.statements.firstOrNull() is Break
val elsePartIsJustBreak = stmt.elsepart.statements.firstOrNull() is Break val elsePartIsJustBreak = stmt.elsepart.statements.firstOrNull() is Break
if(stmt.elsepart.containsNoCodeNorVars) { if(stmt.elsepart.isEmpty()) {
if(truePartIsJustBreak) { if(truePartIsJustBreak) {
// branch with just a break (jump out of loop) // branch with just a break (jump out of loop)
val instruction = branchInstruction(stmt.condition, false) val instruction = branchInstruction(stmt.condition, false)
@ -1297,34 +1328,15 @@ $repeatLabel lda $counterVar
} }
} }
private fun translate(stmt: VarDecl) { private fun translate(decl: VarDecl) {
if(stmt.value!=null && stmt.type==VarDeclType.VAR && stmt.datatype in NumericDatatypes) { if(decl.type==VarDeclType.VAR && decl.value != null && decl.datatype in NumericDatatypes)
// generate an assignment statement to (re)initialize the variable's value. throw AssemblyError("vardecls for variables, with initial numerical value, should have been rewritten as plain vardecl + assignment $decl")
// if the vardecl is not in a subroutine however, we have to initialize it globally. // at this time, nothing has to be done here anymore code-wise
if(stmt.definingSubroutine ==null) {
val block = stmt.definingBlock
var inits = blockLevelVarInits[block]
if(inits==null) {
inits = mutableSetOf()
blockLevelVarInits[block] = inits
}
inits.add(stmt)
} else {
val next = (stmt.parent as INameScope).nextSibling(stmt)
if (next !is ForLoop || next.loopVar.nameInSource.single() != stmt.name) {
assignInitialValueToVar(stmt, listOf(stmt.name))
}
}
}
} }
/**
* TODO: %asminclude and %asmbinary should be done earlier than code gen (-> put content into AST) ... (describe why?)
*/
private fun translate(stmt: Directive) { private fun translate(stmt: Directive) {
when(stmt.directive) { when(stmt.directive) {
"%asminclude" -> { "%asminclude" -> {
// TODO: handle %asminclude with SourceCode
val includedName = stmt.args[0].str!! val includedName = stmt.args[0].str!!
if(stmt.definingModule.source is SourceCode.Generated) if(stmt.definingModule.source is SourceCode.Generated)
TODO("%asminclude inside non-library, non-filesystem module") TODO("%asminclude inside non-library, non-filesystem module")
@ -1387,7 +1399,7 @@ $label nop""")
else -> { else -> {
// all else take its address and assign that also to AY register pair // all else take its address and assign that also to AY register pair
val addrofValue = AddressOf(returnvalue as IdentifierReference, returnvalue.position) val addrofValue = AddressOf(returnvalue as IdentifierReference, returnvalue.position)
assignmentAsmGen.assignExpressionToRegister(addrofValue, returnReg.registerOrPair!!) assignmentAsmGen.assignExpressionToRegister(addrofValue, returnReg.registerOrPair!!, false)
} }
} }
} }

View File

@ -180,7 +180,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Modification> { private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can OFTEN be eliminated // sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can OFTEN be eliminated
// TODO this is not true if X is not a regular RAM memory address (but instead mapped I/O or ROM) // TODO this is not true if X is not a regular RAM memory address (but instead mapped I/O or ROM) but how does this code know?
val mods = mutableListOf<Modification>() val mods = mutableListOf<Modification>()
for (pair in linesByFour) { for (pair in linesByFour) {
val first = pair[0].value.trimStart() val first = pair[0].value.trimStart()

View File

@ -10,12 +10,12 @@ import prog8.ast.statements.DirectMemoryWrite
import prog8.ast.statements.FunctionCallStatement import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Subroutine import prog8.ast.statements.Subroutine
import prog8.ast.toHex import prog8.ast.toHex
import prog8.compiler.AssemblyError import prog8.compiler.target.AssemblyError
import prog8.compiler.functions.FSignature
import prog8.compiler.target.CpuType
import prog8.compiler.target.Cx16Target import prog8.compiler.target.Cx16Target
import prog8.compiler.target.cpu6502.codegen.assignment.* import prog8.compiler.target.cpu6502.codegen.assignment.*
import prog8.compiler.target.subroutineFloatEvalResultVar2 import prog8.compilerinterface.CpuType
import prog8.compilerinterface.FSignature
import prog8.compilerinterface.subroutineFloatEvalResultVar2
internal class BuiltinFunctionsAsmGen(private val program: Program, private val asmgen: AsmGen, private val assignAsmGen: AssignmentAsmGen) { internal class BuiltinFunctionsAsmGen(private val program: Program, private val asmgen: AsmGen, private val assignAsmGen: AssignmentAsmGen) {
@ -261,7 +261,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
if(resultToStack) if(resultToStack)
AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, DataType.UWORD, null) AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, DataType.UWORD, null)
else else
AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, null, program, asmgen) AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, null, program, asmgen)
val assign = AsmAssignment(src, target, false, program.memsizer, fcall.position) val assign = AsmAssignment(src, target, false, program.memsizer, fcall.position)
asmgen.translateNormalAssignment(assign) asmgen.translateNormalAssignment(assign)
asmgen.slabs[name] = size asmgen.slabs[name] = size
@ -273,7 +273,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" jsr prog8_lib.func_sqrt16_stack") asmgen.out(" jsr prog8_lib.func_sqrt16_stack")
else { else {
asmgen.out(" jsr prog8_lib.func_sqrt16_into_A") asmgen.out(" jsr prog8_lib.func_sqrt16_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A) assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
} }
} }
@ -285,11 +285,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
when(func.name) { when(func.name) {
"sin8", "sin8u", "cos8", "cos8u" -> { "sin8", "sin8u", "cos8", "cos8u" -> {
asmgen.out(" jsr prog8_lib.func_${func.name}_into_A") asmgen.out(" jsr prog8_lib.func_${func.name}_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A) assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
} }
"sin16", "sin16u", "cos16", "cos16u" -> { "sin16", "sin16u", "cos16", "cos16u" -> {
asmgen.out(" jsr prog8_lib.func_${func.name}_into_AY") asmgen.out(" jsr prog8_lib.func_${func.name}_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY) assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
} }
} }
} }
@ -578,7 +578,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" jsr floats.func_${func.name}_stack") asmgen.out(" jsr floats.func_${func.name}_stack")
else { else {
asmgen.out(" jsr floats.func_${func.name}_fac1") asmgen.out(" jsr floats.func_${func.name}_fac1")
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, scope, program, asmgen)) assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, scope, program, asmgen))
} }
} }
@ -603,7 +603,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
DataType.FLOAT -> asmgen.out(" jsr floats.func_sign_f_into_A") DataType.FLOAT -> asmgen.out(" jsr floats.func_sign_f_into_A")
else -> throw AssemblyError("weird type $dt") else -> throw AssemblyError("weird type $dt")
} }
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A) assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
} }
} }
@ -624,7 +624,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_into_A") DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_into_A")
else -> throw AssemblyError("weird type $dt") else -> throw AssemblyError("weird type $dt")
} }
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A) assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
} }
} }
@ -644,23 +644,23 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
when (dt.getOr(DataType.UNDEFINED)) { when (dt.getOr(DataType.UNDEFINED)) {
DataType.ARRAY_UB, DataType.STR -> { DataType.ARRAY_UB, DataType.STR -> {
asmgen.out(" jsr prog8_lib.func_${function.name}_ub_into_A") asmgen.out(" jsr prog8_lib.func_${function.name}_ub_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A) assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
} }
DataType.ARRAY_B -> { DataType.ARRAY_B -> {
asmgen.out(" jsr prog8_lib.func_${function.name}_b_into_A") asmgen.out(" jsr prog8_lib.func_${function.name}_b_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A) assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
} }
DataType.ARRAY_UW -> { DataType.ARRAY_UW -> {
asmgen.out(" jsr prog8_lib.func_${function.name}_uw_into_AY") asmgen.out(" jsr prog8_lib.func_${function.name}_uw_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY) assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
} }
DataType.ARRAY_W -> { DataType.ARRAY_W -> {
asmgen.out(" jsr prog8_lib.func_${function.name}_w_into_AY") asmgen.out(" jsr prog8_lib.func_${function.name}_w_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY) assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
} }
DataType.ARRAY_F -> { DataType.ARRAY_F -> {
asmgen.out(" jsr floats.func_${function.name}_f_fac1") asmgen.out(" jsr floats.func_${function.name}_f_fac1")
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, scope, program, asmgen)) assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, scope, program, asmgen))
} }
else -> throw AssemblyError("weird type $dt") else -> throw AssemblyError("weird type $dt")
} }
@ -683,23 +683,23 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
when (dt.getOr(DataType.UNDEFINED)) { when (dt.getOr(DataType.UNDEFINED)) {
DataType.ARRAY_UB, DataType.STR -> { DataType.ARRAY_UB, DataType.STR -> {
asmgen.out(" jsr prog8_lib.func_sum_ub_into_AY") asmgen.out(" jsr prog8_lib.func_sum_ub_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY) assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
} }
DataType.ARRAY_B -> { DataType.ARRAY_B -> {
asmgen.out(" jsr prog8_lib.func_sum_b_into_AY") asmgen.out(" jsr prog8_lib.func_sum_b_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY) assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
} }
DataType.ARRAY_UW -> { DataType.ARRAY_UW -> {
asmgen.out(" jsr prog8_lib.func_sum_uw_into_AY") asmgen.out(" jsr prog8_lib.func_sum_uw_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY) assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
} }
DataType.ARRAY_W -> { DataType.ARRAY_W -> {
asmgen.out(" jsr prog8_lib.func_sum_w_into_AY") asmgen.out(" jsr prog8_lib.func_sum_w_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY) assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
} }
DataType.ARRAY_F -> { DataType.ARRAY_F -> {
asmgen.out(" jsr floats.func_sum_f_fac1") asmgen.out(" jsr floats.func_sum_f_fac1")
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, scope, program, asmgen)) assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, scope, program, asmgen))
} }
else -> throw AssemblyError("weird type $dt") else -> throw AssemblyError("weird type $dt")
} }
@ -785,7 +785,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
&& (firstOffset is NumericLiteralValue || firstOffset is IdentifierReference || firstOffset is TypecastExpression) && (firstOffset is NumericLiteralValue || firstOffset is IdentifierReference || firstOffset is TypecastExpression)
&& (secondOffset is NumericLiteralValue || secondOffset is IdentifierReference || secondOffset is TypecastExpression) && (secondOffset is NumericLiteralValue || secondOffset is IdentifierReference || secondOffset is TypecastExpression)
) { ) {
val pointerVar = firstExpr.left as IdentifierReference
if(firstOffset is NumericLiteralValue && secondOffset is NumericLiteralValue) { if(firstOffset is NumericLiteralValue && secondOffset is NumericLiteralValue) {
if(firstOffset!=secondOffset) { if(firstOffset!=secondOffset) {
swapArrayValues( swapArrayValues(
@ -876,21 +875,23 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.translateNormalAssignment(assignSecond) asmgen.translateNormalAssignment(assignSecond)
} }
DataType.FLOAT -> { DataType.FLOAT -> {
// via evaluation stack // via temp variable and FAC1
asmgen.translateExpression(first) asmgen.assignExpressionTo(first, AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.FLOAT, first.definingSubroutine, "floats.tempvar_swap_float"))
asmgen.translateExpression(second) asmgen.assignExpressionTo(second, AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.FLOAT, null, register=RegisterOrPair.FAC1))
val assignFirst = AsmAssignment( asmgen.translateNormalAssignment(
AsmAssignSource(SourceStorageKind.STACK, program, asmgen, DataType.FLOAT), AsmAssignment(
AsmAssignSource(SourceStorageKind.REGISTER, program, asmgen, datatype, register = RegisterOrPair.FAC1),
targetFromExpr(first, datatype), targetFromExpr(first, datatype),
false, program.memsizer, first.position false, program.memsizer, first.position
)
) )
val assignSecond = AsmAssignment( asmgen.translateNormalAssignment(
AsmAssignSource(SourceStorageKind.STACK, program, asmgen, DataType.FLOAT), AsmAssignment(
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, datatype, "floats.tempvar_swap_float"),
targetFromExpr(second, datatype), targetFromExpr(second, datatype),
false, program.memsizer, second.position false, program.memsizer, second.position
)
) )
asmgen.translateNormalAssignment(assignFirst)
asmgen.translateNormalAssignment(assignSecond)
} }
else -> throw AssemblyError("weird swap dt") else -> throw AssemblyError("weird swap dt")
} }
@ -1140,15 +1141,15 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
when (dt) { when (dt) {
in ByteDatatypes -> { in ByteDatatypes -> {
asmgen.out(" jsr prog8_lib.abs_b_into_A") asmgen.out(" jsr prog8_lib.abs_b_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A) assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
} }
in WordDatatypes -> { in WordDatatypes -> {
asmgen.out(" jsr prog8_lib.abs_w_into_AY") asmgen.out(" jsr prog8_lib.abs_w_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY) assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
} }
DataType.FLOAT -> { DataType.FLOAT -> {
asmgen.out(" jsr floats.abs_f_fac1") asmgen.out(" jsr floats.abs_f_fac1")
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, scope, program, asmgen)) assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, scope, program, asmgen))
} }
else -> throw AssemblyError("weird type") else -> throw AssemblyError("weird type")
} }
@ -1162,7 +1163,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" jsr prog8_lib.func_rnd_stack") asmgen.out(" jsr prog8_lib.func_rnd_stack")
else { else {
asmgen.out(" jsr math.randbyte") asmgen.out(" jsr math.randbyte")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A) assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
} }
} }
"rndw" -> { "rndw" -> {
@ -1170,7 +1171,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" jsr prog8_lib.func_rndw_stack") asmgen.out(" jsr prog8_lib.func_rndw_stack")
else { else {
asmgen.out(" jsr math.randword") asmgen.out(" jsr math.randword")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY) assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
} }
} }
else -> throw AssemblyError("wrong func") else -> throw AssemblyError("wrong func")
@ -1533,7 +1534,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
AsmAssignSource.fromAstSource(value, program, asmgen) AsmAssignSource.fromAstSource(value, program, asmgen)
} }
} }
val tgt = AsmAssignTarget.fromRegisters(conv.reg, null, program, asmgen) val tgt = AsmAssignTarget.fromRegisters(conv.reg!!, false, null, program, asmgen)
val assign = AsmAssignment(src, tgt, false, program.memsizer, value.position) val assign = AsmAssignment(src, tgt, false, program.memsizer, value.position)
asmgen.translateNormalAssignment(assign) asmgen.translateNormalAssignment(assign)
} }

View File

@ -3,19 +3,27 @@ package prog8.compiler.target.cpu6502.codegen
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.ArrayIndex
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
import prog8.ast.statements.Subroutine import prog8.ast.statements.Subroutine
import prog8.ast.toHex import prog8.ast.toHex
import prog8.compiler.AssemblyError import prog8.compiler.target.AssemblyError
import prog8.compiler.functions.BuiltinFunctions import prog8.compilerinterface.BuiltinFunctions
import prog8.compiler.target.CpuType import prog8.compilerinterface.CpuType
import prog8.compiler.target.subroutineFloatEvalResultVar1 import prog8.compilerinterface.subroutineFloatEvalResultVar1
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
internal class ExpressionsAsmGen(private val program: Program, private val asmgen: AsmGen) { internal class ExpressionsAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translateExpression(expression: Expression) { @Deprecated("avoid calling this as it generates slow evalstack based code")
internal fun translateExpression(expression:Expression) {
if (this.asmgen.options.slowCodegenWarnings) {
asmgen.errors.warn("slow stack evaluation used for expression $expression", expression.position)
}
translateExpressionInternal(expression)
}
private fun translateExpressionInternal(expression: Expression) {
when(expression) { when(expression) {
is PrefixExpression -> translateExpression(expression) is PrefixExpression -> translateExpression(expression)
is BinaryExpression -> translateExpression(expression) is BinaryExpression -> translateExpression(expression)
@ -28,17 +36,21 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
is FunctionCall -> translateFunctionCallResultOntoStack(expression) is FunctionCall -> translateFunctionCallResultOntoStack(expression)
is ArrayLiteralValue, is StringLiteralValue -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable") is ArrayLiteralValue, is StringLiteralValue -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable")
is RangeExpr -> throw AssemblyError("range expression should have been changed into array values") is RangeExpr -> throw AssemblyError("range expression should have been changed into array values")
is CharLiteral -> throw AssemblyError("charliteral should have been replaced by ubyte using certain encoding")
} }
} }
internal fun translateComparisonExpressionWithJumpIfFalse(expr: BinaryExpression, jumpIfFalseLabel: String) { internal fun translateComparisonExpressionWithJumpIfFalse(expr: BinaryExpression, jumpIfFalseLabel: String) {
// This is a helper routine called from while, do-util, and if expressions to generate optimized conditional branching code. // This is a helper routine called from while, do-util, and if expressions to generate optimized conditional branching code.
// First, if it is of the form: <constvalue> <comparison> X , then flip the expression so the constant is always the right operand. // First, if it is of the form: <constvalue> <comparison> X , then flip the expression so the constant is always the right operand.
var left = expr.left var left = expr.left
var right = expr.right var right = expr.right
var operator = expr.operator var operator = expr.operator
var leftConstVal = left.constValue(program) var leftConstVal = left.constValue(program)
var rightConstVal = right.constValue(program) var rightConstVal = right.constValue(program)
// make sure the constant value is on the right of the comparison expression
if(leftConstVal!=null) { if(leftConstVal!=null) {
val tmp = left val tmp = left
left = right left = right
@ -54,32 +66,113 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
} }
} }
val idt = left.inferType(program) if (rightConstVal!=null && rightConstVal.number.toDouble() == 0.0)
if(!idt.isKnown) jumpIfZeroOrNot(left, operator, jumpIfFalseLabel)
throw AssemblyError("unknown dt") else
val dt = idt.getOr(DataType.UNDEFINED) jumpIfComparison(left, operator, right, jumpIfFalseLabel, leftConstVal, rightConstVal)
}
private fun jumpIfZeroOrNot(
left: Expression,
operator: String,
jumpIfFalseLabel: String
) {
when(val dt = left.inferType(program).getOr(DataType.UNDEFINED)) {
DataType.UBYTE, DataType.UWORD -> {
if(operator=="<") {
asmgen.out(" jmp $jumpIfFalseLabel")
return
} else if(operator==">=") {
return
}
if(dt==DataType.UBYTE) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.A, false)
if (left is FunctionCall && !left.isSimple)
asmgen.out(" cmp #0")
} else {
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY, false)
asmgen.out(" sty P8ZP_SCRATCH_B1 | ora P8ZP_SCRATCH_B1")
}
when (operator) {
"==" -> asmgen.out(" bne $jumpIfFalseLabel")
"!=" -> asmgen.out(" beq $jumpIfFalseLabel")
">" -> asmgen.out(" beq $jumpIfFalseLabel")
"<=" -> asmgen.out(" bne $jumpIfFalseLabel")
else -> throw AssemblyError("invalid comparison operator $operator")
}
}
DataType.BYTE -> {
asmgen.assignExpressionToRegister(left, RegisterOrPair.A, true)
if (left is FunctionCall && !left.isSimple)
asmgen.out(" cmp #0")
when (operator) {
"==" -> asmgen.out(" bne $jumpIfFalseLabel")
"!=" -> asmgen.out(" beq $jumpIfFalseLabel")
">" -> asmgen.out(" beq $jumpIfFalseLabel | bmi $jumpIfFalseLabel")
"<" -> asmgen.out(" bpl $jumpIfFalseLabel")
">=" -> asmgen.out(" bmi $jumpIfFalseLabel")
"<=" -> asmgen.out("""
beq +
bpl $jumpIfFalseLabel
+ """)
else -> throw AssemblyError("invalid comparison operator $operator")
}
}
DataType.WORD -> {
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY, true)
when (operator) {
"==" -> asmgen.out(" bne $jumpIfFalseLabel | cpy #0 | bne $jumpIfFalseLabel")
"!=" -> asmgen.out(" sty P8ZP_SCRATCH_B1 | ora P8ZP_SCRATCH_B1 | beq $jumpIfFalseLabel")
">" -> asmgen.out("""
cpy #0
bmi $jumpIfFalseLabel
bne +
cmp #0
beq $jumpIfFalseLabel
+ """)
"<" -> asmgen.out(" cpy #0 | bpl $jumpIfFalseLabel")
">=" -> asmgen.out(" cpy #0 | bmi $jumpIfFalseLabel")
"<=" -> asmgen.out("""
cpy #0
bmi +
bne $jumpIfFalseLabel
cmp #0
bne $jumpIfFalseLabel
+ """)
else -> throw AssemblyError("invalid comparison operator $operator")
}
}
DataType.FLOAT -> {
asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1)
asmgen.out(" jsr floats.SIGN") // SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
when (operator) {
"==" -> asmgen.out(" bne $jumpIfFalseLabel")
"!=" -> asmgen.out(" beq $jumpIfFalseLabel")
">" -> asmgen.out(" bmi $jumpIfFalseLabel | beq $jumpIfFalseLabel")
"<" -> asmgen.out(" bpl $jumpIfFalseLabel")
">=" -> asmgen.out(" bmi $jumpIfFalseLabel")
"<=" -> asmgen.out(" cmp #1 | beq $jumpIfFalseLabel")
else -> throw AssemblyError("invalid comparison operator $operator")
}
}
else -> {
throw AssemblyError("invalid dt")
}
}
}
private fun jumpIfComparison(
left: Expression,
operator: String,
right: Expression,
jumpIfFalseLabel: String,
leftConstVal: NumericLiteralValue?,
rightConstVal: NumericLiteralValue?
) {
val dt = left.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
when (operator) { when (operator) {
"==" -> { "==" -> {
// if the left operand is an expression, and the right is 0, we can just evaluate that expression,
// and use the result value directly to determine the boolean result. Shortcut only for integers.
if(rightConstVal?.number?.toDouble() == 0.0) {
if(dt in ByteDatatypes) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.A)
if(left is FunctionCall && !left.isSimple)
asmgen.out(" cmp #0")
asmgen.out(" bne $jumpIfFalseLabel")
return
}
else if(dt in WordDatatypes) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
asmgen.out("""
sty P8ZP_SCRATCH_B1
ora P8ZP_SCRATCH_B1
bne $jumpIfFalseLabel""")
return
}
}
when (dt) { when (dt) {
in ByteDatatypes -> translateByteEqualsJump(left, right, leftConstVal, rightConstVal, jumpIfFalseLabel) in ByteDatatypes -> translateByteEqualsJump(left, right, leftConstVal, rightConstVal, jumpIfFalseLabel)
in WordDatatypes -> translateWordEqualsJump(left, right, leftConstVal, rightConstVal, jumpIfFalseLabel) in WordDatatypes -> translateWordEqualsJump(left, right, leftConstVal, rightConstVal, jumpIfFalseLabel)
@ -89,26 +182,6 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
} }
} }
"!=" -> { "!=" -> {
// if the left operand is an expression, and the right is 0, we can just evaluate that expression,
// and use the result value directly to determine the boolean result. Shortcut only for integers.
if(rightConstVal?.number?.toDouble() == 0.0) {
if(dt in ByteDatatypes) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.A)
if(left is FunctionCall && !left.isSimple)
asmgen.out(" cmp #0")
asmgen.out(" beq $jumpIfFalseLabel")
return
}
else if(dt in WordDatatypes) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
asmgen.out("""
sty P8ZP_SCRATCH_B1
ora P8ZP_SCRATCH_B1
beq $jumpIfFalseLabel""")
return
}
}
when (dt) { when (dt) {
in ByteDatatypes -> translateByteNotEqualsJump(left, right, leftConstVal, rightConstVal, jumpIfFalseLabel) in ByteDatatypes -> translateByteNotEqualsJump(left, right, leftConstVal, rightConstVal, jumpIfFalseLabel)
in WordDatatypes -> translateWordNotEqualsJump(left, right, leftConstVal, rightConstVal, jumpIfFalseLabel) in WordDatatypes -> translateWordNotEqualsJump(left, right, leftConstVal, rightConstVal, jumpIfFalseLabel)
@ -1625,7 +1698,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
} }
private fun translateExpression(typecast: TypecastExpression) { private fun translateExpression(typecast: TypecastExpression) {
translateExpression(typecast.expression) translateExpressionInternal(typecast.expression)
when(typecast.expression.inferType(program).getOr(DataType.UNDEFINED)) { when(typecast.expression.inferType(program).getOr(DataType.UNDEFINED)) {
DataType.UBYTE -> { DataType.UBYTE -> {
when(typecast.type) { when(typecast.type) {
@ -1775,7 +1848,8 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
} }
private fun translateExpression(expr: BinaryExpression) { private fun translateExpression(expr: BinaryExpression) {
// TODO needs to use optimized assembly generation like the assignment instructions. But avoid code duplication.... rewrite all expressions into assignment form? // Uses evalstack to evaluate the given expression.
// TODO we're slowly reducing the number of places where this is called and instead replace that by more efficient assignment-form code (using temp var or register for instance).
val leftIDt = expr.left.inferType(program) val leftIDt = expr.left.inferType(program)
val rightIDt = expr.right.inferType(program) val rightIDt = expr.right.inferType(program)
if(!leftIDt.isKnown || !rightIDt.isKnown) if(!leftIDt.isKnown || !rightIDt.isKnown)
@ -1784,14 +1858,13 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
val leftDt = leftIDt.getOr(DataType.UNDEFINED) val leftDt = leftIDt.getOr(DataType.UNDEFINED)
val rightDt = rightIDt.getOr(DataType.UNDEFINED) val rightDt = rightIDt.getOr(DataType.UNDEFINED)
// see if we can apply some optimized routines // see if we can apply some optimized routines
// TODO avoid using evaluation on stack everywhere
when(expr.operator) { when(expr.operator) {
"+" -> { "+" -> {
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) { if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
val leftVal = expr.left.constValue(program)?.number?.toInt() val leftVal = expr.left.constValue(program)?.number?.toInt()
val rightVal = expr.right.constValue(program)?.number?.toInt() val rightVal = expr.right.constValue(program)?.number?.toInt()
if (leftVal!=null && leftVal in -4..4) { if (leftVal!=null && leftVal in -4..4) {
translateExpression(expr.right) translateExpressionInternal(expr.right)
if(rightDt in ByteDatatypes) { if(rightDt in ByteDatatypes) {
val incdec = if(leftVal<0) "dec" else "inc" val incdec = if(leftVal<0) "dec" else "inc"
repeat(leftVal.absoluteValue) { repeat(leftVal.absoluteValue) {
@ -1821,7 +1894,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
} }
else if (rightVal!=null && rightVal in -4..4) else if (rightVal!=null && rightVal in -4..4)
{ {
translateExpression(expr.left) translateExpressionInternal(expr.left)
if(leftDt in ByteDatatypes) { if(leftDt in ByteDatatypes) {
val incdec = if(rightVal<0) "dec" else "inc" val incdec = if(rightVal<0) "dec" else "inc"
repeat(rightVal.absoluteValue) { repeat(rightVal.absoluteValue) {
@ -1856,7 +1929,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
val rightVal = expr.right.constValue(program)?.number?.toInt() val rightVal = expr.right.constValue(program)?.number?.toInt()
if (rightVal!=null && rightVal in -4..4) if (rightVal!=null && rightVal in -4..4)
{ {
translateExpression(expr.left) translateExpressionInternal(expr.left)
if(leftDt in ByteDatatypes) { if(leftDt in ByteDatatypes) {
val incdec = if(rightVal<0) "inc" else "dec" val incdec = if(rightVal<0) "inc" else "dec"
repeat(rightVal.absoluteValue) { repeat(rightVal.absoluteValue) {
@ -1889,7 +1962,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
">>" -> { ">>" -> {
val amount = expr.right.constValue(program)?.number?.toInt() val amount = expr.right.constValue(program)?.number?.toInt()
if(amount!=null) { if(amount!=null) {
translateExpression(expr.left) translateExpressionInternal(expr.left)
when (leftDt) { when (leftDt) {
DataType.UBYTE -> { DataType.UBYTE -> {
if (amount <= 2) if (amount <= 2)
@ -1960,7 +2033,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
"<<" -> { "<<" -> {
val amount = expr.right.constValue(program)?.number?.toInt() val amount = expr.right.constValue(program)?.number?.toInt()
if(amount!=null) { if(amount!=null) {
translateExpression(expr.left) translateExpressionInternal(expr.left)
if (leftDt in ByteDatatypes) { if (leftDt in ByteDatatypes) {
if (amount <= 2) if (amount <= 2)
repeat(amount) { asmgen.out(" asl P8ESTACK_LO+1,x") } repeat(amount) { asmgen.out(" asl P8ESTACK_LO+1,x") }
@ -1997,7 +2070,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
val amount = value.number.toInt() val amount = value.number.toInt()
if(amount==2) { if(amount==2) {
// optimize x*2 common case // optimize x*2 common case
translateExpression(expr.left) translateExpressionInternal(expr.left)
if(leftDt in ByteDatatypes) { if(leftDt in ByteDatatypes) {
asmgen.out(" asl P8ESTACK_LO+1,x") asmgen.out(" asl P8ESTACK_LO+1,x")
} else { } else {
@ -2008,38 +2081,38 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
when(rightDt) { when(rightDt) {
DataType.UBYTE -> { DataType.UBYTE -> {
if(amount in asmgen.optimizedByteMultiplications) { if(amount in asmgen.optimizedByteMultiplications) {
translateExpression(expr.left) translateExpressionInternal(expr.left)
asmgen.out(" jsr math.stack_mul_byte_$amount") asmgen.out(" jsr math.stack_mul_byte_$amount")
return return
} }
} }
DataType.BYTE -> { DataType.BYTE -> {
if(amount in asmgen.optimizedByteMultiplications) { if(amount in asmgen.optimizedByteMultiplications) {
translateExpression(expr.left) translateExpressionInternal(expr.left)
asmgen.out(" jsr math.stack_mul_byte_$amount") asmgen.out(" jsr math.stack_mul_byte_$amount")
return return
} }
if(amount.absoluteValue in asmgen.optimizedByteMultiplications) { if(amount.absoluteValue in asmgen.optimizedByteMultiplications) {
translateExpression(expr.left) translateExpressionInternal(expr.left)
asmgen.out(" jsr prog8_lib.neg_b | jsr math.stack_mul_byte_${amount.absoluteValue}") asmgen.out(" jsr prog8_lib.neg_b | jsr math.stack_mul_byte_${amount.absoluteValue}")
return return
} }
} }
DataType.UWORD -> { DataType.UWORD -> {
if(amount in asmgen.optimizedWordMultiplications) { if(amount in asmgen.optimizedWordMultiplications) {
translateExpression(expr.left) translateExpressionInternal(expr.left)
asmgen.out(" jsr math.stack_mul_word_$amount") asmgen.out(" jsr math.stack_mul_word_$amount")
return return
} }
} }
DataType.WORD -> { DataType.WORD -> {
if(amount in asmgen.optimizedWordMultiplications) { if(amount in asmgen.optimizedWordMultiplications) {
translateExpression(expr.left) translateExpressionInternal(expr.left)
asmgen.out(" jsr math.stack_mul_word_$amount") asmgen.out(" jsr math.stack_mul_word_$amount")
return return
} }
if(amount.absoluteValue in asmgen.optimizedWordMultiplications) { if(amount.absoluteValue in asmgen.optimizedWordMultiplications) {
translateExpression(expr.left) translateExpressionInternal(expr.left)
asmgen.out(" jsr prog8_lib.neg_w | jsr math.stack_mul_word_${amount.absoluteValue}") asmgen.out(" jsr prog8_lib.neg_w | jsr math.stack_mul_word_${amount.absoluteValue}")
return return
} }
@ -2053,7 +2126,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) { if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
val rightVal = expr.right.constValue(program)?.number?.toInt() val rightVal = expr.right.constValue(program)?.number?.toInt()
if(rightVal!=null && rightVal==2) { if(rightVal!=null && rightVal==2) {
translateExpression(expr.left) translateExpressionInternal(expr.left)
when(leftDt) { when(leftDt) {
DataType.UBYTE -> asmgen.out(" lsr P8ESTACK_LO+1,x") DataType.UBYTE -> asmgen.out(" lsr P8ESTACK_LO+1,x")
DataType.BYTE -> asmgen.out(" asl P8ESTACK_LO+1,x | ror P8ESTACK_LO+1,x") DataType.BYTE -> asmgen.out(" asl P8ESTACK_LO+1,x | ror P8ESTACK_LO+1,x")
@ -2075,9 +2148,9 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
translateCompareStrings(expr.left, expr.operator, expr.right) translateCompareStrings(expr.left, expr.operator, expr.right)
} }
else { else {
// the general, non-optimized cases TODO optimize more cases.... // the general, non-optimized cases TODO optimize more cases.... (or one day just don't use the evalstack at all anymore)
translateExpression(expr.left) translateExpressionInternal(expr.left)
translateExpression(expr.right) translateExpressionInternal(expr.right)
when (leftDt) { when (leftDt) {
in ByteDatatypes -> translateBinaryOperatorBytes(expr.operator, leftDt) in ByteDatatypes -> translateBinaryOperatorBytes(expr.operator, leftDt)
in WordDatatypes -> translateBinaryOperatorWords(expr.operator, leftDt) in WordDatatypes -> translateBinaryOperatorWords(expr.operator, leftDt)
@ -2104,7 +2177,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
} }
private fun translateExpression(expr: PrefixExpression) { private fun translateExpression(expr: PrefixExpression) {
translateExpression(expr.expression) translateExpressionInternal(expr.expression)
val itype = expr.inferType(program) val itype = expr.inferType(program)
if(!itype.isKnown) if(!itype.isKnown)
throw AssemblyError("unknown dt") throw AssemblyError("unknown dt")
@ -2189,8 +2262,6 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
} }
} }
fun translateExpression(indexer: ArrayIndex) = asmgen.translateExpression(indexer.indexExpr)
private fun translateBinaryOperatorBytes(operator: String, types: DataType) { private fun translateBinaryOperatorBytes(operator: String, types: DataType) {
when(operator) { when(operator) {
"**" -> throw AssemblyError("** operator requires floats") "**" -> throw AssemblyError("** operator requires floats")

View File

@ -8,8 +8,8 @@ import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.RangeExpr import prog8.ast.expressions.RangeExpr
import prog8.ast.statements.ForLoop import prog8.ast.statements.ForLoop
import prog8.ast.toHex import prog8.ast.toHex
import prog8.compiler.AssemblyError import prog8.compiler.target.AssemblyError
import prog8.compiler.astprocessing.toConstantIntegerRange import prog8.compilerinterface.toConstantIntegerRange
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) { internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) {
@ -20,7 +20,7 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
throw AssemblyError("unknown dt") throw AssemblyError("unknown dt")
when(stmt.iterable) { when(stmt.iterable) {
is RangeExpr -> { is RangeExpr -> {
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange(asmgen.options.compTarget) val range = (stmt.iterable as RangeExpr).toConstantIntegerRange()
if(range==null) { if(range==null) {
translateForOverNonconstRange(stmt, iterableDt.getOr(DataType.UNDEFINED), stmt.iterable as RangeExpr) translateForOverNonconstRange(stmt, iterableDt.getOr(DataType.UNDEFINED), stmt.iterable as RangeExpr)
} else { } else {

View File

@ -9,12 +9,12 @@ import prog8.ast.statements.InlineAssembly
import prog8.ast.statements.RegisterOrStatusflag import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine import prog8.ast.statements.Subroutine
import prog8.ast.statements.SubroutineParameter import prog8.ast.statements.SubroutineParameter
import prog8.compiler.AssemblyError import prog8.compiler.target.AssemblyError
import prog8.compiler.target.CpuType
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignSource import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignSource
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignTarget import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignTarget
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignment import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignment
import prog8.compiler.target.cpu6502.codegen.assignment.TargetStorageKind import prog8.compiler.target.cpu6502.codegen.assignment.TargetStorageKind
import prog8.compilerinterface.CpuType
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) { internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
@ -64,7 +64,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
argumentViaVariable(sub, arg.first, arg.second) argumentViaVariable(sub, arg.first, arg.second)
} }
} else { } else {
// via registers require(sub.isAsmSubroutine)
if(sub.parameters.size==1) { if(sub.parameters.size==1) {
// just a single parameter, no risk of clobbering registers // just a single parameter, no risk of clobbering registers
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single()), stmt.args[0]) argumentViaRegister(sub, IndexedValue(0, sub.parameters.single()), stmt.args[0])
@ -135,14 +135,25 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
private fun registerArgsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) { private fun registerArgsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) {
// this is called when one or more of the arguments are 'complex' and // this is called when one or more of the arguments are 'complex' and
// cannot be assigned to a register easily or risk clobbering other registers. // cannot be assigned to a register easily or risk clobbering other registers.
// TODO find another way to prepare the arguments, without using the eval stack
if(sub.parameters.isEmpty()) if(sub.parameters.isEmpty())
return return
// 1. load all arguments reversed onto the stack: first arg goes last (is on top). // 1. load all arguments reversed onto the stack: first arg goes last (is on top).
for (arg in stmt.args.reversed()) for (arg in stmt.args.reversed())
asmgen.translateExpression(arg) asmgen.translateExpression(arg)
// TODO here's an alternative to the above, but for now generates bigger code due to intermediate register steps:
// for (arg in stmt.args.reversed()) {
// // note this stuff below is needed to (eventually) avoid calling asmgen.translateExpression()
// // TODO also This STILL requires the translateNormalAssignment() to be fixed to avoid stack eval for expressions...
// val dt = arg.inferType(program).getOr(DataType.UNDEFINED)
// asmgen.assignExpressionTo(arg, AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, dt, sub))
// }
var argForCarry: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null var argForCarry: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
var argForXregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null var argForXregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
var argForAregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null var argForAregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
@ -325,7 +336,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
if(parameter.value.type in ByteDatatypes && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters)) if(parameter.value.type in ByteDatatypes && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters))
AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, parameter.value.type, sub, register = register) AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, parameter.value.type, sub, register = register)
else else
AsmAssignTarget.fromRegisters(register, sub, program, asmgen) AsmAssignTarget.fromRegisters(register, false, sub, program, asmgen)
val src = if(valueDt in PassByReferenceDatatypes) { val src = if(valueDt in PassByReferenceDatatypes) {
if(value is IdentifierReference) { if(value is IdentifierReference) {
val addr = AddressOf(value, Position.DUMMY) val addr = AddressOf(value, Position.DUMMY)

View File

@ -6,7 +6,7 @@ import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.PostIncrDecr import prog8.ast.statements.PostIncrDecr
import prog8.ast.toHex import prog8.ast.toHex
import prog8.compiler.AssemblyError import prog8.compiler.target.AssemblyError
internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) { internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) {

View File

@ -1,11 +1,11 @@
package prog8.compiler.target.cpu6502.codegen.assignment package prog8.compiler.target.cpu6502.codegen.assignment
import prog8.ast.IMemSizer
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.AssemblyError import prog8.compilerinterface.IMemSizer
import prog8.compiler.target.AssemblyError
import prog8.compiler.target.cpu6502.codegen.AsmGen import prog8.compiler.target.cpu6502.codegen.AsmGen
@ -41,11 +41,12 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
{ {
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0} val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0}
val constArrayIndexValue by lazy { array?.indexer?.constIndex() } val constArrayIndexValue by lazy { array?.indexer?.constIndex() }
val asmVarname: String val asmVarname: String by lazy {
get() = if(array==null) if (array == null)
variableAsmName!! variableAsmName!!
else else
asmgen.asmVariableName(array.arrayvar) asmgen.asmVariableName(array.arrayvar)
}
lateinit var origAssign: AsmAssignment lateinit var origAssign: AsmAssignment
@ -68,14 +69,14 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
} }
} }
fun fromRegisters(registers: RegisterOrPair, scope: Subroutine?, program: Program, asmgen: AsmGen): AsmAssignTarget = fun fromRegisters(registers: RegisterOrPair, signed: Boolean, scope: Subroutine?, program: Program, asmgen: AsmGen): AsmAssignTarget =
when(registers) { when(registers) {
RegisterOrPair.A, RegisterOrPair.A,
RegisterOrPair.X, RegisterOrPair.X,
RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UBYTE, scope, register = registers) RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, if(signed) DataType.BYTE else DataType.UBYTE, scope, register = registers)
RegisterOrPair.AX, RegisterOrPair.AX,
RegisterOrPair.AY, RegisterOrPair.AY,
RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UWORD, scope, register = registers) RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, if(signed) DataType.WORD else DataType.UWORD, scope, register = registers)
RegisterOrPair.FAC1, RegisterOrPair.FAC1,
RegisterOrPair.FAC2 -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.FLOAT, scope, register = registers) RegisterOrPair.FAC2 -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.FLOAT, scope, register = registers)
RegisterOrPair.R0, RegisterOrPair.R0,
@ -93,7 +94,7 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
RegisterOrPair.R12, RegisterOrPair.R12,
RegisterOrPair.R13, RegisterOrPair.R13,
RegisterOrPair.R14, RegisterOrPair.R14,
RegisterOrPair.R15 -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UWORD, scope, register = registers) RegisterOrPair.R15 -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, if(signed) DataType.WORD else DataType.UWORD, scope, register = registers)
} }
} }
} }

View File

@ -5,12 +5,12 @@ import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.toHex import prog8.ast.toHex
import prog8.compiler.AssemblyError import prog8.compiler.target.AssemblyError
import prog8.compiler.functions.BuiltinFunctions
import prog8.compiler.functions.builtinFunctionReturnType
import prog8.compiler.target.CpuType
import prog8.compiler.target.cpu6502.codegen.AsmGen import prog8.compiler.target.cpu6502.codegen.AsmGen
import prog8.compiler.target.cpu6502.codegen.ExpressionsAsmGen import prog8.compiler.target.cpu6502.codegen.ExpressionsAsmGen
import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.CpuType
import prog8.compilerinterface.builtinFunctionReturnType
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen, internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen,
@ -33,6 +33,11 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
} }
fun translateNormalAssignment(assign: AsmAssignment) { fun translateNormalAssignment(assign: AsmAssignment) {
if(assign.isAugmentable) {
augmentableAsmGen.translate(assign)
return
}
when(assign.source.kind) { when(assign.source.kind) {
SourceStorageKind.LITERALNUMBER -> { SourceStorageKind.LITERALNUMBER -> {
// simple case: assign a constant number // simple case: assign a constant number
@ -171,13 +176,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B -> { DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B -> {
// copy the actual string result into the target string variable // copy the actual string result into the target string variable
asmgen.out(""" asmgen.out("""
pha pha
lda #<${assign.target.asmVarname} lda #<${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1 sta P8ZP_SCRATCH_W1
lda #>${assign.target.asmVarname} lda #>${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1+1 sta P8ZP_SCRATCH_W1+1
pla pla
jsr prog8_lib.strcpy""") jsr prog8_lib.strcpy""")
} }
else -> throw AssemblyError("weird target dt") else -> throw AssemblyError("weird target dt")
} }
@ -249,30 +254,40 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
} }
} }
} }
is PrefixExpression -> {
// first assign the value to the target then apply the operator in place on the target.
translateNormalAssignment(AsmAssignment(
AsmAssignSource.fromAstSource(value.expression, program, asmgen),
assign.target,
false, program.memsizer, assign.position
))
when(value.operator) {
"+" -> {}
"-" -> augmentableAsmGen.inplaceNegate(assign.target, assign.target.datatype)
"~" -> augmentableAsmGen.inplaceInvert(assign.target, assign.target.datatype)
"not" -> augmentableAsmGen.inplaceBooleanNot(assign.target, assign.target.datatype)
else -> throw AssemblyError("invalid prefix operator")
}
}
else -> { else -> {
// Everything else just evaluate via the stack. // Everything else just evaluate via the stack.
// (we can't use the assignment helper functions to do it via registers here, // (we can't use the assignment helper functions (assignExpressionTo...) to do it via registers here,
// because the code here is the implementation of exactly that...) // because the code here is the implementation of exactly that...)
if (value.parent is Return) { // TODO FIX THIS... by using a temp var? so that it becomes augmentable assignment expression?
if (this.asmgen.options.slowCodegenWarnings) asmgen.translateExpression(value)
println("warning: slow stack evaluation used for return: $value target=${assign.target.kind} at ${value.position}")
}
exprAsmgen.translateExpression(value)
if (assign.target.datatype in WordDatatypes && assign.source.datatype in ByteDatatypes) if (assign.target.datatype in WordDatatypes && assign.source.datatype in ByteDatatypes)
asmgen.signExtendStackLsb(assign.source.datatype) asmgen.signExtendStackLsb(assign.source.datatype)
assignStackValue(assign.target) if(assign.target.kind!=TargetStorageKind.STACK || assign.target.datatype != assign.source.datatype)
assignStackValue(assign.target)
} }
} }
} }
SourceStorageKind.REGISTER -> { SourceStorageKind.REGISTER -> {
when(assign.source.datatype) { asmgen.assignRegister(assign.source.register!!, assign.target)
DataType.UBYTE -> assignRegisterByte(assign.target, assign.source.register!!.asCpuRegister())
DataType.UWORD -> assignRegisterpairWord(assign.target, assign.source.register!!)
else -> throw AssemblyError("invalid register dt")
}
} }
SourceStorageKind.STACK -> { SourceStorageKind.STACK -> {
assignStackValue(assign.target) if(assign.target.kind!=TargetStorageKind.STACK || assign.target.datatype != assign.source.datatype)
assignStackValue(assign.target)
} }
} }
} }
@ -364,16 +379,16 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
when (valueDt) { when (valueDt) {
in ByteDatatypes -> { in ByteDatatypes -> {
assignExpressionToRegister(value, RegisterOrPair.A) assignExpressionToRegister(value, RegisterOrPair.A, valueDt==DataType.BYTE)
assignTypeCastedRegisters(target.asmVarname, targetDt, RegisterOrPair.A, valueDt) assignTypeCastedRegisters(target.asmVarname, targetDt, RegisterOrPair.A, valueDt)
} }
in WordDatatypes -> { in WordDatatypes -> {
assignExpressionToRegister(value, RegisterOrPair.AY) assignExpressionToRegister(value, RegisterOrPair.AY, valueDt==DataType.WORD)
assignTypeCastedRegisters(target.asmVarname, targetDt, RegisterOrPair.AY, valueDt) assignTypeCastedRegisters(target.asmVarname, targetDt, RegisterOrPair.AY, valueDt)
} }
DataType.FLOAT -> { DataType.FLOAT -> {
assignExpressionToRegister(value, RegisterOrPair.FAC1) assignExpressionToRegister(value, RegisterOrPair.FAC1, true)
assignTypecastedFloatFAC1(target.asmVarname, targetDt) assignTypeCastedFloatFAC1(target.asmVarname, targetDt)
} }
in PassByReferenceDatatypes -> { in PassByReferenceDatatypes -> {
// str/array value cast (most likely to UWORD, take address-of) // str/array value cast (most likely to UWORD, take address-of)
@ -399,14 +414,14 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.X, RegisterOrPair.X,
RegisterOrPair.Y -> { RegisterOrPair.Y -> {
// 'cast' an ubyte value to a byte register; no cast needed at all // 'cast' an ubyte value to a byte register; no cast needed at all
return assignExpressionToRegister(value, target.register) return assignExpressionToRegister(value, target.register, false)
} }
RegisterOrPair.AX, RegisterOrPair.AX,
RegisterOrPair.AY, RegisterOrPair.AY,
RegisterOrPair.XY, RegisterOrPair.XY,
in Cx16VirtualRegisters -> { in Cx16VirtualRegisters -> {
// cast an ubyte value to a 16 bits register, just assign it and make use of the value extension // cast an ubyte value to a 16 bits register, just assign it and make use of the value extension
return assignExpressionToRegister(value, target.register!!) return assignExpressionToRegister(value, target.register!!, false)
} }
else -> {} else -> {}
} }
@ -424,19 +439,15 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.XY, RegisterOrPair.XY,
in Cx16VirtualRegisters -> { in Cx16VirtualRegisters -> {
// 'cast' uword into a 16 bits register, just assign it // 'cast' uword into a 16 bits register, just assign it
return assignExpressionToRegister(value, target.register!!) return assignExpressionToRegister(value, target.register!!, false)
} }
else -> {} else -> {}
} }
} }
// give up, do it via eval stack // No more special optmized cases yet. Do the rest via more complex evaluation
// note: cannot use assignTypeCastedValue because that is ourselves :P // note: cannot use assignTypeCastedValue because that is ourselves :P
// TODO optimize typecasts for more special cases? asmgen.assignExpressionTo(origTypeCastExpression, target)
if(this.asmgen.options.slowCodegenWarnings)
println("warning: slow stack evaluation used for typecast: $value into $targetDt (target=${target.kind} at ${value.position}")
asmgen.translateExpression(origTypeCastExpression) // this performs the actual type cast in translateExpression(Typecast)
assignStackValue(target)
} }
private fun assignCastViaLsbFunc(value: Expression, target: AsmAssignTarget) { private fun assignCastViaLsbFunc(value: Expression, target: AsmAssignTarget) {
@ -447,7 +458,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
translateNormalAssignment(assign) translateNormalAssignment(assign)
} }
private fun assignTypecastedFloatFAC1(targetAsmVarName: String, targetDt: DataType) { private fun assignTypeCastedFloatFAC1(targetAsmVarName: String, targetDt: DataType) {
if(targetDt==DataType.FLOAT) if(targetDt==DataType.FLOAT)
throw AssemblyError("typecast to identical type") throw AssemblyError("typecast to identical type")
@ -954,7 +965,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
lda #<$sourceName lda #<$sourceName
ldy #>$sourceName+1 ldy #>$sourceName+1
sta P8ESTACK_LO,x sta P8ESTACK_LO,x
sty P8ESTACK_HI,x tya
sta P8ESTACK_HI,x
dex""") dex""")
} }
else -> throw AssemblyError("string-assign to weird target") else -> throw AssemblyError("string-assign to weird target")
@ -1363,7 +1375,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
// these will be correctly typecasted from a byte to a word value // these will be correctly typecasted from a byte to a word value
if(target.register !in Cx16VirtualRegisters && if(target.register !in Cx16VirtualRegisters &&
target.register!=RegisterOrPair.AX && target.register!=RegisterOrPair.AY && target.register!=RegisterOrPair.XY) { target.register!=RegisterOrPair.AX && target.register!=RegisterOrPair.AY && target.register!=RegisterOrPair.XY) {
if(target.kind==TargetStorageKind.VARIABLE) { if(target.kind== TargetStorageKind.VARIABLE) {
val parts = target.asmVarname.split('.') val parts = target.asmVarname.split('.')
if (parts.size != 2 || parts[0] != "cx16") if (parts.size != 2 || parts[0] != "cx16")
require(target.datatype in ByteDatatypes) require(target.datatype in ByteDatatypes)
@ -1459,7 +1471,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
} }
internal fun assignRegisterpairWord(target: AsmAssignTarget, regs: RegisterOrPair) { internal fun assignRegisterpairWord(target: AsmAssignTarget, regs: RegisterOrPair) {
require(target.datatype in NumericDatatypes) require(target.datatype in NumericDatatypes || target.datatype in PassByReferenceDatatypes)
if(target.datatype==DataType.FLOAT) if(target.datatype==DataType.FLOAT)
throw AssemblyError("float value should be from FAC1 not from registerpair memory pointer") throw AssemblyError("float value should be from FAC1 not from registerpair memory pointer")
@ -1586,7 +1598,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
} }
TargetStorageKind.STACK -> { TargetStorageKind.STACK -> {
when(regs) { when(regs) {
RegisterOrPair.AY -> asmgen.out(" sta P8ESTACK_LO,x | sty P8ESTACK_HI,x | dex") RegisterOrPair.AY -> asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
RegisterOrPair.AX, RegisterOrPair.XY -> throw AssemblyError("can't use X here") RegisterOrPair.AX, RegisterOrPair.XY -> throw AssemblyError("can't use X here")
in Cx16VirtualRegisters -> { in Cx16VirtualRegisters -> {
val srcReg = asmgen.asmSymbolName(regs) val srcReg = asmgen.asmSymbolName(regs)
@ -2157,9 +2169,9 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
} }
} }
internal fun assignExpressionToRegister(expr: Expression, register: RegisterOrPair) { internal fun assignExpressionToRegister(expr: Expression, register: RegisterOrPair, signed: Boolean) {
val src = AsmAssignSource.fromAstSource(expr, program, asmgen) val src = AsmAssignSource.fromAstSource(expr, program, asmgen)
val tgt = AsmAssignTarget.fromRegisters(register, null, program, asmgen) val tgt = AsmAssignTarget.fromRegisters(register, signed, null, program, asmgen)
val assign = AsmAssignment(src, tgt, false, program.memsizer, expr.position) val assign = AsmAssignment(src, tgt, false, program.memsizer, expr.position)
translateNormalAssignment(assign) translateNormalAssignment(assign)
} }
@ -2171,8 +2183,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
translateNormalAssignment(assign) translateNormalAssignment(assign)
} }
internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair) { internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair, signed: Boolean) {
val tgt = AsmAssignTarget.fromRegisters(register, null, program, asmgen) val tgt = AsmAssignTarget.fromRegisters(register, signed, null, program, asmgen)
val src = AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, tgt.datatype, variableAsmName = asmVarName) val src = AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, tgt.datatype, variableAsmName = asmVarName)
val assign = AsmAssignment(src, tgt, false, program.memsizer, Position.DUMMY) val assign = AsmAssignment(src, tgt, false, program.memsizer, Position.DUMMY)
translateNormalAssignment(assign) translateNormalAssignment(assign)

View File

@ -5,10 +5,10 @@ import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.Subroutine import prog8.ast.statements.Subroutine
import prog8.ast.toHex import prog8.ast.toHex
import prog8.compiler.AssemblyError import prog8.compiler.target.AssemblyError
import prog8.compiler.target.CpuType
import prog8.compiler.target.cpu6502.codegen.AsmGen import prog8.compiler.target.cpu6502.codegen.AsmGen
import prog8.compiler.target.cpu6502.codegen.ExpressionsAsmGen import prog8.compiler.target.cpu6502.codegen.ExpressionsAsmGen
import prog8.compilerinterface.CpuType
internal class AugmentableAssignmentAsmGen(private val program: Program, internal class AugmentableAssignmentAsmGen(private val program: Program,
private val assignmentAsmGen: AssignmentAsmGen, private val assignmentAsmGen: AssignmentAsmGen,
@ -181,8 +181,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
} }
} }
else -> { else -> {
asmgen.translateExpression(memory.addressExpression) // TODO OTHER EVALUATION HERE, don't use the estack
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack | sta P8ZP_SCRATCH_B1") asmgen.assignExpressionTo(memory.addressExpression, AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, DataType.UWORD, memory.definingSubroutine))
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack | sta P8ZP_SCRATCH_B1") // TODO don't use estack to transfer the address to read from
when { when {
valueLv != null -> inplaceModification_byte_litval_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, valueLv.toInt()) valueLv != null -> inplaceModification_byte_litval_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, valueLv.toInt())
ident != null -> inplaceModification_byte_variable_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, ident) ident != null -> inplaceModification_byte_variable_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, ident)
@ -193,7 +194,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
} }
else -> inplaceModification_byte_value_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, value) else -> inplaceModification_byte_value_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, value)
} }
asmgen.out(" lda P8ZP_SCRATCH_B1 | jsr prog8_lib.write_byte_to_address_on_stack | inx") asmgen.out(" lda P8ZP_SCRATCH_B1 | jsr prog8_lib.write_byte_to_address_on_stack | inx") // TODO don't use estack to transfer the address to read from
} }
} }
} }
@ -246,19 +247,37 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
indexVar!=null -> { indexVar!=null -> {
when (target.datatype) { when (target.datatype) {
in ByteDatatypes -> { in ByteDatatypes -> {
val tgt = AsmAssignTarget.fromRegisters(RegisterOrPair.A, null, program, asmgen) val tgt =
AsmAssignTarget.fromRegisters(
RegisterOrPair.A,
target.datatype == DataType.BYTE, null,
program,
asmgen
)
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position) val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
assignmentAsmGen.translateNormalAssignment(assign) assignmentAsmGen.translateNormalAssignment(assign)
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A) assignmentAsmGen.assignRegisterByte(target, CpuRegister.A)
} }
in WordDatatypes -> { in WordDatatypes -> {
val tgt = AsmAssignTarget.fromRegisters(RegisterOrPair.AY, null, program, asmgen) val tgt =
AsmAssignTarget.fromRegisters(
RegisterOrPair.AY,
target.datatype == DataType.WORD, null,
program,
asmgen
)
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position) val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
assignmentAsmGen.translateNormalAssignment(assign) assignmentAsmGen.translateNormalAssignment(assign)
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY) assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
} }
DataType.FLOAT -> { DataType.FLOAT -> {
val tgt = AsmAssignTarget.fromRegisters(RegisterOrPair.FAC1, null, program, asmgen) val tgt =
AsmAssignTarget.fromRegisters(
RegisterOrPair.FAC1,
true, null,
program,
asmgen
)
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position) val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
assignmentAsmGen.translateNormalAssignment(assign) assignmentAsmGen.translateNormalAssignment(assign)
assignmentAsmGen.assignFAC1float(target) assignmentAsmGen.assignFAC1float(target)
@ -1705,7 +1724,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
} }
} }
private fun inplaceBooleanNot(target: AsmAssignTarget, dt: DataType) { internal fun inplaceBooleanNot(target: AsmAssignTarget, dt: DataType) {
when (dt) { when (dt) {
DataType.UBYTE -> { DataType.UBYTE -> {
when (target.kind) { when (target.kind) {
@ -1749,9 +1768,30 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
} }
} }
} }
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place not of ubyte array") TargetStorageKind.REGISTER -> {
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg not") when(target.register!!) {
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack not") RegisterOrPair.A -> asmgen.out("""
cmp #0
beq +
lda #1
+ eor #1""")
RegisterOrPair.X -> asmgen.out("""
txa
beq +
lda #1
+ eor #1
tax""")
RegisterOrPair.Y -> asmgen.out("""
tya
beq +
lda #1
+ eor #1
tay""")
else -> throw AssemblyError("invalid reg dt for byte not")
}
}
TargetStorageKind.STACK -> TODO("missing codegen for byte stack not")
else -> throw AssemblyError("missing codegen for in-place not of ubyte ${target.kind}")
} }
} }
DataType.UWORD -> { DataType.UWORD -> {
@ -1767,17 +1807,56 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
lsr a lsr a
sta ${target.asmVarname}+1""") sta ${target.asmVarname}+1""")
} }
TargetStorageKind.MEMORY -> throw AssemblyError("no asm gen for uword-memory not") TargetStorageKind.REGISTER -> {
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place not of uword array") when(target.register!!) {
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg not") RegisterOrPair.AX -> {
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack not") asmgen.out("""
stx P8ZP_SCRATCH_REG
ora P8ZP_SCRATCH_REG
beq +
lda #0
tax
beq ++
+ lda #1
+""")
}
RegisterOrPair.AY -> {
asmgen.out("""
sty P8ZP_SCRATCH_REG
ora P8ZP_SCRATCH_REG
beq +
lda #0
tay
beq ++
+ lda #1
+""")
}
RegisterOrPair.XY -> {
asmgen.out("""
stx P8ZP_SCRATCH_REG
tya
ora P8ZP_SCRATCH_REG
beq +
ldy #0
ldx #0
beq ++
+ ldx #1
+""")
}
in Cx16VirtualRegisters -> TODO()
else -> throw AssemblyError("invalid reg dt for word not")
}
}
TargetStorageKind.MEMORY -> TODO("no asm gen for uword-memory not")
TargetStorageKind.STACK -> TODO("missing codegen for word stack not")
else -> throw AssemblyError("missing codegen for in-place not of uword for ${target.kind}")
} }
} }
else -> throw AssemblyError("boolean-not of invalid type") else -> throw AssemblyError("boolean-not of invalid type")
} }
} }
private fun inplaceInvert(target: AsmAssignTarget, dt: DataType) { internal fun inplaceInvert(target: AsmAssignTarget, dt: DataType) {
when (dt) { when (dt) {
DataType.UBYTE -> { DataType.UBYTE -> {
when (target.kind) { when (target.kind) {
@ -1812,9 +1891,16 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
} }
} }
} }
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place invert ubyte array") TargetStorageKind.REGISTER -> {
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg invert") when(target.register!!) {
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack invert") RegisterOrPair.A -> asmgen.out(" eor #255")
RegisterOrPair.X -> asmgen.out(" txa | eor #255 | tax")
RegisterOrPair.Y -> asmgen.out(" tya | eor #255 | tay")
else -> throw AssemblyError("invalid reg dt for byte invert")
}
}
TargetStorageKind.STACK -> TODO("missing codegen for byte stack invert")
else -> throw AssemblyError("missing codegen for in-place invert ubyte for ${target.kind}")
} }
} }
DataType.UWORD -> { DataType.UWORD -> {
@ -1828,17 +1914,27 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
eor #255 eor #255
sta ${target.asmVarname}+1""") sta ${target.asmVarname}+1""")
} }
TargetStorageKind.MEMORY -> throw AssemblyError("no asm gen for uword-memory invert") TargetStorageKind.REGISTER -> {
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place invert uword array") when(target.register!!) {
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg invert") RegisterOrPair.AX -> asmgen.out(" pha | txa | eor #255 | tax | pla | eor #255")
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack invert") RegisterOrPair.AY -> asmgen.out(" pha | tya | eor #255 | tay | pla | eor #255")
RegisterOrPair.XY -> asmgen.out(" txa | eor #255 | tax | tya | eor #255 | tay")
in Cx16VirtualRegisters -> {
TODO("codegen for cx16 word register invert")
}
else -> throw AssemblyError("invalid reg dt for word invert")
}
}
TargetStorageKind.MEMORY -> TODO("no asm gen for uword-memory invert")
TargetStorageKind.STACK -> TODO("missing codegen for word stack invert")
else -> throw AssemblyError("missing codegen for in-place invert uword for ${target.kind}")
} }
} }
else -> throw AssemblyError("invert of invalid type") else -> throw AssemblyError("invert of invalid type")
} }
} }
private fun inplaceNegate(target: AsmAssignTarget, dt: DataType) { internal fun inplaceNegate(target: AsmAssignTarget, dt: DataType) {
when (dt) { when (dt) {
DataType.BYTE -> { DataType.BYTE -> {
when (target.kind) { when (target.kind) {
@ -1849,10 +1945,17 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
sbc ${target.asmVarname} sbc ${target.asmVarname}
sta ${target.asmVarname}""") sta ${target.asmVarname}""")
} }
TargetStorageKind.MEMORY -> throw AssemblyError("can't in-place negate memory ubyte") TargetStorageKind.REGISTER -> {
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place negate byte array") when(target.register!!) {
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg negate") RegisterOrPair.A -> asmgen.out(" sta P8ZP_SCRATCH_B1 | lda #0 | sec | sbc P8ZP_SCRATCH_B1")
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack negate") RegisterOrPair.X -> asmgen.out(" stx P8ZP_SCRATCH_B1 | lda #0 | sec | sbc P8ZP_SCRATCH_B1 | tax")
RegisterOrPair.Y -> asmgen.out(" sty P8ZP_SCRATCH_B1 | lda #0 | sec | sbc P8ZP_SCRATCH_B1 | tay")
else -> throw AssemblyError("invalid reg dt for byte negate")
}
}
TargetStorageKind.MEMORY -> TODO("can't in-place negate memory ubyte")
TargetStorageKind.STACK -> TODO("missing codegen for byte stack negate")
else -> throw AssemblyError("missing codegen for in-place negate byte array")
} }
} }
DataType.WORD -> { DataType.WORD -> {
@ -1867,10 +1970,55 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
sbc ${target.asmVarname}+1 sbc ${target.asmVarname}+1
sta ${target.asmVarname}+1""") sta ${target.asmVarname}+1""")
} }
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place negate word array") TargetStorageKind.REGISTER -> {
TargetStorageKind.MEMORY -> throw AssemblyError("no asm gen for word memory negate") when(target.register!!) { //P8ZP_SCRATCH_REG
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg negate") RegisterOrPair.AX -> {
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack negate") asmgen.out("""
sta P8ZP_SCRATCH_REG
stx P8ZP_SCRATCH_REG+1
lda #0
sec
sbc P8ZP_SCRATCH_REG
pha
lda #0
sbc P8ZP_SCRATCH_REG+1
tax
pla""")
}
RegisterOrPair.AY -> {
asmgen.out("""
sta P8ZP_SCRATCH_REG
sty P8ZP_SCRATCH_REG+1
lda #0
sec
sbc P8ZP_SCRATCH_REG
pha
lda #0
sbc P8ZP_SCRATCH_REG+1
tay
pla""")
}
RegisterOrPair.XY -> {
asmgen.out("""
stx P8ZP_SCRATCH_REG
sty P8ZP_SCRATCH_REG+1
lda #0
sec
sbc P8ZP_SCRATCH_REG
tax
lda #0
sbc P8ZP_SCRATCH_REG+1
tay""")
}
in Cx16VirtualRegisters -> {
TODO("codegen for cx16 word register negate")
}
else -> throw AssemblyError("invalid reg dt for word neg")
}
}
TargetStorageKind.MEMORY -> TODO("no asm gen for word memory negate")
TargetStorageKind.STACK -> TODO("missing codegen for word stack negate")
else -> throw AssemblyError("missing codegen for in-place negate word array")
} }
} }
DataType.FLOAT -> { DataType.FLOAT -> {
@ -1883,9 +2031,10 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
sta ${target.asmVarname}+1 sta ${target.asmVarname}+1
""") """)
} }
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place negate float array") TargetStorageKind.REGISTER -> TODO("missing codegen for float reg negate")
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack float negate") TargetStorageKind.MEMORY -> TODO("missing codegen for float memory negate")
else -> throw AssemblyError("weird target kind for float") TargetStorageKind.STACK -> TODO("missing codegen for stack float negate")
else -> throw AssemblyError("weird target kind for inplace negate float ${target.kind}")
} }
} }
else -> throw AssemblyError("negate of invalid type") else -> throw AssemblyError("negate of invalid type")

View File

@ -1,14 +1,13 @@
package prog8.compiler.target.cx16 package prog8.compiler.target.cx16
import prog8.compiler.*
import prog8.compiler.target.CpuType
import prog8.compiler.target.IMachineDefinition
import prog8.compiler.target.c64.C64MachineDefinition import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.cbm.viceMonListPostfix import prog8.compiler.target.cbm.viceMonListPostfix
import prog8.compilerinterface.*
import java.io.IOException import java.io.IOException
import java.nio.file.Path import java.nio.file.Path
internal object CX16MachineDefinition: IMachineDefinition {
object CX16MachineDefinition: IMachineDefinition {
override val cpu = CpuType.CPU65c02 override val cpu = CpuType.CPU65c02
@ -88,7 +87,7 @@ internal object CX16MachineDefinition: IMachineDefinition {
"rmb", "smb", "stp", "wai") "rmb", "smb", "stp", "wai")
internal class CX16Zeropage(options: CompilationOptions) : Zeropage(options) { class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0x7a // temp storage for a single byte override val SCRATCH_B1 = 0x7a // temp storage for a single byte
override val SCRATCH_REG = 0x7b // temp storage for a register, must be B1+1 override val SCRATCH_REG = 0x7b // temp storage for a register, must be B1+1
@ -98,37 +97,28 @@ internal object CX16MachineDefinition: IMachineDefinition {
init { init {
if (options.floats && options.zeropage !in arrayOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE )) if (options.floats && options.zeropage !in arrayOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
throw CompilerException("when floats are enabled, zero page type should be 'basicsafe' or 'dontuse'") throw InternalCompilerException("when floats are enabled, zero page type should be 'basicsafe' or 'dontuse'")
// the addresses 0x02 to 0x21 (inclusive) are taken for sixteen virtual 16-bit api registers. // the addresses 0x02 to 0x21 (inclusive) are taken for sixteen virtual 16-bit api registers.
when (options.zeropage) { when (options.zeropage) {
ZeropageType.FULL -> { ZeropageType.FULL -> {
free.addAll(0x22..0xff) free.addAll(0x22..0xff)
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
} }
ZeropageType.KERNALSAFE -> { ZeropageType.KERNALSAFE -> {
free.addAll(0x22..0x7f) free.addAll(0x22..0x7f)
free.addAll(0xa9..0xff) free.addAll(0xa9..0xff)
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
} }
ZeropageType.BASICSAFE -> { ZeropageType.BASICSAFE -> {
free.addAll(0x22..0x7f) free.addAll(0x22..0x7f)
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
} }
ZeropageType.DONTUSE -> { ZeropageType.DONTUSE -> {
free.clear() // don't use zeropage at all free.clear() // don't use zeropage at all
} }
else -> throw CompilerException("for this machine target, zero page type 'floatsafe' is not available. ${options.zeropage}") else -> throw InternalCompilerException("for this machine target, zero page type 'floatsafe' is not available. ${options.zeropage}")
} }
require(SCRATCH_B1 !in free) removeReservedFromFreePool()
require(SCRATCH_REG !in free)
require(SCRATCH_W1 !in free)
require(SCRATCH_W2 !in free)
for (reserved in options.zpReserved)
reserve(reserved)
} }
} }
} }

View File

@ -1,4 +1,4 @@
package prog8tests package prog8tests.asmgen
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo import org.hamcrest.Matchers.equalTo
@ -6,18 +6,23 @@ import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
import prog8.ast.Module import prog8.ast.Module
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.DataType
import prog8.ast.base.Position
import prog8.ast.base.RegisterOrPair
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.AddressOf import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.IdentifierReference import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.*
import prog8.compiler.target.C64Target import prog8.compiler.target.C64Target
import prog8.compiler.target.c64.C64MachineDefinition import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.cpu6502.codegen.AsmGen import prog8.compiler.target.cpu6502.codegen.AsmGen
import prog8.compilerinterface.*
import prog8.parser.SourceCode import prog8.parser.SourceCode
import prog8tests.helpers.DummyFunctions import prog8tests.asmgen.helpers.DummyFunctions
import prog8tests.helpers.DummyMemsizer import prog8tests.asmgen.helpers.DummyMemsizer
import prog8tests.asmgen.helpers.DummyStringEncoder
import prog8tests.asmgen.helpers.ErrorReporterForTests
import java.nio.file.Path import java.nio.file.Path
@ -63,20 +68,20 @@ locallabel:
val assign8 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","label_outside"), Position.DUMMY), Position.DUMMY), Position.DUMMY) val assign8 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","label_outside"), Position.DUMMY), Position.DUMMY), Position.DUMMY)
val statements = mutableListOf(varInSub, var2InSub, labelInSub, assign1, assign2, assign3, assign4, assign5, assign6, assign7, assign8) val statements = mutableListOf(varInSub, var2InSub, labelInSub, assign1, assign2, assign3, assign4, assign5, assign6, assign7, assign8)
val subroutine = Subroutine("start", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, statements, Position.DUMMY) val subroutine = Subroutine("start", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, statements, Position.DUMMY)
val labelInBlock = Label("label_outside", Position.DUMMY) val labelInBlock = Label("label_outside", Position.DUMMY)
val varInBlock = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "var_outside", null, false, false, false, Position.DUMMY) val varInBlock = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "var_outside", null, false, false, false, Position.DUMMY)
val block = Block("main", null, mutableListOf(labelInBlock, varInBlock, subroutine), false, Position.DUMMY) val block = Block("main", null, mutableListOf(labelInBlock, varInBlock, subroutine), false, Position.DUMMY)
val module = Module(mutableListOf(block), Position.DUMMY, SourceCode.Generated("test")) val module = Module(mutableListOf(block), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer) val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module) .addModule(module)
module.linkIntoProgram(program) module.linkIntoProgram(program)
return program return program
} }
private fun createTestAsmGen(program: Program): AsmGen { private fun createTestAsmGen(program: Program): AsmGen {
val errors = ErrorReporter() val errors = ErrorReporterForTests()
val options = CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, true, C64Target) val options = CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, true, C64Target)
val zp = C64MachineDefinition.C64Zeropage(options) val zp = C64MachineDefinition.C64Zeropage(options)
val asmgen = AsmGen(program, errors, zp, options, C64Target, Path.of("")) val asmgen = AsmGen(program, errors, zp, options, C64Target, Path.of(""))

View File

@ -0,0 +1,37 @@
package prog8tests.asmgen.helpers
import prog8.ast.IBuiltinFunctions
import prog8.ast.base.Position
import prog8.ast.expressions.Expression
import prog8.ast.expressions.InferredTypes
import prog8.ast.expressions.NumericLiteralValue
import prog8.compilerinterface.IMemSizer
import prog8.ast.base.DataType
import prog8.compilerinterface.IStringEncoding
internal val DummyFunctions = object : IBuiltinFunctions {
override val names: Set<String> = emptySet()
override val purefunctionNames: Set<String> = emptySet()
override fun constValue(
name: String,
args: List<Expression>,
position: Position,
): NumericLiteralValue? = null
override fun returnType(name: String, args: MutableList<Expression>) = InferredTypes.InferredType.unknown()
}
internal val DummyMemsizer = object : IMemSizer {
override fun memorySize(dt: DataType): Int = 0
}
internal val DummyStringEncoder = object : IStringEncoding {
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
return emptyList()
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String {
return ""
}
}

View File

@ -0,0 +1,30 @@
package prog8tests.asmgen.helpers
import prog8.ast.base.Position
import prog8.compilerinterface.IErrorReporter
internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: Boolean=true): IErrorReporter {
val errors = mutableListOf<String>()
val warnings = mutableListOf<String>()
override fun err(msg: String, position: Position) {
errors.add("${position.toClickableStr()} $msg")
}
override fun warn(msg: String, position: Position) {
warnings.add("${position.toClickableStr()} $msg")
}
override fun noErrors(): Boolean = errors.isEmpty()
override fun report() {
warnings.forEach { println("UNITTEST COMPILATION REPORT: WARNING: $it") }
errors.forEach { println("UNITTEST COMPILATION REPORT: ERROR: $it") }
if(throwExceptionAtReportIfErrors)
finalizeNumErrors(errors.size, warnings.size)
errors.clear()
warnings.clear()
}
}

View File

@ -0,0 +1,54 @@
plugins {
id 'java'
id 'application'
id "org.jetbrains.kotlin.jvm"
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
}
dependencies {
implementation project(':compilerInterfaces')
implementation project(':compilerAst')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2'
testImplementation 'org.hamcrest:hamcrest:2.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2'
}
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
}
resources {
srcDirs = ["${project.projectDir}/res"]
}
}
test {
java {
srcDirs = ["${project.projectDir}/test"]
}
}
}
test {
// Enable JUnit 5 (Gradle 4.6+).
useJUnitPlatform()
// Always run tests, even when nothing changed.
dependsOn 'cleanTest'
// Show test results.
testLogging {
events "skipped", "failed"
}
}

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="module" module-name="compilerInterfaces" />
<orderEntry type="module" module-name="compilerAst" />
<orderEntry type="library" scope="TEST" name="hamcrest" level="project" />
<orderEntry type="library" name="junit.jupiter" level="project" />
</component>
</module>

View File

@ -1,22 +1,24 @@
package prog8.optimizer package prog8.optimizer
import prog8.ast.INameScope import prog8.ast.IStatementContainer
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.expressions.BinaryExpression import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.augmentAssignmentOperators import prog8.ast.expressions.augmentAssignmentOperators
import prog8.ast.statements.AssignTarget import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment import prog8.ast.statements.Assignment
import prog8.ast.walk.AstWalker import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification import prog8.ast.walk.IAstModification
import prog8.compiler.astprocessing.isInRegularRAMof import prog8.compilerinterface.CompilationOptions
import prog8.compiler.target.ICompilationTarget import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.isInRegularRAMof
internal class BinExprSplitter(private val program: Program, private val compTarget: ICompilationTarget) : AstWalker() { class BinExprSplitter(private val program: Program, private val options: CompilationOptions, private val compTarget: ICompilationTarget) : AstWalker() {
// override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> { // override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
// TODO somehow if we do this, the resulting code for some programs (cube3d.p8) gets hundreds of bytes larger...: // TODO somehow if we do this, the resulting code for some programs (cube3d.p8) gets hundreds of bytes larger...: [ IS THIS STILL TRUE AFTER ALL CHANGES? ]
// if(decl.type==VarDeclType.VAR ) { // if(decl.type==VarDeclType.VAR ) {
// val binExpr = decl.value as? BinaryExpression // val binExpr = decl.value as? BinaryExpression
// if (binExpr != null && binExpr.operator in augmentAssignmentOperators) { // if (binExpr != null && binExpr.operator in augmentAssignmentOperators) {
@ -38,6 +40,11 @@ internal class BinExprSplitter(private val program: Program, private val compTar
val binExpr = assignment.value as? BinaryExpression val binExpr = assignment.value as? BinaryExpression
if (binExpr != null) { if (binExpr != null) {
if(binExpr.inferType(program).istype(DataType.FLOAT) && !options.optimizeFloatExpressions)
return noModifications
/* /*
Reduce the complexity of a (binary) expression that has to be evaluated on the eval stack, Reduce the complexity of a (binary) expression that has to be evaluated on the eval stack,
@ -65,7 +72,7 @@ X = BinExpr X = LeftExpr
val augExpr = BinaryExpression(targetExpr, binExpr.operator, binExpr.right, binExpr.right.position) val augExpr = BinaryExpression(targetExpr, binExpr.operator, binExpr.right, binExpr.right.position)
return listOf( return listOf(
IAstModification.ReplaceNode(binExpr, augExpr, assignment), IAstModification.ReplaceNode(binExpr, augExpr, assignment),
IAstModification.InsertBefore(assignment, firstAssign, assignment.parent as INameScope) IAstModification.InsertBefore(assignment, firstAssign, assignment.parent as IStatementContainer)
) )
} }
} }

View File

@ -12,7 +12,7 @@ import prog8.ast.walk.IAstModification
import kotlin.math.pow import kotlin.math.pow
internal class ConstantFoldingOptimizer(private val program: Program) : AstWalker() { class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> { override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
// @( &thing ) --> thing // @( &thing ) --> thing

View File

@ -1,6 +1,5 @@
package prog8.optimizer package prog8.optimizer
import prog8.ast.INameScope
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
@ -8,16 +7,20 @@ import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.walk.AstWalker import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter import prog8.compilerinterface.ICompilationTarget
import prog8.compiler.astprocessing.size import prog8.compilerinterface.IErrorReporter
import prog8.compiler.astprocessing.toConstantIntegerRange import prog8.compilerinterface.size
import prog8.compiler.target.ICompilationTarget import prog8.compilerinterface.toConstantIntegerRange
// Fix up the literal value's type to match that of the vardecl // Fix up the literal value's type to match that of the vardecl
// (also check range literal operands types before they get expanded into arrays for instance) // (also check range literal operands types before they get expanded into arrays for instance)
internal class VarConstantValueTypeAdjuster(private val program: Program, private val errors: IErrorReporter) : AstWalker() { class VarConstantValueTypeAdjuster(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> { override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(decl.parent is AnonymousScope)
throw FatalAstException("vardecl may no longer occur in anonymousscope")
try { try {
val declConstValue = decl.value?.constValue(program) val declConstValue = decl.value?.constValue(program)
if(declConstValue!=null && (decl.type==VarDeclType.VAR || decl.type==VarDeclType.CONST) if(declConstValue!=null && (decl.type==VarDeclType.VAR || decl.type==VarDeclType.CONST)
@ -31,28 +34,6 @@ internal class VarConstantValueTypeAdjuster(private val program: Program, privat
errors.err(x.message, x.position) errors.err(x.message, x.position)
} }
// move vardecl to the containing subroutine and add initialization assignment in its place if needed
if(decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
val subroutine = decl.definingSubroutine as? INameScope
if(subroutine!=null && subroutine!==parent) {
val declValue = decl.value
decl.value = null
decl.allowInitializeWithZero = false
return if (declValue == null) {
listOf(
IAstModification.Remove(decl, parent as INameScope),
IAstModification.InsertFirst(decl, subroutine)
)
} else {
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
val assign = Assignment(target, declValue, decl.position)
listOf(
IAstModification.ReplaceNode(decl, assign, parent),
IAstModification.InsertFirst(decl, subroutine)
)
}
}
}
return noModifications return noModifications
} }
@ -160,9 +141,9 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
if(rangeExpr!=null) { if(rangeExpr!=null) {
// convert the initializer range expression to an actual array // convert the initializer range expression to an actual array
val declArraySize = decl.arraysize?.constIndex() val declArraySize = decl.arraysize?.constIndex()
if(declArraySize!=null && declArraySize!=rangeExpr.size(compTarget)) if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size doesn't match declared array size", decl.value?.position!!) errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
val constRange = rangeExpr.toConstantIntegerRange(compTarget) val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) { if(constRange!=null) {
val eltType = rangeExpr.inferType(program).getOr(DataType.UBYTE) val eltType = rangeExpr.inferType(program).getOr(DataType.UBYTE)
val newValue = if(eltType in ByteDatatypes) { val newValue = if(eltType in ByteDatatypes) {
@ -214,9 +195,9 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
if(rangeExpr!=null) { if(rangeExpr!=null) {
// convert the initializer range expression to an actual array of floats // convert the initializer range expression to an actual array of floats
val declArraySize = decl.arraysize?.constIndex() val declArraySize = decl.arraysize?.constIndex()
if(declArraySize!=null && declArraySize!=rangeExpr.size(compTarget)) if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size (${rangeExpr.size(compTarget)}) doesn't match declared array size ($declArraySize)", decl.value?.position!!) errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
val constRange = rangeExpr.toConstantIntegerRange(compTarget) val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) { if(constRange!=null) {
val newValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F), val newValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F),
constRange.map { NumericLiteralValue(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(), constRange.map { NumericLiteralValue(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),

View File

@ -15,14 +15,25 @@ import kotlin.math.log2
import kotlin.math.pow import kotlin.math.pow
/* /*
todo add more expression optimizations todo add more peephole expression optimizations
Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html
*(&X) => X
X % 1 => 0
X / 1 => X
X ^ -1 => ~x
X >= 1 => X > 0
X < 1 => X <= 0
X + С1 == C2 => X == C2 - C1
((X + C1) + C2) => (X + (C1 + C2))
((X + C1) + (Y + C2)) => ((X + Y) + (C1 + C2))
*/ */
internal class ExpressionSimplifier(private val program: Program) : AstWalker() { class ExpressionSimplifier(private val program: Program) : AstWalker() {
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet() private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet() private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()

View File

@ -2,11 +2,12 @@ package prog8.optimizer
import prog8.ast.IBuiltinFunctions import prog8.ast.IBuiltinFunctions
import prog8.ast.Program import prog8.ast.Program
import prog8.compiler.IErrorReporter import prog8.compilerinterface.CompilationOptions
import prog8.compiler.target.ICompilationTarget import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
internal fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilationTarget) { fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilationTarget) {
val valuetypefixer = VarConstantValueTypeAdjuster(this, errors) val valuetypefixer = VarConstantValueTypeAdjuster(this, errors)
valuetypefixer.visit(this) valuetypefixer.visit(this)
if(errors.noErrors()) { if(errors.noErrors()) {
@ -40,9 +41,10 @@ internal fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilati
} }
internal fun Program.optimizeStatements(errors: IErrorReporter, fun Program.optimizeStatements(errors: IErrorReporter,
functions: IBuiltinFunctions, functions: IBuiltinFunctions,
compTarget: ICompilationTarget): Int { compTarget: ICompilationTarget
): Int {
val optimizer = StatementOptimizer(this, errors, functions, compTarget) val optimizer = StatementOptimizer(this, errors, functions, compTarget)
optimizer.visit(this) optimizer.visit(this)
val optimizationCount = optimizer.applyModifications() val optimizationCount = optimizer.applyModifications()
@ -52,14 +54,14 @@ internal fun Program.optimizeStatements(errors: IErrorReporter,
return optimizationCount return optimizationCount
} }
internal fun Program.simplifyExpressions() : Int { fun Program.simplifyExpressions() : Int {
val opti = ExpressionSimplifier(this) val opti = ExpressionSimplifier(this)
opti.visit(this) opti.visit(this)
return opti.applyModifications() return opti.applyModifications()
} }
internal fun Program.splitBinaryExpressions(compTarget: ICompilationTarget) : Int { fun Program.splitBinaryExpressions(options: CompilationOptions, compTarget: ICompilationTarget) : Int {
val opti = BinExprSplitter(this, compTarget) val opti = BinExprSplitter(this, options, compTarget)
opti.visit(this) opti.visit(this)
return opti.applyModifications() return opti.applyModifications()
} }

View File

@ -1,7 +1,7 @@
package prog8.optimizer package prog8.optimizer
import prog8.ast.IBuiltinFunctions import prog8.ast.IBuiltinFunctions
import prog8.ast.INameScope import prog8.ast.IStatementContainer
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
@ -10,20 +10,21 @@ import prog8.ast.statements.*
import prog8.ast.walk.AstWalker import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification import prog8.ast.walk.IAstModification
import prog8.ast.walk.IAstVisitor import prog8.ast.walk.IAstVisitor
import prog8.compiler.IErrorReporter import prog8.compilerinterface.ICompilationTarget
import prog8.compiler.astprocessing.size import prog8.compilerinterface.IErrorReporter
import prog8.compiler.target.ICompilationTarget import prog8.compilerinterface.size
import kotlin.math.floor import kotlin.math.floor
internal const val retvarName = "prog8_retval" internal const val retvarName = "prog8_retval"
internal class StatementOptimizer(private val program: Program, class StatementOptimizer(private val program: Program,
private val errors: IErrorReporter, private val errors: IErrorReporter,
private val functions: IBuiltinFunctions, private val functions: IBuiltinFunctions,
private val compTarget: ICompilationTarget) : AstWalker() { private val compTarget: ICompilationTarget
) : AstWalker() {
private val subsThatNeedReturnVariable = mutableSetOf<Triple<INameScope, DataType, Position>>() private val subsThatNeedReturnVariable = mutableSetOf<Triple<IStatementContainer, DataType, Position>>()
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> { override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
@ -79,7 +80,7 @@ internal class StatementOptimizer(private val program: Program,
val functionName = functionCallStatement.target.nameInSource[0] val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in functions.purefunctionNames) { if (functionName in functions.purefunctionNames) {
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position) errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
return listOf(IAstModification.Remove(functionCallStatement, functionCallStatement.definingScope)) return listOf(IAstModification.Remove(functionCallStatement, parent as IStatementContainer))
} }
} }
@ -117,7 +118,7 @@ internal class StatementOptimizer(private val program: Program,
functionCallStatement.void, pos functionCallStatement.void, pos
) )
return listOf( return listOf(
IAstModification.InsertBefore(functionCallStatement, chrout1, parent as INameScope), IAstModification.InsertBefore(functionCallStatement, chrout1, parent as IStatementContainer),
IAstModification.ReplaceNode(functionCallStatement, chrout2, parent) IAstModification.ReplaceNode(functionCallStatement, chrout2, parent)
) )
} }
@ -130,7 +131,7 @@ internal class StatementOptimizer(private val program: Program,
if(subroutine!=null) { if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull() val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Return) if(first is Return)
return listOf(IAstModification.Remove(functionCallStatement, functionCallStatement.definingScope)) return listOf(IAstModification.Remove(functionCallStatement, parent as IStatementContainer))
} }
return noModifications return noModifications
@ -152,11 +153,11 @@ internal class StatementOptimizer(private val program: Program,
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> { override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
// remove empty if statements // remove empty if statements
if(ifStatement.truepart.containsNoCodeNorVars && ifStatement.elsepart.containsNoCodeNorVars) if(ifStatement.truepart.isEmpty() && ifStatement.elsepart.isEmpty())
return listOf(IAstModification.Remove(ifStatement, ifStatement.definingScope)) return listOf(IAstModification.Remove(ifStatement, parent as IStatementContainer))
// empty true part? switch with the else part // empty true part? switch with the else part
if(ifStatement.truepart.containsNoCodeNorVars && ifStatement.elsepart.containsCodeOrVars) { if(ifStatement.truepart.isEmpty() && ifStatement.elsepart.isNotEmpty()) {
val invertedCondition = PrefixExpression("not", ifStatement.condition, ifStatement.condition.position) val invertedCondition = PrefixExpression("not", ifStatement.condition, ifStatement.condition.position)
val emptyscope = AnonymousScope(mutableListOf(), ifStatement.elsepart.position) val emptyscope = AnonymousScope(mutableListOf(), ifStatement.elsepart.position)
val truepart = AnonymousScope(ifStatement.elsepart.statements, ifStatement.truepart.position) val truepart = AnonymousScope(ifStatement.elsepart.statements, ifStatement.truepart.position)
@ -184,20 +185,20 @@ internal class StatementOptimizer(private val program: Program,
} }
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> { override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
if(forLoop.body.containsNoCodeNorVars) { if(forLoop.body.isEmpty()) {
errors.warn("removing empty for loop", forLoop.position) errors.warn("removing empty for loop", forLoop.position)
return listOf(IAstModification.Remove(forLoop, forLoop.definingScope)) return listOf(IAstModification.Remove(forLoop, parent as IStatementContainer))
} else if(forLoop.body.statements.size==1) { } else if(forLoop.body.statements.size==1) {
val loopvar = forLoop.body.statements[0] as? VarDecl val loopvar = forLoop.body.statements[0] as? VarDecl
if(loopvar!=null && loopvar.name==forLoop.loopVar.nameInSource.singleOrNull()) { if(loopvar!=null && loopvar.name==forLoop.loopVar.nameInSource.singleOrNull()) {
// remove empty for loop (only loopvar decl in it) // remove empty for loop (only loopvar decl in it)
return listOf(IAstModification.Remove(forLoop, forLoop.definingScope)) return listOf(IAstModification.Remove(forLoop, parent as IStatementContainer))
} }
} }
val range = forLoop.iterable as? RangeExpr val range = forLoop.iterable as? RangeExpr
if(range!=null) { if(range!=null) {
if (range.size(compTarget) == 1) { if (range.size() == 1) {
// for loop over a (constant) range of just a single value-- optimize the loop away // for loop over a (constant) range of just a single value-- optimize the loop away
// loopvar/reg = range value , follow by block // loopvar/reg = range value , follow by block
val scope = AnonymousScope(mutableListOf(), forLoop.position) val scope = AnonymousScope(mutableListOf(), forLoop.position)
@ -268,7 +269,7 @@ internal class StatementOptimizer(private val program: Program,
} else { } else {
// always false -> remove the while statement altogether // always false -> remove the while statement altogether
errors.warn("condition is always false", whileLoop.condition.position) errors.warn("condition is always false", whileLoop.condition.position)
listOf(IAstModification.Remove(whileLoop, whileLoop.definingScope)) listOf(IAstModification.Remove(whileLoop, parent as IStatementContainer))
} }
} }
return noModifications return noModifications
@ -277,14 +278,14 @@ internal class StatementOptimizer(private val program: Program,
override fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> { override fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> {
val iter = repeatLoop.iterations val iter = repeatLoop.iterations
if(iter!=null) { if(iter!=null) {
if(repeatLoop.body.containsNoCodeNorVars) { if(repeatLoop.body.isEmpty()) {
errors.warn("empty loop removed", repeatLoop.position) errors.warn("empty loop removed", repeatLoop.position)
return listOf(IAstModification.Remove(repeatLoop, repeatLoop.definingScope)) return listOf(IAstModification.Remove(repeatLoop, parent as IStatementContainer))
} }
val iterations = iter.constValue(program)?.number?.toInt() val iterations = iter.constValue(program)?.number?.toInt()
if (iterations == 0) { if (iterations == 0) {
errors.warn("iterations is always 0, removed loop", iter.position) errors.warn("iterations is always 0, removed loop", iter.position)
return listOf(IAstModification.Remove(repeatLoop, repeatLoop.definingScope)) return listOf(IAstModification.Remove(repeatLoop, parent as IStatementContainer))
} }
if (iterations == 1) { if (iterations == 1) {
errors.warn("iterations is always 1", iter.position) errors.warn("iterations is always 1", iter.position)
@ -296,10 +297,10 @@ internal class StatementOptimizer(private val program: Program,
override fun after(jump: Jump, parent: Node): Iterable<IAstModification> { override fun after(jump: Jump, parent: Node): Iterable<IAstModification> {
// if the jump is to the next statement, remove the jump // if the jump is to the next statement, remove the jump
val scope = jump.definingScope val scope = jump.parent as IStatementContainer
val label = jump.identifier?.targetStatement(program) val label = jump.identifier?.targetStatement(program)
if(label!=null && scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1) if(label!=null && scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1)
return listOf(IAstModification.Remove(jump, jump.definingScope)) return listOf(IAstModification.Remove(jump, scope))
return noModifications return noModifications
} }
@ -332,7 +333,7 @@ internal class StatementOptimizer(private val program: Program,
) )
return listOf( return listOf(
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent), IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
IAstModification.InsertAfter(assignment, addConstant, assignment.definingScope)) IAstModification.InsertAfter(assignment, addConstant, parent as IStatementContainer))
} else if (op2 == "-") { } else if (op2 == "-") {
// A = A +/- B - N // A = A +/- B - N
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position) val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
@ -343,7 +344,7 @@ internal class StatementOptimizer(private val program: Program,
) )
return listOf( return listOf(
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent), IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
IAstModification.InsertAfter(assignment, subConstant, assignment.definingScope)) IAstModification.InsertAfter(assignment, subConstant, parent as IStatementContainer))
} }
} }
} }
@ -365,7 +366,7 @@ internal class StatementOptimizer(private val program: Program,
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> { override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if(assignment.target isSameAs assignment.value) { if(assignment.target isSameAs assignment.value) {
// remove assignment to self // remove assignment to self
return listOf(IAstModification.Remove(assignment, assignment.definingScope)) return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
} }
val targetIDt = assignment.target.inferType(program) val targetIDt = assignment.target.inferType(program)
@ -385,7 +386,7 @@ internal class StatementOptimizer(private val program: Program,
when (bexpr.operator) { when (bexpr.operator) {
"+" -> { "+" -> {
if (rightCv == 0.0) { if (rightCv == 0.0) {
return listOf(IAstModification.Remove(assignment, assignment.definingScope)) return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) { } else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0) { if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0) {
// replace by several INCs if it's not a memory address (inc on a memory mapped register doesn't work very well) // replace by several INCs if it's not a memory address (inc on a memory mapped register doesn't work very well)
@ -399,7 +400,7 @@ internal class StatementOptimizer(private val program: Program,
} }
"-" -> { "-" -> {
if (rightCv == 0.0) { if (rightCv == 0.0) {
return listOf(IAstModification.Remove(assignment, assignment.definingScope)) return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) { } else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0) { if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0) {
// replace by several DECs if it's not a memory address (dec on a memory mapped register doesn't work very well) // replace by several DECs if it's not a memory address (dec on a memory mapped register doesn't work very well)
@ -411,18 +412,18 @@ internal class StatementOptimizer(private val program: Program,
} }
} }
} }
"*" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope)) "*" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
"/" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope)) "/" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
"**" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope)) "**" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
"|" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope)) "|" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
"^" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope)) "^" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
"<<" -> { "<<" -> {
if (rightCv == 0.0) if (rightCv == 0.0)
return listOf(IAstModification.Remove(assignment, assignment.definingScope)) return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
} }
">>" -> { ">>" -> {
if (rightCv == 0.0) if (rightCv == 0.0)
return listOf(IAstModification.Remove(assignment, assignment.definingScope)) return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
} }
} }
@ -445,7 +446,7 @@ internal class StatementOptimizer(private val program: Program,
val assign = Assignment(tgt, value, returnStmt.position) val assign = Assignment(tgt, value, returnStmt.position)
val returnReplacement = Return(returnValueIntermediary2, returnStmt.position) val returnReplacement = Return(returnValueIntermediary2, returnStmt.position)
return listOf( return listOf(
IAstModification.InsertBefore(returnStmt, assign, parent as INameScope), IAstModification.InsertBefore(returnStmt, assign, parent as IStatementContainer),
IAstModification.ReplaceNode(returnStmt, returnReplacement, parent) IAstModification.ReplaceNode(returnStmt, returnReplacement, parent)
) )
} }
@ -469,7 +470,7 @@ internal class StatementOptimizer(private val program: Program,
return super.after(returnStmt, parent) return super.after(returnStmt, parent)
} }
private fun hasBreak(scope: INameScope): Boolean { private fun hasBreak(scope: IStatementContainer): Boolean {
class Searcher: IAstVisitor class Searcher: IAstVisitor
{ {

View File

@ -9,47 +9,49 @@ import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.walk.AstWalker import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter import prog8.compilerinterface.CallGraph
import prog8.compiler.astprocessing.isInRegularRAMof import prog8.compilerinterface.ICompilationTarget
import prog8.compiler.target.ICompilationTarget import prog8.compilerinterface.IErrorReporter
import prog8.compilerinterface.isInRegularRAMof
internal class UnusedCodeRemover(private val program: Program, class UnusedCodeRemover(private val program: Program,
private val errors: IErrorReporter, private val errors: IErrorReporter,
private val compTarget: ICompilationTarget): AstWalker() { private val compTarget: ICompilationTarget
): AstWalker() {
private val callgraph = CallGraph(program) private val callgraph = CallGraph(program)
override fun before(module: Module, parent: Node): Iterable<IAstModification> { override fun before(module: Module, parent: Node): Iterable<IAstModification> {
return if (!module.isLibrary && (module.containsNoCodeNorVars || callgraph.unused(module))) return if (!module.isLibrary && (module.containsNoCodeNorVars || callgraph.unused(module)))
listOf(IAstModification.Remove(module, module.definingScope)) listOf(IAstModification.Remove(module, parent as IStatementContainer))
else else
noModifications noModifications
} }
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> { override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
reportUnreachable(breakStmt, parent as INameScope) reportUnreachable(breakStmt, parent as IStatementContainer)
return emptyList() return emptyList()
} }
override fun before(jump: Jump, parent: Node): Iterable<IAstModification> { override fun before(jump: Jump, parent: Node): Iterable<IAstModification> {
reportUnreachable(jump, parent as INameScope) reportUnreachable(jump, parent as IStatementContainer)
return emptyList() return emptyList()
} }
override fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> { override fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> {
reportUnreachable(returnStmt, parent as INameScope) reportUnreachable(returnStmt, parent as IStatementContainer)
return emptyList() return emptyList()
} }
override fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> { override fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(functionCallStatement.target.nameInSource.last() == "exit") if(functionCallStatement.target.nameInSource.last() == "exit")
reportUnreachable(functionCallStatement, parent as INameScope) reportUnreachable(functionCallStatement, parent as IStatementContainer)
return emptyList() return emptyList()
} }
private fun reportUnreachable(stmt: Statement, parent: INameScope) { private fun reportUnreachable(stmt: Statement, parent: IStatementContainer) {
when(val next = parent.nextSibling(stmt)) { when(val next = stmt.nextSibling()) {
null, is Label, is Directive, is VarDecl, is InlineAssembly, is Subroutine -> {} null, is Label, is Directive, is VarDecl, is InlineAssembly, is Subroutine -> {}
else -> errors.warn("unreachable code", next.position) else -> errors.warn("unreachable code", next.position)
} }
@ -65,11 +67,11 @@ internal class UnusedCodeRemover(private val program: Program,
if (block.containsNoCodeNorVars) { if (block.containsNoCodeNorVars) {
if(block.name != internedStringsModuleName) if(block.name != internedStringsModuleName)
errors.warn("removing unused block '${block.name}'", block.position) errors.warn("removing unused block '${block.name}'", block.position)
return listOf(IAstModification.Remove(block, parent as INameScope)) return listOf(IAstModification.Remove(block, parent as IStatementContainer))
} }
if(callgraph.unused(block)) { if(callgraph.unused(block)) {
errors.warn("removing unused block '${block.name}'", block.position) errors.warn("removing unused block '${block.name}'", block.position)
return listOf(IAstModification.Remove(block, parent as INameScope)) return listOf(IAstModification.Remove(block, parent as IStatementContainer))
} }
} }
@ -84,16 +86,16 @@ internal class UnusedCodeRemover(private val program: Program,
if(subroutine.containsNoCodeNorVars) { if(subroutine.containsNoCodeNorVars) {
if(!subroutine.definingModule.isLibrary) if(!subroutine.definingModule.isLibrary)
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position) errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
val removals = mutableListOf(IAstModification.Remove(subroutine, subroutine.definingScope)) val removals = mutableListOf(IAstModification.Remove(subroutine, parent as IStatementContainer))
callgraph.calledBy[subroutine]?.let { callgraph.calledBy[subroutine]?.let {
for(node in it) for(node in it)
removals.add(IAstModification.Remove(node, node.definingScope)) removals.add(IAstModification.Remove(node, node.parent as IStatementContainer))
} }
return removals return removals
} }
if(!subroutine.definingModule.isLibrary) if(!subroutine.definingModule.isLibrary)
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position) errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
return listOf(IAstModification.Remove(subroutine, subroutine.definingScope)) return listOf(IAstModification.Remove(subroutine, parent as IStatementContainer))
} }
} }
@ -107,7 +109,7 @@ internal class UnusedCodeRemover(private val program: Program,
if (!forceOutput && !decl.autogeneratedDontRemove && !decl.sharedWithAsm && !decl.definingBlock.isInLibrary) { if (!forceOutput && !decl.autogeneratedDontRemove && !decl.sharedWithAsm && !decl.definingBlock.isInLibrary) {
if (callgraph.unused(decl)) { if (callgraph.unused(decl)) {
errors.warn("removing unused variable '${decl.name}'", decl.position) errors.warn("removing unused variable '${decl.name}'", decl.position)
return listOf(IAstModification.Remove(decl, decl.definingScope)) return listOf(IAstModification.Remove(decl, parent as IStatementContainer))
} }
} }
} }

View File

@ -0,0 +1,2 @@
Unittests for things in this module are located in the Compiler module instead,
for convenience sake - and to not spread the test cases around too much.

View File

@ -5,20 +5,19 @@ plugins {
id 'com.github.johnrengelman.shadow' version '7.1.0' id 'com.github.johnrengelman.shadow' version '7.1.0'
} }
targetCompatibility = 11 java {
sourceCompatibility = 11 toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
repositories {
mavenLocal()
mavenCentral()
maven { url "https://kotlin.bintray.com/kotlinx" }
} }
def prog8version = rootProject.file('compiler/res/version.txt').text.trim() def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
dependencies { dependencies {
implementation project(':compilerInterfaces')
implementation project(':codeOptimizers')
implementation project(':compilerAst') implementation project(':compilerAst')
implementation project(':codeGeneration')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect" // implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.3' implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.3'
@ -44,22 +43,6 @@ configurations {
} }
compileKotlin {
kotlinOptions {
jvmTarget = "11"
useIR = true
// verbose = true
// freeCompilerArgs += "-XXLanguage:+NewInference"
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "11"
useIR = true
}
}
sourceSets { sourceSets {
main { main {
java { java {
@ -84,11 +67,6 @@ application {
applicationName = 'p8compile' applicationName = 'p8compile'
} }
artifacts {
archives shadowJar
}
shadowJar { shadowJar {
archiveBaseName = 'prog8compiler' archiveBaseName = 'prog8compiler'
archiveVersion = prog8version archiveVersion = prog8version
@ -108,3 +86,5 @@ test {
events "skipped", "failed" events "skipped", "failed"
} }
} }
build.finalizedBy installDist, installShadowDist

View File

@ -22,5 +22,8 @@
<orderEntry type="library" name="jetbrains.kotlinx.cli.jvm" level="project" /> <orderEntry type="library" name="jetbrains.kotlinx.cli.jvm" level="project" />
<orderEntry type="library" name="junit.jupiter" level="project" /> <orderEntry type="library" name="junit.jupiter" level="project" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" /> <orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="module" module-name="codeOptimizers" />
<orderEntry type="module" module-name="compilerInterfaces" />
<orderEntry type="module" module-name="codeGeneration" />
</component> </component>
</module> </module>

View File

@ -12,6 +12,7 @@ floats {
const float PI = 3.141592653589793 const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586 const float TWOPI = 6.283185307179586
ubyte[5] tempvar_swap_float ; used for some swap() operations
; ---- C64 basic and kernal ROM float constants and functions ---- ; ---- C64 basic and kernal ROM float constants and functions ----

View File

@ -10,10 +10,11 @@ floats {
; ---- this block contains C-64 compatible floating point related functions ---- ; ---- this block contains C-64 compatible floating point related functions ----
; the addresses are from cx16 V39 emulator and roms! they won't work on older versions. ; the addresses are from cx16 V39 emulator and roms! they won't work on older versions.
const float PI = 3.141592653589793 const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586 const float TWOPI = 6.283185307179586
ubyte[5] tempvar_swap_float ; used for some swap() operations
; ---- ROM float functions ---- ; ---- ROM float functions ----

View File

@ -6,10 +6,12 @@ prog8_lib {
%asminclude "library:prog8_lib.asm" %asminclude "library:prog8_lib.asm"
%asminclude "library:prog8_funcs.asm" %asminclude "library:prog8_funcs.asm"
; TODO these retval variables are no longer used???
uword @zp retval_interm_uw ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size) uword @zp retval_interm_uw ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
word @zp retval_interm_w ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size) word @zp retval_interm_w ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
ubyte @zp retval_interm_ub ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size) ubyte @zp retval_interm_ub ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
byte @zp retval_interm_b ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size) byte @zp retval_interm_b ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
; NOTE: these variables are checked in the StatementReorderer (in fun after(decl: VarDecl)), for these exact names!
asmsub pattern_match(str string @AY, str pattern @R0) clobbers(Y) -> ubyte @A { asmsub pattern_match(str string @AY, str pattern @R0) clobbers(Y) -> ubyte @A {
%asm {{ %asm {{

View File

@ -1 +1 @@
7.1 7.2

View File

@ -6,7 +6,6 @@ import prog8.compiler.CompilationResult
import prog8.compiler.compileProgram import prog8.compiler.compileProgram
import prog8.compiler.target.C64Target import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target import prog8.compiler.target.Cx16Target
import prog8.parser.ParsingFailedError
import java.io.File import java.io.File
import java.nio.file.FileSystems import java.nio.file.FileSystems
import java.nio.file.Path import java.nio.file.Path
@ -36,10 +35,12 @@ private fun compileMain(args: Array<String>): Boolean {
val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".") val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".")
val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code") val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code")
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations") val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations")
val optimizeFloatExpressions by cli.option(ArgType.Boolean, fullName = "optfloatx", description = "optimize float expressions (warning: can increase program size)")
val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watches for file changes), greatly increases compilation speed") val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watches for file changes), greatly increases compilation speed")
val slowCodegenWarnings by cli.option(ArgType.Boolean, fullName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation") val slowCodegenWarnings by cli.option(ArgType.Boolean, fullName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation")
val quietAssembler by cli.option(ArgType.Boolean, fullName = "quietasm", description = "don't print assembler output results")
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available").default(C64Target.name) val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available").default(C64Target.name)
val sourceDirs by cli.option(ArgType.String, fullName="srcdirs", description = "list of extra paths to search in for imported modules").multiple().delimiter(File.pathSeparator) val sourceDirs by cli.option(ArgType.String, fullName="srcdirs", description = "list of extra paths, separated with ${File.pathSeparator}, to search in for imported modules").multiple().delimiter(File.pathSeparator)
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999) val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
try { try {
@ -74,7 +75,10 @@ private fun compileMain(args: Array<String>): Boolean {
val results = mutableListOf<CompilationResult>() val results = mutableListOf<CompilationResult>()
for(filepathRaw in moduleFiles) { for(filepathRaw in moduleFiles) {
val filepath = pathFrom(filepathRaw).normalize() val filepath = pathFrom(filepathRaw).normalize()
val compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, srcdirs, outputPath) val compilationResult = compileProgram(filepath,
dontOptimize!=true, optimizeFloatExpressions==true,
dontWriteAssembly!=true, slowCodegenWarnings==true, quietAssembler==true,
compilationTarget, srcdirs, outputPath)
results.add(compilationResult) results.add(compilationResult)
} }
@ -111,11 +115,12 @@ private fun compileMain(args: Array<String>): Boolean {
val filepath = pathFrom(filepathRaw).normalize() val filepath = pathFrom(filepathRaw).normalize()
val compilationResult: CompilationResult val compilationResult: CompilationResult
try { try {
compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, srcdirs, outputPath) compilationResult = compileProgram(filepath,
dontOptimize!=true, optimizeFloatExpressions==true,
dontWriteAssembly!=true, slowCodegenWarnings==true, quietAssembler==true,
compilationTarget, srcdirs, outputPath)
if(!compilationResult.success) if(!compilationResult.success)
return false return false
} catch (x: ParsingFailedError) {
return false
} catch (x: AstException) { } catch (x: AstException) {
return false return false
} }

View File

@ -1,3 +0,0 @@
package prog8.compiler
internal class AssemblyError(msg: String) : RuntimeException(msg)

View File

@ -1,6 +1,7 @@
package prog8.compiler package prog8.compiler
import prog8.ast.IFunctionCall import prog8.ast.IFunctionCall
import prog8.ast.IStatementContainer
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
@ -9,28 +10,18 @@ import prog8.ast.statements.*
import prog8.ast.walk.AstWalker import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification import prog8.ast.walk.IAstModification
import prog8.ast.walk.IAstVisitor import prog8.ast.walk.IAstVisitor
import prog8.compiler.astprocessing.isInRegularRAMof import prog8.compiler.astprocessing.isSubroutineParameter
import prog8.compiler.target.ICompilationTarget import prog8.compilerinterface.*
internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: IErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() { internal class BeforeAsmGenerationAstChanger(val program: Program, private val options: CompilationOptions,
private val errors: IErrorReporter) : AstWalker() {
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> { override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(decl.type==VarDeclType.VAR && decl.value != null && decl.datatype in NumericDatatypes)
throw FatalAstException("vardecls for variables, with initial numerical value, should have been rewritten as plain vardecl + assignment $decl")
subroutineVariables.add(decl.name to decl) subroutineVariables.add(decl.name to decl)
if (decl.value == null && !decl.autogeneratedDontRemove && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
// A numeric vardecl without an initial value is initialized with zero,
// unless there's already an assignment below, that initializes the value.
// This allows you to restart the program and have the same starting values of the variables
if(decl.allowInitializeWithZero)
{
val nextAssign = decl.definingScope.nextSibling(decl) as? Assignment
if (nextAssign != null && nextAssign.target isSameAs IdentifierReference(listOf(decl.name), Position.DUMMY))
decl.value = null
else {
decl.value = decl.zeroElementValue()
}
}
}
return noModifications return noModifications
} }
@ -40,8 +31,12 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
// But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF. // But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF.
if(!assignment.isAugmentable if(!assignment.isAugmentable
&& assignment.target.identifier != null && assignment.target.identifier != null
&& assignment.target.isInRegularRAMof(compTarget.machine)) { && assignment.target.isInRegularRAMof(options.compTarget.machine)) {
val binExpr = assignment.value as? BinaryExpression val binExpr = assignment.value as? BinaryExpression
if(binExpr!=null && binExpr.inferType(program).istype(DataType.FLOAT) && !options.optimizeFloatExpressions)
return noModifications
if (binExpr != null && binExpr.operator !in comparisonOperators) { if (binExpr != null && binExpr.operator !in comparisonOperators) {
if (binExpr.left !is BinaryExpression) { if (binExpr.left !is BinaryExpression) {
if (binExpr.right.referencesIdentifier(*assignment.target.identifier!!.nameInSource.toTypedArray())) { if (binExpr.right.referencesIdentifier(*assignment.target.identifier!!.nameInSource.toTypedArray())) {
@ -52,14 +47,14 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
// use the other part of the expression to split. // use the other part of the expression to split.
val assignRight = Assignment(assignment.target, binExpr.right, assignment.position) val assignRight = Assignment(assignment.target, binExpr.right, assignment.position)
return listOf( return listOf(
IAstModification.InsertBefore(assignment, assignRight, assignment.definingScope), IAstModification.InsertBefore(assignment, assignRight, parent as IStatementContainer),
IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr), IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr),
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr)) IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
} }
} else { } else {
val assignLeft = Assignment(assignment.target, binExpr.left, assignment.position) val assignLeft = Assignment(assignment.target, binExpr.left, assignment.position)
return listOf( return listOf(
IAstModification.InsertBefore(assignment, assignLeft, assignment.definingScope), IAstModification.InsertBefore(assignment, assignLeft, parent as IStatementContainer),
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr)) IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
} }
} }
@ -74,33 +69,35 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> { override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
subroutineVariables.clear() subroutineVariables.clear()
addedIfConditionVars.clear() addedIfConditionVars.clear()
if(!subroutine.isAsmSubroutine) {
// change 'str' parameters into 'uword' (just treat it as an address)
val stringParams = subroutine.parameters.filter { it.type==DataType.STR }
val parameterChanges = stringParams.map {
val uwordParam = SubroutineParameter(it.name, DataType.UWORD, it.position)
IAstModification.ReplaceNode(it, uwordParam, subroutine)
}
val stringParamNames = stringParams.map { it.name }.toSet()
val varsChanges = subroutine.statements
.filterIsInstance<VarDecl>()
.filter { it.autogeneratedDontRemove && it.name in stringParamNames }
.map {
val newvar = VarDecl(it.type, DataType.UWORD, it.zeropage, null, it.name, null, false, true, it.sharedWithAsm, it.position)
IAstModification.ReplaceNode(it, newvar, subroutine)
}
return parameterChanges + varsChanges
}
return noModifications return noModifications
} }
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> { override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
if(scope.statements.any { it is VarDecl || it is IStatementContainer })
throw FatalAstException("anonymousscope may no longer contain any vardecls or subscopes")
val decls = scope.statements.filterIsInstance<VarDecl>().filter { it.type == VarDeclType.VAR } val decls = scope.statements.filterIsInstance<VarDecl>().filter { it.type == VarDeclType.VAR }
subroutineVariables.addAll(decls.map { it.name to it }) subroutineVariables.addAll(decls.map { it.name to it })
val sub = scope.definingSubroutine
if (sub != null) {
// move any remaining vardecls of the scope into the upper scope. Make sure the position remains the same!
val replacements = mutableListOf<IAstModification>()
val movements = mutableListOf<IAstModification.InsertFirst>()
for(decl in decls) {
if(decl.value!=null && decl.datatype in NumericDatatypes) {
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
val assign = Assignment(target, decl.value!!, decl.position)
replacements.add(IAstModification.ReplaceNode(decl, assign, scope))
decl.value = null
decl.allowInitializeWithZero = false
} else {
replacements.add(IAstModification.Remove(decl, scope))
}
movements.add(IAstModification.InsertFirst(decl, sub))
}
return replacements + movements
}
return noModifications return noModifications
} }
@ -146,7 +143,8 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> { override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
// see if we can remove superfluous typecasts (outside of expressions) // see if we can remove superfluous typecasts (outside of expressions)
// such as casting byte<->ubyte, word<->uword // such as casting byte<->ubyte, word<->uword
// Also the special typecast of a reference type (str, array) to an UWORD will be changed into address-of. // Also the special typecast of a reference type (str, array) to an UWORD will be changed into address-of,
// UNLESS it's a str parameter in the containing subroutine - then we remove the typecast altogether
val sourceDt = typecast.expression.inferType(program).getOr(DataType.UNDEFINED) val sourceDt = typecast.expression.inferType(program).getOr(DataType.UNDEFINED)
if (typecast.type in ByteDatatypes && sourceDt in ByteDatatypes if (typecast.type in ByteDatatypes && sourceDt in ByteDatatypes
|| typecast.type in WordDatatypes && sourceDt in WordDatatypes) { || typecast.type in WordDatatypes && sourceDt in WordDatatypes) {
@ -155,22 +153,23 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
} }
} }
// Note: for various reasons (most importantly, code simplicity), the code generator assumes/requires
// that the types of assignment values and their target are the same,
// and that the types of both operands of a binaryexpression node are the same.
// So, it is not easily possible to remove the typecasts that are there to make these conditions true.
// The only place for now where we can do this is for:
// asmsub register pair parameter.
if(sourceDt in PassByReferenceDatatypes) { if(sourceDt in PassByReferenceDatatypes) {
if(typecast.type==DataType.UWORD) { if(typecast.type==DataType.UWORD) {
if(typecast.expression is IdentifierReference) { val identifier = typecast.expression as? IdentifierReference
return listOf(IAstModification.ReplaceNode( if(identifier!=null) {
return if(identifier.isSubroutineParameter(program)) {
listOf(IAstModification.ReplaceNode(
typecast, typecast,
AddressOf(typecast.expression as IdentifierReference, typecast.position), typecast.expression,
parent parent
)) ))
} else {
listOf(IAstModification.ReplaceNode(
typecast,
AddressOf(identifier, typecast.position),
parent
))
}
} else if(typecast.expression is IFunctionCall) { } else if(typecast.expression is IFunctionCall) {
return listOf(IAstModification.ReplaceNode( return listOf(IAstModification.ReplaceNode(
typecast, typecast,
@ -186,7 +185,15 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
return noModifications return noModifications
} }
@Suppress("DuplicatedCode")
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> { override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
val prefixExpr = ifStatement.condition as? PrefixExpression
if(prefixExpr!=null && prefixExpr.operator=="not") {
// if not x -> if x==0
val booleanExpr = BinaryExpression(prefixExpr.expression, "==", NumericLiteralValue.optimalInteger(0, ifStatement.condition.position), ifStatement.condition.position)
return listOf(IAstModification.ReplaceNode(ifStatement.condition, booleanExpr, ifStatement))
}
val binExpr = ifStatement.condition as? BinaryExpression val binExpr = ifStatement.condition as? BinaryExpression
if(binExpr==null || binExpr.operator !in comparisonOperators) { if(binExpr==null || binExpr.operator !in comparisonOperators) {
// if x -> if x!=0, if x+5 -> if x+5 != 0 // if x -> if x!=0, if x+5 -> if x+5 != 0
@ -197,52 +204,20 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
if((binExpr.operator=="==" || binExpr.operator=="!=") && if((binExpr.operator=="==" || binExpr.operator=="!=") &&
(binExpr.left as? NumericLiteralValue)?.number==0 && (binExpr.left as? NumericLiteralValue)?.number==0 &&
(binExpr.right as? NumericLiteralValue)?.number!=0) (binExpr.right as? NumericLiteralValue)?.number!=0)
throw CompilerException("if 0==X should have been swapped to if X==0") throw InternalCompilerException("if 0==X should have been swapped to if X==0")
// split the conditional expression into separate variables if the operand(s) is not simple.
// DISABLED FOR NOW AS IT GENEREATES LARGER CODE IN THE SIMPLE CASES LIKE IF X {...} or IF NOT X {...}
// val modifications = mutableListOf<IAstModification>()
// if(!binExpr.left.isSimple) {
// val sub = binExpr.definingSubroutine()!!
// val (variable, isNew, assignment) = addIfOperandVar(sub, "left", binExpr.left)
// if(isNew)
// modifications.add(IAstModification.InsertFirst(variable, sub))
// modifications.add(IAstModification.InsertBefore(ifStatement, assignment, parent as INameScope))
// modifications.add(IAstModification.ReplaceNode(binExpr.left, IdentifierReference(listOf(variable.name), binExpr.position), binExpr))
// addedIfConditionVars.add(Pair(sub, variable.name))
// }
// if(!binExpr.right.isSimple) {
// val sub = binExpr.definingSubroutine()!!
// val (variable, isNew, assignment) = addIfOperandVar(sub, "right", binExpr.right)
// if(isNew)
// modifications.add(IAstModification.InsertFirst(variable, sub))
// modifications.add(IAstModification.InsertBefore(ifStatement, assignment, parent as INameScope))
// modifications.add(IAstModification.ReplaceNode(binExpr.right, IdentifierReference(listOf(variable.name), binExpr.position), binExpr))
// addedIfConditionVars.add(Pair(sub, variable.name))
// }
// return modifications
return noModifications return noModifications
} }
// private fun addIfOperandVar(sub: Subroutine, side: String, operand: Expression): Triple<VarDecl, Boolean, Assignment> { @Suppress("DuplicatedCode")
// val dt = operand.inferType(program).typeOrElse(DataType.UNDEFINED)
// val varname = "prog8_ifvar_${side}_${dt.name.toLowerCase()}"
// val tgt = AssignTarget(IdentifierReference(listOf(varname), operand.position), null, null, operand.position)
// val assign = Assignment(tgt, operand, operand.position)
// if(Pair(sub, varname) in addedIfConditionVars) {
// val vardecl = VarDecl(VarDeclType.VAR, dt, ZeropageWish.DONTCARE, null, varname, null, null, false, true, operand.position)
// return Triple(vardecl, false, assign)
// }
// val existing = sub.statements.firstOrNull { it is VarDecl && it.name == varname} as VarDecl?
// return if (existing == null) {
// val vardecl = VarDecl(VarDeclType.VAR, dt, ZeropageWish.DONTCARE, null, varname, null, null, false, true, operand.position)
// Triple(vardecl, true, assign)
// } else {
// Triple(existing, false, assign)
// }
// }
override fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> { override fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
val prefixExpr = untilLoop.condition as? PrefixExpression
if(prefixExpr!=null && prefixExpr.operator=="not") {
// until not x -> until x==0
val booleanExpr = BinaryExpression(prefixExpr.expression, "==", NumericLiteralValue.optimalInteger(0, untilLoop.condition.position), untilLoop.condition.position)
return listOf(IAstModification.ReplaceNode(untilLoop.condition, booleanExpr, untilLoop))
}
val binExpr = untilLoop.condition as? BinaryExpression val binExpr = untilLoop.condition as? BinaryExpression
if(binExpr==null || binExpr.operator !in comparisonOperators) { if(binExpr==null || binExpr.operator !in comparisonOperators) {
// until x -> until x!=0, until x+5 -> until x+5 != 0 // until x -> until x!=0, until x+5 -> until x+5 != 0
@ -252,7 +227,15 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
return noModifications return noModifications
} }
@Suppress("DuplicatedCode")
override fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> { override fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
val prefixExpr = whileLoop.condition as? PrefixExpression
if(prefixExpr!=null && prefixExpr.operator=="not") {
// while not x -> while x==0
val booleanExpr = BinaryExpression(prefixExpr.expression, "==", NumericLiteralValue.optimalInteger(0, whileLoop.condition.position), whileLoop.condition.position)
return listOf(IAstModification.ReplaceNode(whileLoop.condition, booleanExpr, whileLoop))
}
val binExpr = whileLoop.condition as? BinaryExpression val binExpr = whileLoop.condition as? BinaryExpression
if(binExpr==null || binExpr.operator !in comparisonOperators) { if(binExpr==null || binExpr.operator !in comparisonOperators) {
// while x -> while x!=0, while x+5 -> while x+5 != 0 // while x -> while x!=0, while x+5 -> while x+5 != 0
@ -347,7 +330,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
// assign the indexing expression to the helper variable, but only if that hasn't been done already // assign the indexing expression to the helper variable, but only if that hasn't been done already
val target = AssignTarget(IdentifierReference(listOf("cx16", register), expr.indexer.position), null, null, expr.indexer.position) val target = AssignTarget(IdentifierReference(listOf("cx16", register), expr.indexer.position), null, null, expr.indexer.position)
val assign = Assignment(target, expr.indexer.indexExpr, expr.indexer.position) val assign = Assignment(target, expr.indexer.indexExpr, expr.indexer.position)
modifications.add(IAstModification.InsertBefore(statement, assign, statement.definingScope)) modifications.add(IAstModification.InsertBefore(statement, assign, statement.parent as IStatementContainer))
modifications.add(IAstModification.ReplaceNode(expr.indexer.indexExpr, target.identifier!!.copy(), expr.indexer)) modifications.add(IAstModification.ReplaceNode(expr.indexer.indexExpr, target.identifier!!.copy(), expr.indexer))
return modifications return modifications
} }

View File

@ -1,9 +1,8 @@
package prog8.compiler package prog8.compiler
import com.github.michaelbull.result.* import com.github.michaelbull.result.*
import prog8.ast.AstToSourceCode import prog8.ast.AstToSourceTextConverter
import prog8.ast.IBuiltinFunctions import prog8.ast.IBuiltinFunctions
import prog8.ast.IMemSizer
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.AstException import prog8.ast.base.AstException
import prog8.ast.base.Position import prog8.ast.base.Position
@ -11,73 +10,39 @@ import prog8.ast.expressions.Expression
import prog8.ast.expressions.NumericLiteralValue import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.Directive import prog8.ast.statements.Directive
import prog8.compiler.astprocessing.* import prog8.compiler.astprocessing.*
import prog8.compiler.functions.*
import prog8.compiler.target.C64Target import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target import prog8.compiler.target.Cx16Target
import prog8.compiler.target.ICompilationTarget import prog8.compiler.target.cpu6502.codegen.AsmGen
import prog8.compiler.target.asmGeneratorFor import prog8.compilerinterface.*
import prog8.optimizer.* import prog8.optimizer.*
import prog8.parser.ParseError import prog8.parser.ParseError
import prog8.parser.ParsingFailedError
import prog8.parser.SourceCode
import prog8.parser.SourceCode.Companion.libraryFilePrefix
import java.io.File
import java.nio.file.Path import java.nio.file.Path
import kotlin.io.path.Path import kotlin.io.path.Path
import kotlin.io.path.nameWithoutExtension import kotlin.io.path.nameWithoutExtension
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
enum class OutputType {
RAW,
PRG
}
enum class LauncherType {
BASIC,
NONE
}
enum class ZeropageType {
BASICSAFE,
FLOATSAFE,
KERNALSAFE,
FULL,
DONTUSE
}
data class CompilationOptions(val output: OutputType,
val launcher: LauncherType,
val zeropage: ZeropageType,
val zpReserved: List<IntRange>,
val floats: Boolean,
val noSysInit: Boolean,
val compTarget: ICompilationTarget) {
var slowCodegenWarnings = false
var optimize = false
}
class CompilerException(message: String?) : Exception(message)
class CompilationResult(val success: Boolean, class CompilationResult(val success: Boolean,
val programAst: Program, val program: Program,
val programName: String, val programName: String,
val compTarget: ICompilationTarget, val compTarget: ICompilationTarget,
val importedFiles: List<Path>) val importedFiles: List<Path>)
// TODO refactor the gigantic list of parameters
fun compileProgram(filepath: Path, fun compileProgram(filepath: Path,
optimize: Boolean, optimize: Boolean,
optimizeFloatExpressions: Boolean,
writeAssembly: Boolean, writeAssembly: Boolean,
slowCodegenWarnings: Boolean, slowCodegenWarnings: Boolean,
quietAssembler: Boolean,
compilationTarget: String, compilationTarget: String,
sourceDirs: List<String>, sourceDirs: List<String>,
outputDir: Path): CompilationResult { outputDir: Path,
errors: IErrorReporter = ErrorReporter()): CompilationResult {
var programName = "" var programName = ""
lateinit var programAst: Program lateinit var program: Program
lateinit var importedFiles: List<Path> lateinit var importedFiles: List<Path>
val errors = ErrorReporter()
val compTarget = val compTarget =
when(compilationTarget) { when(compilationTarget) {
@ -89,31 +54,35 @@ fun compileProgram(filepath: Path,
try { try {
val totalTime = measureTimeMillis { val totalTime = measureTimeMillis {
// import main module and everything it needs // import main module and everything it needs
val (ast, compilationOptions, imported) = parseImports(filepath, errors, compTarget, sourceDirs) val (programresult, compilationOptions, imported) = parseImports(filepath, errors, compTarget, sourceDirs)
compilationOptions.slowCodegenWarnings = slowCodegenWarnings with(compilationOptions) {
compilationOptions.optimize = optimize this.slowCodegenWarnings = slowCodegenWarnings
programAst = ast this.optimize = optimize
this.optimizeFloatExpressions = optimizeFloatExpressions
}
program = programresult
importedFiles = imported importedFiles = imported
processAst(programAst, errors, compilationOptions) processAst(program, errors, compilationOptions)
if (compilationOptions.optimize) if (compilationOptions.optimize)
optimizeAst( optimizeAst(
programAst, program,
compilationOptions,
errors, errors,
BuiltinFunctionsFacade(BuiltinFunctions), BuiltinFunctionsFacade(BuiltinFunctions),
compTarget, compTarget
compilationOptions
) )
postprocessAst(programAst, errors, compilationOptions) postprocessAst(program, errors, compilationOptions)
// printAst(programAst) // println("*********** AST BEFORE ASSEMBLYGEN *************")
// printAst(program)
if (writeAssembly) { if (writeAssembly) {
val result = writeAssembly(programAst, errors, outputDir, compilationOptions) val result = writeAssembly(program, errors, outputDir, quietAssembler, compilationOptions)
when (result) { when (result) {
is WriteAssemblyResult.Ok -> programName = result.filename is WriteAssemblyResult.Ok -> programName = result.filename
is WriteAssemblyResult.Fail -> { is WriteAssemblyResult.Fail -> {
System.err.println(result.error) System.err.println(result.error)
return CompilationResult(false, programAst, programName, compTarget, importedFiles) return CompilationResult(false, program, programName, compTarget, importedFiles)
} }
} }
} }
@ -121,14 +90,20 @@ fun compileProgram(filepath: Path,
System.out.flush() System.out.flush()
System.err.flush() System.err.flush()
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.") println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
return CompilationResult(true, programAst, programName, compTarget, importedFiles) return CompilationResult(true, program, programName, compTarget, importedFiles)
} catch (px: ParseError) { } catch (px: ParseError) {
System.err.print("\u001b[91m") // bright red System.err.print("\u001b[91m") // bright red
System.err.println("${px.position.toClickableStr()} parse error: ${px.message}".trim()) System.err.println("${px.position.toClickableStr()} parse error: ${px.message}".trim())
System.err.print("\u001b[0m") // reset System.err.print("\u001b[0m") // reset
} catch (pfx: ParsingFailedError) { } catch (ac: AbortCompilation) {
if(!ac.message.isNullOrEmpty()) {
System.err.print("\u001b[91m") // bright red
System.err.println(ac.message)
System.err.print("\u001b[0m") // reset
}
} catch (nsf: NoSuchFileException) {
System.err.print("\u001b[91m") // bright red System.err.print("\u001b[91m") // bright red
System.err.println(pfx.message) System.err.println("File not found: ${nsf.message}")
System.err.print("\u001b[0m") // reset System.err.print("\u001b[0m") // reset
} catch (ax: AstException) { } catch (ax: AstException) {
System.err.print("\u001b[91m") // bright red System.err.print("\u001b[91m") // bright red
@ -148,7 +123,7 @@ fun compileProgram(filepath: Path,
throw x throw x
} }
val failedProgram = Program("failed", BuiltinFunctionsFacade(BuiltinFunctions), compTarget) val failedProgram = Program("failed", BuiltinFunctionsFacade(BuiltinFunctions), compTarget, compTarget)
return CompilationResult(false, failedProgram, programName, compTarget, emptyList()) return CompilationResult(false, failedProgram, programName, compTarget, emptyList())
} }
@ -158,13 +133,13 @@ private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuilt
override val names = functions.keys override val names = functions.keys
override val purefunctionNames = functions.filter { it.value.pure }.map { it.key }.toSet() override val purefunctionNames = functions.filter { it.value.pure }.map { it.key }.toSet()
override fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue? { override fun constValue(name: String, args: List<Expression>, position: Position): NumericLiteralValue? {
val func = BuiltinFunctions[name] val func = BuiltinFunctions[name]
if(func!=null) { if(func!=null) {
val exprfunc = func.constExpressionFunc val exprfunc = func.constExpressionFunc
if(exprfunc!=null) { if(exprfunc!=null) {
return try { return try {
exprfunc(args, position, program, memsizer) exprfunc(args, position, program)
} catch(x: NotConstArgumentException) { } catch(x: NotConstArgumentException) {
// const-evaluating the builtin function call failed. // const-evaluating the builtin function call failed.
null null
@ -188,21 +163,18 @@ fun parseImports(filepath: Path,
sourceDirs: List<String>): Triple<Program, CompilationOptions, List<Path>> { sourceDirs: List<String>): Triple<Program, CompilationOptions, List<Path>> {
println("Compiler target: ${compTarget.name}. Parsing...") println("Compiler target: ${compTarget.name}. Parsing...")
val bf = BuiltinFunctionsFacade(BuiltinFunctions) val bf = BuiltinFunctionsFacade(BuiltinFunctions)
val programAst = Program(filepath.nameWithoutExtension, bf, compTarget) val program = Program(filepath.nameWithoutExtension, bf, compTarget, compTarget)
bf.program = programAst bf.program = program
val importer = ModuleImporter(programAst, compTarget.name, errors, sourceDirs) val importer = ModuleImporter(program, compTarget.name, errors, sourceDirs)
val importedModuleResult = importer.importModule(filepath) val importedModuleResult = importer.importModule(filepath)
importedModuleResult.onFailure { throw it } importedModuleResult.onFailure { throw it }
errors.report() errors.report()
val importedFiles = programAst.modules.map { it.source } val importedFiles = program.modules.map { it.source }
.filter { it.isFromFilesystem } .filter { it.isFromFilesystem }
.map { Path(it.origin) } .map { Path(it.origin) }
val compilerOptions = determineCompilationOptions(programAst, compTarget) val compilerOptions = determineCompilationOptions(program, compTarget)
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
// depending on the machine and compiler options we may have to include some libraries // depending on the machine and compiler options we may have to include some libraries
for(lib in compTarget.machine.importLibs(compilerOptions, compTarget.name)) for(lib in compTarget.machine.importLibs(compilerOptions, compTarget.name))
importer.importLibraryModule(lib) importer.importLibraryModule(lib)
@ -210,8 +182,12 @@ fun parseImports(filepath: Path,
// always import prog8_lib and math // always import prog8_lib and math
importer.importLibraryModule("math") importer.importLibraryModule("math")
importer.importLibraryModule("prog8_lib") importer.importLibraryModule("prog8_lib")
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
errors.err("BASIC launcher requires output type PRG", program.toplevelModule.position)
errors.report() errors.report()
return Triple(programAst, compilerOptions, importedFiles)
return Triple(program, compilerOptions, importedFiles)
} }
fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget): CompilationOptions { fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget): CompilationOptions {
@ -274,44 +250,44 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
) )
} }
private fun processAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) { private fun processAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
// perform initial syntax checks and processings // perform initial syntax checks and processings
println("Processing for target ${compilerOptions.compTarget.name}...") println("Processing for target ${compilerOptions.compTarget.name}...")
programAst.checkIdentifiers(errors, compilerOptions) program.preprocessAst()
program.checkIdentifiers(errors, compilerOptions)
errors.report() errors.report()
// TODO: turning char literals into UBYTEs via an encoding should really happen in code gen - but for that we'd need DataType.CHAR // TODO: turning char literals into UBYTEs via an encoding should really happen in code gen - but for that we'd need DataType.CHAR
// NOTE: we will then lose the opportunity to do constant-folding on any expression containing a char literal, but how often will those occur? // ...but what do we gain from this? We can leave it as it is now: where a char literal is no more than syntactic sugar for an UBYTE value.
// Also they might be optimized away eventually in codegen or by the assembler even // By introduction a CHAR dt, we will also lose the opportunity to do constant-folding on any expression containing a char literal.
programAst.charLiteralsToUByteLiterals(errors, compilerOptions.compTarget) program.charLiteralsToUByteLiterals(compilerOptions.compTarget)
program.constantFold(errors, compilerOptions.compTarget)
errors.report() errors.report()
programAst.constantFold(errors, compilerOptions.compTarget) program.reorderStatements(errors)
errors.report() errors.report()
programAst.reorderStatements(errors) program.addTypecasts(errors)
errors.report() errors.report()
programAst.addTypecasts(errors) program.variousCleanups(program, errors)
errors.report() errors.report()
programAst.variousCleanups(programAst, errors) program.checkValid(errors, compilerOptions)
errors.report() errors.report()
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget) program.checkIdentifiers(errors, compilerOptions)
errors.report()
programAst.checkIdentifiers(errors, compilerOptions)
errors.report() errors.report()
} }
private fun optimizeAst(programAst: Program, errors: IErrorReporter, functions: IBuiltinFunctions, compTarget: ICompilationTarget, options: CompilationOptions) { private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, errors: IErrorReporter, functions: IBuiltinFunctions, compTarget: ICompilationTarget) {
// optimize the parse tree // optimize the parse tree
println("Optimizing...") println("Optimizing...")
val remover = UnusedCodeRemover(programAst, errors, compTarget) val remover = UnusedCodeRemover(program, errors, compTarget)
remover.visit(programAst) remover.visit(program)
remover.applyModifications() remover.applyModifications()
while (true) { while (true) {
// keep optimizing expressions and statements until no more steps remain // keep optimizing expressions and statements until no more steps remain
val optsDone1 = programAst.simplifyExpressions() val optsDone1 = program.simplifyExpressions()
val optsDone2 = programAst.splitBinaryExpressions(compTarget) val optsDone2 = program.splitBinaryExpressions(compilerOptions, compTarget)
val optsDone3 = programAst.optimizeStatements(errors, functions, compTarget) val optsDone3 = program.optimizeStatements(errors, functions, compTarget)
programAst.constantFold(errors, compTarget) // because simplified statements and expressions can result in more constants that can be folded away program.constantFold(errors, compTarget) // because simplified statements and expressions can result in more constants that can be folded away
errors.report() errors.report()
if (optsDone1 + optsDone2 + optsDone3 == 0) if (optsDone1 + optsDone2 + optsDone3 == 0)
break break
@ -320,17 +296,17 @@ private fun optimizeAst(programAst: Program, errors: IErrorReporter, functions:
errors.report() errors.report()
} }
private fun postprocessAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) { private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
programAst.addTypecasts(errors) program.addTypecasts(errors)
errors.report() errors.report()
programAst.variousCleanups(programAst, errors) program.variousCleanups(program, errors)
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget) // check if final tree is still valid program.checkValid(errors, compilerOptions) // check if final tree is still valid
errors.report() errors.report()
val callGraph = CallGraph(programAst) val callGraph = CallGraph(program)
callGraph.checkRecursiveCalls(errors) callGraph.checkRecursiveCalls(errors)
errors.report() errors.report()
programAst.verifyFunctionArgTypes() program.verifyFunctionArgTypes()
programAst.moveMainAndStartToFirst() program.moveMainAndStartToFirst()
} }
private sealed class WriteAssemblyResult { private sealed class WriteAssemblyResult {
@ -338,58 +314,56 @@ private sealed class WriteAssemblyResult {
class Fail(val error: String): WriteAssemblyResult() class Fail(val error: String): WriteAssemblyResult()
} }
private fun writeAssembly(programAst: Program, private fun writeAssembly(program: Program,
errors: IErrorReporter, errors: IErrorReporter,
outputDir: Path, outputDir: Path,
compilerOptions: CompilationOptions): WriteAssemblyResult { quietAssembler: Boolean,
compilerOptions: CompilationOptions
): WriteAssemblyResult {
// asm generation directly from the Ast // asm generation directly from the Ast
programAst.processAstBeforeAsmGeneration(errors, compilerOptions.compTarget) program.processAstBeforeAsmGeneration(compilerOptions, errors)
errors.report() errors.report()
// printAst(programAst) // println("*********** AST RIGHT BEFORE ASM GENERATION *************")
// printAst(program)
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions) compilerOptions.compTarget.machine.initializeZeropage(compilerOptions)
val assembly = asmGeneratorFor(compilerOptions.compTarget, val assembly = asmGeneratorFor(compilerOptions.compTarget,
programAst, program,
errors, errors,
compilerOptions.compTarget.machine.zeropage, compilerOptions.compTarget.machine.zeropage,
compilerOptions, compilerOptions,
outputDir).compileToAssembly() outputDir).compileToAssembly()
errors.report()
return if(assembly.valid && errors.noErrors()) { return if(assembly.valid && errors.noErrors()) {
val assemblerReturnStatus = assembly.assemble(compilerOptions) val assemblerReturnStatus = assembly.assemble(quietAssembler, compilerOptions)
if(assemblerReturnStatus!=0) if(assemblerReturnStatus!=0)
WriteAssemblyResult.Fail("assembler step failed with return code $assemblerReturnStatus") WriteAssemblyResult.Fail("assembler step failed with return code $assemblerReturnStatus")
else { else {
errors.report()
WriteAssemblyResult.Ok(assembly.name) WriteAssemblyResult.Ok(assembly.name)
} }
} else { } else {
errors.report()
WriteAssemblyResult.Fail("compiler failed with errors") WriteAssemblyResult.Fail("compiler failed with errors")
} }
} }
fun printAst(programAst: Program) { fun printAst(program: Program) {
println() println()
val printer = AstToSourceCode(::print, programAst) val printer = AstToSourceTextConverter(::print, program)
printer.visit(programAst) printer.visit(program)
println() println()
} }
internal fun loadAsmIncludeFile(filename: String, source: SourceCode): Result<String, NoSuchFileException> { internal fun asmGeneratorFor(
return if (filename.startsWith(libraryFilePrefix)) { compTarget: ICompilationTarget,
return runCatching { program: Program,
val stream = object {}.javaClass.getResourceAsStream("/prog8lib/${filename.substring(libraryFilePrefix.length)}") // TODO handle via SourceCode errors: IErrorReporter,
stream!!.bufferedReader().use { r -> r.readText() } zp: Zeropage,
}.mapError { NoSuchFileException(File(filename)) } options: CompilationOptions,
} else { outputDir: Path
// first try in the isSameAs folder as where the containing file was imported from ): IAssemblyGenerator
val sib = Path(source.origin).resolveSibling(filename) {
// at the moment we only have one code generation backend (for 6502 and 65c02)
if (sib.toFile().isFile) return AsmGen(program, errors, zp, options, compTarget, outputDir)
Ok(sib.toFile().readText())
else
Ok(File(filename).readText())
}
} }

View File

@ -1,16 +1,7 @@
package prog8.compiler package prog8.compiler
import prog8.ast.base.Position import prog8.ast.base.Position
import prog8.parser.ParsingFailedError import prog8.compilerinterface.IErrorReporter
interface IErrorReporter {
fun err(msg: String, position: Position)
fun warn(msg: String, position: Position)
fun noErrors(): Boolean
fun report()
}
internal class ErrorReporter: IErrorReporter { internal class ErrorReporter: IErrorReporter {
private enum class MessageSeverity { private enum class MessageSeverity {
@ -33,24 +24,29 @@ internal class ErrorReporter: IErrorReporter {
var numErrors = 0 var numErrors = 0
var numWarnings = 0 var numWarnings = 0
messages.forEach { messages.forEach {
val printer = when(it.severity) {
MessageSeverity.WARNING -> System.out
MessageSeverity.ERROR -> System.err
}
when(it.severity) { when(it.severity) {
MessageSeverity.ERROR -> System.err.print("\u001b[91m") // bright red MessageSeverity.ERROR -> printer.print("\u001b[91m") // bright red
MessageSeverity.WARNING -> System.err.print("\u001b[93m") // bright yellow MessageSeverity.WARNING -> printer.print("\u001b[93m") // bright yellow
} }
val msg = "${it.position.toClickableStr()} ${it.severity} ${it.message}".trim() val msg = "${it.position.toClickableStr()} ${it.severity} ${it.message}".trim()
if(msg !in alreadyReportedMessages) { if(msg !in alreadyReportedMessages) {
System.err.println(msg) printer.println(msg)
alreadyReportedMessages.add(msg) alreadyReportedMessages.add(msg)
when(it.severity) { when(it.severity) {
MessageSeverity.WARNING -> numWarnings++ MessageSeverity.WARNING -> numWarnings++
MessageSeverity.ERROR -> numErrors++ MessageSeverity.ERROR -> numErrors++
} }
} }
System.err.print("\u001b[0m") // reset color printer.print("\u001b[0m") // reset color
} }
System.out.flush()
System.err.flush()
messages.clear() messages.clear()
if(numErrors>0) finalizeNumErrors(numErrors, numWarnings)
throw ParsingFailedError("There are $numErrors errors and $numWarnings warnings.")
} }
override fun noErrors() = messages.none { it.severity==MessageSeverity.ERROR } override fun noErrors() = messages.none { it.severity==MessageSeverity.ERROR }

View File

@ -7,6 +7,7 @@ import prog8.ast.base.Position
import prog8.ast.base.SyntaxError import prog8.ast.base.SyntaxError
import prog8.ast.statements.Directive import prog8.ast.statements.Directive
import prog8.ast.statements.DirectiveArg import prog8.ast.statements.DirectiveArg
import prog8.compilerinterface.IErrorReporter
import prog8.parser.Prog8Parser import prog8.parser.Prog8Parser
import prog8.parser.SourceCode import prog8.parser.SourceCode
import java.io.File import java.io.File
@ -33,7 +34,7 @@ class ModuleImporter(private val program: Program,
val srcPath = when (candidates.size) { val srcPath = when (candidates.size) {
0 -> return Err(NoSuchFileException( 0 -> return Err(NoSuchFileException(
file = filePath.normalize().toFile(), file = filePath.normalize().toFile(),
reason = "searched in $searchIn")) reason = "Searched in $searchIn"))
1 -> candidates.first() 1 -> candidates.first()
else -> candidates.first() // when more candiates, pick the one from the first location else -> candidates.first() // when more candiates, pick the one from the first location
} }
@ -105,7 +106,7 @@ class ModuleImporter(private val program: Program,
importModule(it) importModule(it)
}, },
failure = { failure = {
errors.err("no module found with name $moduleName", import.position) errors.err("no module found with name $moduleName. Searched in: $sourcePaths (and internal libraries)", import.position)
return null return null
} }
) )
@ -142,7 +143,6 @@ class ModuleImporter(private val program: Program,
} else { } else {
val dropCurDir = if(sourcePaths.isNotEmpty() && sourcePaths[0].name == ".") 1 else 0 val dropCurDir = if(sourcePaths.isNotEmpty() && sourcePaths[0].name == ".") 1 else 0
sourcePaths.drop(dropCurDir) + sourcePaths.drop(dropCurDir) +
// TODO: won't work until Prog8Parser is fixed s.t. it fully initializes the modules it returns. // hm, what won't work?)
listOf(Path(importingModule.position.file).parent ?: Path("")) + listOf(Path(importingModule.position.file).parent ?: Path("")) +
listOf(Path(".", "prog8lib")) listOf(Path(".", "prog8lib"))
} }

View File

@ -1,27 +1,22 @@
package prog8.compiler.astprocessing package prog8.compiler.astprocessing
import prog8.ast.INameScope import prog8.ast.INameScope
import prog8.ast.IStatementContainer
import prog8.ast.Module import prog8.ast.Module
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor import prog8.ast.walk.IAstVisitor
import prog8.compiler.CompilationOptions import prog8.compilerinterface.*
import prog8.compiler.IErrorReporter
import prog8.compiler.ZeropageType
import prog8.compiler.functions.BuiltinFunctions
import prog8.compiler.functions.builtinFunctionReturnType
import prog8.compiler.target.ICompilationTarget
import java.io.CharConversionException import java.io.CharConversionException
import java.io.File import java.io.File
import java.util.* import java.util.*
import kotlin.io.path.* import kotlin.io.path.Path
internal class AstChecker(private val program: Program, internal class AstChecker(private val program: Program,
private val compilerOptions: CompilationOptions,
private val errors: IErrorReporter, private val errors: IErrorReporter,
private val compTarget: ICompilationTarget private val compilerOptions: CompilationOptions
) : IAstVisitor { ) : IAstVisitor {
override fun visit(program: Program) { override fun visit(program: Program) {
@ -31,7 +26,7 @@ internal class AstChecker(private val program: Program,
if(mainBlocks.size>1) if(mainBlocks.size>1)
errors.err("more than one 'main' block", mainBlocks[0].position) errors.err("more than one 'main' block", mainBlocks[0].position)
if(mainBlocks.isEmpty()) if(mainBlocks.isEmpty())
errors.err("there is no 'main' block", program.modules.firstOrNull()?.position ?: program.position) errors.err("there is no 'main' block", program.modules.firstOrNull()?.position ?: Position.DUMMY)
for(mainBlock in mainBlocks) { for(mainBlock in mainBlocks) {
val startSub = mainBlock.subScope("start") as? Subroutine val startSub = mainBlock.subScope("start") as? Subroutine
@ -196,8 +191,12 @@ internal class AstChecker(private val program: Program,
is Label, is Label,
is VarDecl, is VarDecl,
is InlineAssembly, is InlineAssembly,
is INameScope, is IStatementContainer,
is NopStatement -> true is NopStatement -> true
is Assignment -> {
val target = statement.target.identifier!!.targetStatement(program)
target === statement.previousSibling() // an initializer assignment is okay
}
else -> false else -> false
} }
if (!ok) { if (!ok) {
@ -217,7 +216,7 @@ internal class AstChecker(private val program: Program,
super.visit(label) super.visit(label)
} }
private fun hasReturnOrJump(scope: INameScope): Boolean { private fun hasReturnOrJump(scope: IStatementContainer): Boolean {
class Searcher: IAstVisitor class Searcher: IAstVisitor
{ {
var count=0 var count=0
@ -245,8 +244,8 @@ internal class AstChecker(private val program: Program,
if(subroutine.name in BuiltinFunctions) if(subroutine.name in BuiltinFunctions)
err("cannot redefine a built-in function") err("cannot redefine a built-in function")
if(subroutine.parameters.size>16) if(subroutine.parameters.size>6 && !subroutine.isAsmSubroutine)
err("subroutines are limited to 16 parameters") errors.warn("subroutine has a large number of parameters, this slows down code execution a lot", subroutine.position)
val uniqueNames = subroutine.parameters.asSequence().map { it.name }.toSet() val uniqueNames = subroutine.parameters.asSequence().map { it.name }.toSet()
if(uniqueNames.size!=subroutine.parameters.size) if(uniqueNames.size!=subroutine.parameters.size)
@ -290,7 +289,7 @@ internal class AstChecker(private val program: Program,
else if(param.second.registerOrPair in arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) { else if(param.second.registerOrPair in arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
if (param.first.type != DataType.UWORD && param.first.type != DataType.WORD if (param.first.type != DataType.UWORD && param.first.type != DataType.WORD
&& param.first.type != DataType.STR && param.first.type !in ArrayDatatypes && param.first.type != DataType.FLOAT) && param.first.type != DataType.STR && param.first.type !in ArrayDatatypes && param.first.type != DataType.FLOAT)
err("parameter '${param.first.name}' should be (u)word/address") err("parameter '${param.first.name}' should be (u)word (an address) or str")
} }
else if(param.second.statusflag!=null) { else if(param.second.statusflag!=null) {
if (param.first.type != DataType.UBYTE) if (param.first.type != DataType.UBYTE)
@ -382,10 +381,12 @@ internal class AstChecker(private val program: Program,
err("can only use Carry as status flag parameter") err("can only use Carry as status flag parameter")
} else { } else {
// Pass-by-reference datatypes can not occur as parameters to a subroutine directly // Non-string Pass-by-reference datatypes can not occur as parameters to a subroutine directly
// Instead, their reference (address) should be passed (as an UWORD). // Instead, their reference (address) should be passed (as an UWORD).
if(subroutine.parameters.any{it.type in PassByReferenceDatatypes }) { for(p in subroutine.parameters) {
err("Pass-by-reference types (str, array) cannot occur as a parameter type directly. Instead, use an uword to receive their address, or access the variable from the outer scope directly.") if(p.type in PassByReferenceDatatypes && p.type != DataType.STR) {
err("Non-string pass-by-reference types cannot occur as a parameter type directly. Instead, use an uword to receive their address, or access the variable from the outer scope directly.")
}
} }
} }
} }
@ -453,7 +454,7 @@ internal class AstChecker(private val program: Program,
val targetIdentifier = assignTarget.identifier val targetIdentifier = assignTarget.identifier
if (targetIdentifier != null) { if (targetIdentifier != null) {
val targetName = targetIdentifier.nameInSource val targetName = targetIdentifier.nameInSource
when (val targetSymbol = program.namespace.lookup(targetName, assignment)) { when (val targetSymbol = assignment.definingScope.lookup(targetName)) {
null -> { null -> {
errors.err("undefined symbol: ${targetIdentifier.nameInSource.joinToString(".")}", targetIdentifier.position) errors.err("undefined symbol: ${targetIdentifier.nameInSource.joinToString(".")}", targetIdentifier.position)
return return
@ -505,7 +506,7 @@ internal class AstChecker(private val program: Program,
} }
override fun visit(decl: VarDecl) { override fun visit(decl: VarDecl) {
fun err(msg: String, position: Position?=null) = errors.err(msg, position ?: decl.position) fun err(msg: String) = errors.err(msg, decl.position)
// the initializer value can't refer to the variable itself (recursive definition) // the initializer value can't refer to the variable itself (recursive definition)
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexExpr?.referencesIdentifier(decl.name) == true) if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexExpr?.referencesIdentifier(decl.name) == true)
@ -586,10 +587,10 @@ internal class AstChecker(private val program: Program,
val numvalue = decl.value as? NumericLiteralValue val numvalue = decl.value as? NumericLiteralValue
if(numvalue!=null) { if(numvalue!=null) {
if (numvalue.type !in IntegerDatatypes || numvalue.number.toInt() < 0 || numvalue.number.toInt() > 65535) { if (numvalue.type !in IntegerDatatypes || numvalue.number.toInt() < 0 || numvalue.number.toInt() > 65535) {
err("memory address must be valid integer 0..\$ffff", decl.value?.position) err("memory address must be valid integer 0..\$ffff")
} }
} else { } else {
err("value of memory mapped variable can only be a fixed number, perhaps you meant to use an address pointer type instead?", decl.value?.position) err("value of memory mapped variable can only be a fixed number, perhaps you meant to use an address pointer type instead?")
} }
} }
} }
@ -597,7 +598,7 @@ internal class AstChecker(private val program: Program,
val declValue = decl.value val declValue = decl.value
if(declValue!=null && decl.type==VarDeclType.VAR) { if(declValue!=null && decl.type==VarDeclType.VAR) {
if (declValue.inferType(program) isnot decl.datatype) { if (declValue.inferType(program) isnot decl.datatype) {
err("initialisation value has incompatible type (${declValue.inferType(program)}) for the variable (${decl.datatype})", declValue.position) err("initialisation value has incompatible type (${declValue.inferType(program)}) for the variable (${decl.datatype})")
} }
} }
@ -626,10 +627,13 @@ internal class AstChecker(private val program: Program,
} }
} }
// string assignment is not supported in a vard
if(decl.datatype==DataType.STR) { if(decl.datatype==DataType.STR) {
if(decl.value==null) if(decl.value==null) {
err("string var must be initialized with a string literal") // complain about uninitialized str, but only if it's a regular variable
val parameter = (decl.parent as? Subroutine)?.parameters?.singleOrNull{ it.name==decl.name }
if(parameter==null)
err("string var must be initialized with a string literal")
}
else if (decl.type==VarDeclType.VAR && decl.value !is StringLiteralValue) else if (decl.type==VarDeclType.VAR && decl.value !is StringLiteralValue)
err("string var can only be initialized with a string literal") err("string var can only be initialized with a string literal")
} }
@ -768,7 +772,7 @@ internal class AstChecker(private val program: Program,
override fun visit(char: CharLiteral) { override fun visit(char: CharLiteral) {
try { // just *try* if it can be encoded, don't actually do it try { // just *try* if it can be encoded, don't actually do it
compTarget.encodeString(char.value.toString(), char.altEncoding) compilerOptions.compTarget.encodeString(char.value.toString(), char.altEncoding)
} catch (cx: CharConversionException) { } catch (cx: CharConversionException) {
errors.err(cx.message ?: "can't encode character", char.position) errors.err(cx.message ?: "can't encode character", char.position)
} }
@ -780,7 +784,7 @@ internal class AstChecker(private val program: Program,
checkValueTypeAndRangeString(DataType.STR, string) checkValueTypeAndRangeString(DataType.STR, string)
try { // just *try* if it can be encoded, don't actually do it try { // just *try* if it can be encoded, don't actually do it
compTarget.encodeString(string.value, string.altEncoding) compilerOptions.compTarget.encodeString(string.value, string.altEncoding)
} catch (cx: CharConversionException) { } catch (cx: CharConversionException) {
errors.err(cx.message ?: "can't encode string", string.position) errors.err(cx.message ?: "can't encode string", string.position)
} }
@ -789,11 +793,10 @@ internal class AstChecker(private val program: Program,
} }
override fun visit(expr: PrefixExpression) { override fun visit(expr: PrefixExpression) {
val idt = expr.inferType(program) val dt = expr.expression.inferType(program).getOr(DataType.UNDEFINED)
if(!idt.isKnown) if(dt==DataType.UNDEFINED)
return // any error should be reported elsewhere return // any error should be reported elsewhere
val dt = idt.getOr(DataType.UNDEFINED)
if(expr.operator=="-") { if(expr.operator=="-") {
if (dt != DataType.BYTE && dt != DataType.WORD && dt != DataType.FLOAT) { if (dt != DataType.BYTE && dt != DataType.WORD && dt != DataType.FLOAT) {
errors.err("can only take negative of a signed number type", expr.position) errors.err("can only take negative of a signed number type", expr.position)
@ -1067,7 +1070,7 @@ internal class AstChecker(private val program: Program,
override fun visit(postIncrDecr: PostIncrDecr) { override fun visit(postIncrDecr: PostIncrDecr) {
if(postIncrDecr.target.identifier != null) { if(postIncrDecr.target.identifier != null) {
val targetName = postIncrDecr.target.identifier!!.nameInSource val targetName = postIncrDecr.target.identifier!!.nameInSource
val target = program.namespace.lookup(targetName, postIncrDecr) val target = postIncrDecr.definingScope.lookup(targetName)
if(target==null) { if(target==null) {
val symbol = postIncrDecr.target.identifier!! val symbol = postIncrDecr.target.identifier!!
errors.err("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position) errors.err("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)
@ -1254,7 +1257,7 @@ internal class AstChecker(private val program: Program,
// check if the floating point values are all within range // check if the floating point values are all within range
val doubles = value.value.map {it.constValue(program)?.number!!.toDouble()}.toDoubleArray() val doubles = value.value.map {it.constValue(program)?.number!!.toDouble()}.toDoubleArray()
if(doubles.any { it < compTarget.machine.FLOAT_MAX_NEGATIVE || it > compTarget.machine.FLOAT_MAX_POSITIVE }) if(doubles.any { it < compilerOptions.compTarget.machine.FLOAT_MAX_NEGATIVE || it > compilerOptions.compTarget.machine.FLOAT_MAX_POSITIVE })
return err("floating point value overflow") return err("floating point value overflow")
return true return true
} }

View File

@ -3,74 +3,25 @@ package prog8.compiler.astprocessing
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.DataType import prog8.ast.base.DataType
import prog8.ast.base.VarDeclType import prog8.ast.expressions.CharLiteral
import prog8.ast.expressions.* import prog8.ast.expressions.IdentifierReference
import prog8.ast.statements.AssignTarget import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.Directive import prog8.ast.statements.Directive
import prog8.ast.walk.AstWalker import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification import prog8.ast.walk.IAstModification
import prog8.compiler.BeforeAsmGenerationAstChanger import prog8.compiler.BeforeAsmGenerationAstChanger
import prog8.compiler.CompilationOptions import prog8.compilerinterface.CompilationOptions
import prog8.compiler.IErrorReporter import prog8.compilerinterface.IErrorReporter
import prog8.compiler.IStringEncoding import prog8.compilerinterface.IStringEncoding
import prog8.compiler.target.ICompilationTarget
import prog8.compiler.target.IMachineDefinition
import kotlin.math.abs
fun RangeExpr.size(encoding: IStringEncoding): Int? { internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: CompilationOptions) {
val fromLv = (from as? NumericLiteralValue) val checker = AstChecker(this, errors, compilerOptions)
val toLv = (to as? NumericLiteralValue)
if(fromLv==null || toLv==null)
return null
return toConstantIntegerRange(encoding)?.count()
}
fun RangeExpr.toConstantIntegerRange(encoding: IStringEncoding): IntProgression? {
val fromVal: Int
val toVal: Int
val fromString = from as? StringLiteralValue
val toString = to as? StringLiteralValue
if(fromString!=null && toString!=null ) {
// string range -> int range over character values
fromVal = encoding.encodeString(fromString.value, fromString.altEncoding)[0].toInt()
toVal = encoding.encodeString(toString.value, fromString.altEncoding)[0].toInt()
} else {
val fromLv = from as? NumericLiteralValue
val toLv = to as? NumericLiteralValue
if(fromLv==null || toLv==null)
return null // non-constant range
// integer range
fromVal = fromLv.number.toInt()
toVal = toLv.number.toInt()
}
val stepVal = (step as? NumericLiteralValue)?.number?.toInt() ?: 1
return makeRange(fromVal, toVal, stepVal)
}
private fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
return when {
fromVal <= toVal -> when {
stepVal <= 0 -> IntRange.EMPTY
stepVal == 1 -> fromVal..toVal
else -> fromVal..toVal step stepVal
}
else -> when {
stepVal >= 0 -> IntRange.EMPTY
stepVal == -1 -> fromVal downTo toVal
else -> fromVal downTo toVal step abs(stepVal)
}
}
}
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: IErrorReporter, compTarget: ICompilationTarget) {
val checker = AstChecker(this, compilerOptions, errors, compTarget)
checker.visit(this) checker.visit(this)
} }
internal fun Program.processAstBeforeAsmGeneration(errors: IErrorReporter, compTarget: ICompilationTarget) { internal fun Program.processAstBeforeAsmGeneration(compilerOptions: CompilationOptions, errors: IErrorReporter) {
val fixer = BeforeAsmGenerationAstChanger(this, errors, compTarget) val fixer = BeforeAsmGenerationAstChanger(this, compilerOptions, errors)
fixer.visit(this) fixer.visit(this)
while(errors.noErrors() && fixer.applyModifications()>0) { while(errors.noErrors() && fixer.applyModifications()>0) {
fixer.visit(this) fixer.visit(this)
@ -88,7 +39,7 @@ internal fun Program.reorderStatements(errors: IErrorReporter) {
} }
} }
internal fun Program.charLiteralsToUByteLiterals(errors: IErrorReporter, enc: IStringEncoding) { internal fun Program.charLiteralsToUByteLiterals(enc: IStringEncoding) {
val walker = object : AstWalker() { val walker = object : AstWalker() {
override fun after(char: CharLiteral, parent: Node): Iterable<IAstModification> { override fun after(char: CharLiteral, parent: Node): Iterable<IAstModification> {
return listOf(IAstModification.ReplaceNode( return listOf(IAstModification.ReplaceNode(
@ -113,9 +64,17 @@ internal fun Program.verifyFunctionArgTypes() {
fixer.visit(this) fixer.visit(this)
} }
internal fun Program.preprocessAst() {
val transforms = AstPreprocessor()
transforms.visit(this)
var mods = transforms.applyModifications()
while(mods>0)
mods = transforms.applyModifications()
}
internal fun Program.checkIdentifiers(errors: IErrorReporter, options: CompilationOptions) { internal fun Program.checkIdentifiers(errors: IErrorReporter, options: CompilationOptions) {
val checker2 = AstIdentifiersChecker(this, errors, options.compTarget) val checker2 = AstIdentifiersChecker(errors, options.compTarget)
checker2.visit(this) checker2.visit(this)
if(errors.noErrors()) { if(errors.noErrors()) {
@ -135,7 +94,6 @@ internal fun Program.variousCleanups(program: Program, errors: IErrorReporter) {
process.applyModifications() process.applyModifications()
} }
internal fun Program.moveMainAndStartToFirst() { internal fun Program.moveMainAndStartToFirst() {
// the module containing the program entrypoint is moved to the first in the sequence. // the module containing the program entrypoint is moved to the first in the sequence.
// the "main" block containing the entrypoint is moved to the top in there, // the "main" block containing the entrypoint is moved to the top in there,
@ -166,46 +124,10 @@ internal fun Program.moveMainAndStartToFirst() {
} }
} }
internal fun AssignTarget.isInRegularRAMof(machine: IMachineDefinition): Boolean { internal fun IdentifierReference.isSubroutineParameter(program: Program): Boolean {
val memAddr = memoryAddress val vardecl = this.targetVarDecl(program)
val arrayIdx = arrayindexed if(vardecl!=null && vardecl.autogeneratedDontRemove) {
val ident = identifier return vardecl.definingSubroutine?.parameters?.any { it.name==vardecl.name } == true
when {
memAddr != null -> {
return when (memAddr.addressExpression) {
is NumericLiteralValue -> {
machine.isRegularRAMaddress((memAddr.addressExpression as NumericLiteralValue).number.toInt())
}
is IdentifierReference -> {
val program = definingModule.program
val decl = (memAddr.addressExpression as IdentifierReference).targetVarDecl(program)
if ((decl?.type == VarDeclType.VAR || decl?.type == VarDeclType.CONST) && decl.value is NumericLiteralValue)
machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
else
false
}
else -> false
}
}
arrayIdx != null -> {
val program = definingModule.program
val targetStmt = arrayIdx.arrayvar.targetVarDecl(program)
return if (targetStmt?.type == VarDeclType.MEMORY) {
val addr = targetStmt.value as? NumericLiteralValue
if (addr != null)
machine.isRegularRAMaddress(addr.number.toInt())
else
false
} else true
}
ident != null -> {
val program = definingModule.program
val decl = ident.targetVarDecl(program)!!
return if (decl.type == VarDeclType.MEMORY && decl.value is NumericLiteralValue)
machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
else
true
}
else -> return true
} }
return false
} }

View File

@ -1,15 +1,14 @@
package prog8.compiler.astprocessing package prog8.compiler.astprocessing
import prog8.ast.Program
import prog8.ast.base.Position import prog8.ast.base.Position
import prog8.ast.expressions.StringLiteralValue import prog8.ast.expressions.StringLiteralValue
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor import prog8.ast.walk.IAstVisitor
import prog8.compiler.IErrorReporter import prog8.compilerinterface.BuiltinFunctions
import prog8.compiler.functions.BuiltinFunctions import prog8.compilerinterface.ICompilationTarget
import prog8.compiler.target.ICompilationTarget import prog8.compilerinterface.IErrorReporter
internal class AstIdentifiersChecker(private val program: Program, private val errors: IErrorReporter, private val compTarget: ICompilationTarget) : IAstVisitor { internal class AstIdentifiersChecker(private val errors: IErrorReporter, private val compTarget: ICompilationTarget) : IAstVisitor {
private var blocks = mutableMapOf<String, Block>() private var blocks = mutableMapOf<String, Block>()
private fun nameError(name: String, position: Position, existing: Statement) { private fun nameError(name: String, position: Position, existing: Statement) {
@ -42,7 +41,7 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
if(decl.name in compTarget.machine.opcodeNames) if(decl.name in compTarget.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position) errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position)
val existing = program.namespace.lookup(listOf(decl.name), decl) val existing = decl.definingScope.lookup(listOf(decl.name))
if (existing != null && existing !== decl) if (existing != null && existing !== decl)
nameError(decl.name, decl.position, existing) nameError(decl.name, decl.position, existing)
@ -65,22 +64,19 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
// if (subroutine.parameters.any { it.name in BuiltinFunctions }) // if (subroutine.parameters.any { it.name in BuiltinFunctions })
// checkResult.add(NameError("builtin function name cannot be used as parameter", subroutine.position)) // checkResult.add(NameError("builtin function name cannot be used as parameter", subroutine.position))
val existing = program.namespace.lookup(listOf(subroutine.name), subroutine) val existing = subroutine.lookup(listOf(subroutine.name))
if (existing != null && existing !== subroutine) if (existing != null && existing !== subroutine)
nameError(subroutine.name, subroutine.position, existing) nameError(subroutine.name, subroutine.position, existing)
// check that there are no local variables, labels, or other subs that redefine the subroutine's parameters. Blocks are okay. // check that there are no local symbols (variables, labels, subs) that redefine the subroutine's parameters.
val symbolsInSub = subroutine.allDefinedSymbols val symbolsInSub = subroutine.allDefinedSymbols
val namesInSub = symbolsInSub.map{ it.first }.toSet() val namesInSub = symbolsInSub.map{ it.first }.toSet()
val paramNames = subroutine.parameters.map { it.name }.toSet() val paramNames = subroutine.parameters.map { it.name }.toSet()
val paramsToCheck = paramNames.intersect(namesInSub) val paramsToCheck = paramNames.intersect(namesInSub)
for(name in paramsToCheck) { for(name in paramsToCheck) {
val labelOrVar = subroutine.getLabelOrVariable(name) val symbol = subroutine.searchSymbol(name)
if(labelOrVar!=null && labelOrVar.position != subroutine.position) if(symbol!=null && symbol.position != subroutine.position)
nameError(name, labelOrVar.position, subroutine) nameError(name, symbol.position, subroutine)
val sub = subroutine.statements.firstOrNull { it is Subroutine && it.name==name}
if(sub!=null)
nameError(name, subroutine.position, sub)
} }
if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) { if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) {

View File

@ -0,0 +1,48 @@
package prog8.compiler.astprocessing
import prog8.ast.Node
import prog8.ast.base.NumericDatatypes
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.IdentifierReference
import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.statements.VarDecl
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
class AstPreprocessor : AstWalker() {
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
// move vardecls in Anonymous scope up to the containing subroutine
// and add initialization assignment in its place if needed
val vars = scope.statements.filterIsInstance<VarDecl>()
val parentscope = scope.definingScope
if(vars.any() && parentscope !== parent) {
val movements = mutableListOf<IAstModification>()
val replacements = mutableListOf<IAstModification>()
for(decl in vars) {
if(decl.type != VarDeclType.VAR) {
movements.add(IAstModification.InsertFirst(decl, parentscope))
replacements.add(IAstModification.Remove(decl, scope))
} else {
if(decl.value!=null && decl.datatype in NumericDatatypes) {
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
val assign = Assignment(target, decl.value!!, decl.position)
replacements.add(IAstModification.ReplaceNode(decl, assign, scope))
decl.value = null
decl.allowInitializeWithZero = false
} else {
replacements.add(IAstModification.Remove(decl, scope))
}
movements.add(IAstModification.InsertFirst(decl, parentscope))
}
}
return movements + replacements
}
return noModifications
}
}

View File

@ -67,7 +67,7 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
} }
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> { override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
return replacePointerVarIndexWithMemread(program, arrayIndexedExpression, parent) return replacePointerVarIndexWithMemreadOrMemwrite(program, arrayIndexedExpression, parent)
} }
private fun concatString(expr: BinaryExpression): StringLiteralValue? { private fun concatString(expr: BinaryExpression): StringLiteralValue? {
@ -99,12 +99,12 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
internal fun replacePointerVarIndexWithMemread(program: Program, arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> { internal fun replacePointerVarIndexWithMemreadOrMemwrite(program: Program, arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program) val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)
if(arrayVar!=null && arrayVar.datatype == DataType.UWORD) { if(arrayVar!=null && arrayVar.datatype == DataType.UWORD) {
// rewrite pointervar[index] into @(pointervar+index) // rewrite pointervar[index] into @(pointervar+index)
val indexer = arrayIndexedExpression.indexer val indexer = arrayIndexedExpression.indexer
val add = BinaryExpression(arrayIndexedExpression.arrayvar, "+", indexer.indexExpr, arrayIndexedExpression.position) val add = BinaryExpression(arrayIndexedExpression.arrayvar.copy(), "+", indexer.indexExpr, arrayIndexedExpression.position)
return if(parent is AssignTarget) { return if(parent is AssignTarget) {
// we're part of the target of an assignment, we have to actually change the assign target itself // we're part of the target of an assignment, we have to actually change the assign target itself
val memwrite = DirectMemoryWrite(add, arrayIndexedExpression.position) val memwrite = DirectMemoryWrite(add, arrayIndexedExpression.position)

View File

@ -1,5 +1,6 @@
package prog8.compiler.astprocessing package prog8.compiler.astprocessing
import prog8.ast.IStatementContainer
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.DataType import prog8.ast.base.DataType
@ -40,11 +41,13 @@ internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
// this array literal is part of an expression, turn it into an identifier reference // this array literal is part of an expression, turn it into an identifier reference
val litval2 = array.cast(arrayDt.getOr(DataType.UNDEFINED)) val litval2 = array.cast(arrayDt.getOr(DataType.UNDEFINED))
if(litval2!=null) { if(litval2!=null) {
if(array.parent !is IStatementContainer)
return noModifications
val vardecl2 = VarDecl.createAuto(litval2) val vardecl2 = VarDecl.createAuto(litval2)
val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position) val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position)
return listOf( return listOf(
IAstModification.ReplaceNode(array, identifier, parent), IAstModification.ReplaceNode(array, identifier, parent),
IAstModification.InsertFirst(vardecl2, array.definingScope) IAstModification.InsertFirst(vardecl2, array.parent as IStatementContainer)
) )
} }
} }

View File

@ -1,17 +1,13 @@
package prog8.compiler.astprocessing package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall import prog8.ast.*
import prog8.ast.Module import prog8.ast.base.*
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.walk.AstWalker import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter import prog8.compilerinterface.BuiltinFunctions
import prog8.compiler.functions.BuiltinFunctions import prog8.compilerinterface.IErrorReporter
internal class StatementReorderer(val program: Program, val errors: IErrorReporter) : AstWalker() { internal class StatementReorderer(val program: Program, val errors: IErrorReporter) : AstWalker() {
@ -38,15 +34,62 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
module.statements.add(0, mainBlock) module.statements.add(0, mainBlock)
} }
reorderVardeclsAndDirectives(module.statements) directivesToTheTop(module.statements)
return noModifications return noModifications
} }
private fun reorderVardeclsAndDirectives(statements: MutableList<Statement>) { private val declsProcessedWithInitAssignment = mutableSetOf<VarDecl>()
val varDecls = statements.filterIsInstance<VarDecl>()
statements.removeAll(varDecls)
statements.addAll(0, varDecls)
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
if(decl !in declsProcessedWithInitAssignment) {
declsProcessedWithInitAssignment.add(decl)
if (decl.value == null) {
if (!decl.autogeneratedDontRemove && decl.allowInitializeWithZero) {
// A numeric vardecl without an initial value is initialized with zero,
// unless there's already an assignment below it, that initializes the value (or a for loop that uses it as loopvar).
// This allows you to restart the program and have the same starting values of the variables
// So basically consider 'ubyte xx' as a short form for 'ubyte xx; xx=0'
decl.value = null
if(decl.name.startsWith("retval_interm_") && decl.definingScope.name=="prog8_lib") {
// no need to zero out the special internal returnvalue intermediates.
// TODO these variables are no longer used???
return noModifications
}
val nextStmt = decl.nextSibling()
val nextAssign = nextStmt as? Assignment
val nextFor = nextStmt as? ForLoop
val hasNextForWithThisLoopvar = nextFor?.loopVar?.nameInSource==listOf(decl.name)
val hasNextAssignment = nextAssign!=null && nextAssign.target isSameAs IdentifierReference(listOf(decl.name), Position.DUMMY)
if (!hasNextAssignment && !hasNextForWithThisLoopvar) {
// Add assignment to initialize with zero
// Note: for block-level vars, this will introduce assignments in the block scope. These have to be dealt with correctly later.
val identifier = IdentifierReference(listOf(decl.name), decl.position)
val assignzero = Assignment(AssignTarget(identifier, null, null, decl.position), decl.zeroElementValue(), decl.position)
return listOf(IAstModification.InsertAfter(
decl, assignzero, parent as IStatementContainer
))
}
}
} else {
// Transform the vardecl with initvalue to a plain vardecl + assignment
// this allows for other optimizations to kick in.
// So basically consider 'ubyte xx=99' as a short form for 'ubyte xx; xx=99'
val pos = decl.value!!.position
val identifier = IdentifierReference(listOf(decl.name), pos)
val assign = Assignment(AssignTarget(identifier, null, null, pos), decl.value!!, pos)
decl.value = null
return listOf(IAstModification.InsertAfter(
decl, assign, parent as IStatementContainer
))
}
}
}
return noModifications
}
private fun directivesToTheTop(statements: MutableList<Statement>) {
val directives = statements.filterIsInstance<Directive>().filter {it.directive in directivesToMove} val directives = statements.filterIsInstance<Directive>().filter {it.directive in directivesToMove}
statements.removeAll(directives) statements.removeAll(directives)
statements.addAll(0, directives) statements.addAll(0, directives)
@ -61,7 +104,7 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
) )
} }
reorderVardeclsAndDirectives(block.statements) directivesToTheTop(block.statements)
return noModifications return noModifications
} }
@ -86,7 +129,12 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
} }
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> { override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
return replacePointerVarIndexWithMemread(program, arrayIndexedExpression, parent) if(parent !is VarDecl) {
// don't replace the initializer value in a vardecl - this will be moved to a separate
// assignment statement soon in after(VarDecl)
return replacePointerVarIndexWithMemreadOrMemwrite(program, arrayIndexedExpression, parent)
}
return noModifications
} }
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> { override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {

View File

@ -8,8 +8,8 @@ import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.walk.AstWalker import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter import prog8.compilerinterface.BuiltinFunctions
import prog8.compiler.functions.BuiltinFunctions import prog8.compilerinterface.IErrorReporter
class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalker() { class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalker() {
@ -78,10 +78,10 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
TypecastExpression(assignment.value, targettype, true, assignment.value.position), TypecastExpression(assignment.value, targettype, true, assignment.value.position),
assignment)) assignment))
} else { } else {
fun castLiteral(cvalue: NumericLiteralValue): List<IAstModification.ReplaceNode> { fun castLiteral(cvalue2: NumericLiteralValue): List<IAstModification.ReplaceNode> {
val cast = cvalue.cast(targettype) val cast = cvalue2.cast(targettype)
return if(cast.isValid) return if(cast.isValid)
listOf(IAstModification.ReplaceNode(cvalue, cast.valueOrZero(), cvalue.parent)) listOf(IAstModification.ReplaceNode(assignment.value, cast.valueOrZero(), assignment))
else else
emptyList() emptyList()
} }
@ -135,11 +135,13 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
TypecastExpression(pair.second, requiredType, true, pair.second.position), TypecastExpression(pair.second, requiredType, true, pair.second.position),
call as Node) call as Node)
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) { } else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
// we allow STR/ARRAY values in place of UWORD parameters. Take their address instead. // We allow STR/ARRAY values in place of UWORD parameters.
if(pair.second is IdentifierReference) { // Take their address instead, UNLESS it's a str parameter in the containing subroutine
val identifier = pair.second as? IdentifierReference
if(identifier?.isSubroutineParameter(program)==false) {
modifications += IAstModification.ReplaceNode( modifications += IAstModification.ReplaceNode(
call.args[index], call.args[index],
AddressOf(pair.second as IdentifierReference, pair.second.position), AddressOf(identifier, pair.second.position),
call as Node) call as Node)
} }
} else if(pair.second is NumericLiteralValue) { } else if(pair.second is NumericLiteralValue) {

View File

@ -1,7 +1,7 @@
package prog8.compiler.astprocessing package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall import prog8.ast.IFunctionCall
import prog8.ast.INameScope import prog8.ast.IStatementContainer
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.FatalAstException import prog8.ast.base.FatalAstException
@ -10,23 +10,23 @@ import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.walk.AstWalker import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter import prog8.compilerinterface.IErrorReporter
internal class VariousCleanups(val program: Program, val errors: IErrorReporter): AstWalker() { internal class VariousCleanups(val program: Program, val errors: IErrorReporter): AstWalker() {
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> { override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
return listOf(IAstModification.Remove(nopStatement, parent as INameScope)) return listOf(IAstModification.Remove(nopStatement, parent as IStatementContainer))
} }
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> { override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
return if(parent is INameScope) return if(parent is IStatementContainer)
listOf(ScopeFlatten(scope, parent as INameScope)) listOf(ScopeFlatten(scope, parent as IStatementContainer))
else else
noModifications noModifications
} }
class ScopeFlatten(val scope: AnonymousScope, val into: INameScope) : IAstModification { class ScopeFlatten(val scope: AnonymousScope, val into: IStatementContainer) : IAstModification {
override fun perform() { override fun perform() {
val idx = into.statements.indexOf(scope) val idx = into.statements.indexOf(scope)
if(idx>=0) { if(idx>=0) {

View File

@ -8,21 +8,21 @@ import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.TypecastExpression import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor import prog8.ast.walk.IAstVisitor
import prog8.compiler.CompilerException import prog8.compilerinterface.BuiltinFunctions
import prog8.compiler.functions.BuiltinFunctions import prog8.compilerinterface.InternalCompilerException
class VerifyFunctionArgTypes(val program: Program) : IAstVisitor { class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
override fun visit(functionCall: FunctionCall) { override fun visit(functionCall: FunctionCall) {
val error = checkTypes(functionCall as IFunctionCall, program) val error = checkTypes(functionCall as IFunctionCall, program)
if(error!=null) if(error!=null)
throw CompilerException(error) throw InternalCompilerException(error)
} }
override fun visit(functionCallStatement: FunctionCallStatement) { override fun visit(functionCallStatement: FunctionCallStatement) {
val error = checkTypes(functionCallStatement as IFunctionCall, program) val error = checkTypes(functionCallStatement as IFunctionCall, program)
if (error!=null) if (error!=null)
throw CompilerException(error) throw InternalCompilerException(error)
} }
companion object { companion object {

View File

@ -1,17 +0,0 @@
package prog8.compiler.target
import prog8.compiler.CompilationOptions
internal interface IAssemblyGenerator {
fun compileToAssembly(): IAssemblyProgram
}
internal const val generatedLabelPrefix = "_prog8_label_"
internal const val subroutineFloatEvalResultVar1 = "_prog8_float_eval_result1"
internal const val subroutineFloatEvalResultVar2 = "_prog8_float_eval_result2"
internal interface IAssemblyProgram {
val valid: Boolean
val name: String
fun assemble(options: CompilationOptions): Int
}

View File

@ -1,98 +0,0 @@
package prog8.compiler.target
import com.github.michaelbull.result.fold
import prog8.ast.IMemSizer
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.AssignTarget
import prog8.compiler.CompilationOptions
import prog8.compiler.IErrorReporter
import prog8.compiler.IStringEncoding
import prog8.compiler.Zeropage
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.cbm.Petscii
import prog8.compiler.target.cpu6502.codegen.AsmGen
import prog8.compiler.target.cx16.CX16MachineDefinition
import java.io.CharConversionException
import java.nio.file.Path
interface ICompilationTarget: IStringEncoding, IMemSizer {
val name: String
val machine: IMachineDefinition
override fun encodeString(str: String, altEncoding: Boolean): List<Short>
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
}
internal object C64Target: ICompilationTarget {
override val name = "c64"
override val machine = C64MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
val coded = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
return coded.fold(
failure = { throw it },
success = { it }
)
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
try {
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
} catch (x: CharConversionException) {
throw CharConversionException("can't decode string: ${x.message}")
}
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
else -> -9999999
}
}
}
internal object Cx16Target: ICompilationTarget {
override val name = "cx16"
override val machine = CX16MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
val coded= if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
return coded.fold(
failure = { throw it },
success = { it }
)
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
try {
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
} catch (x: CharConversionException) {
throw CharConversionException("can't decode string: ${x.message}")
}
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
else -> -9999999
}
}
}
internal fun asmGeneratorFor(
compTarget: ICompilationTarget,
program: Program,
errors: IErrorReporter,
zp: Zeropage,
options: CompilationOptions,
outputDir: Path
): IAssemblyGenerator
{
// at the moment we only have one code generation backend (for 6502 and 65c02)
return AsmGen(program, errors, zp, options, compTarget, outputDir)
}

View File

@ -1,24 +1,28 @@
package prog8tests package prog8tests
import kotlin.test.*
import com.github.michaelbull.result.getErrorOrElse import com.github.michaelbull.result.getErrorOrElse
import com.github.michaelbull.result.getOrElse import com.github.michaelbull.result.getOrElse
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.* import org.hamcrest.Matchers.equalTo
import org.hamcrest.Matchers.nullValue
import org.hamcrest.core.Is import org.hamcrest.core.Is
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.*
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.Disabled
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.internedStringsModuleName import prog8.ast.internedStringsModuleName
import prog8.compiler.IErrorReporter
import prog8.compiler.ModuleImporter import prog8.compiler.ModuleImporter
import prog8.compilerinterface.IErrorReporter
import prog8.parser.ParseError import prog8.parser.ParseError
import prog8.parser.SourceCode import prog8.parser.SourceCode
import prog8tests.helpers.* import prog8tests.ast.helpers.*
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.DummyFunctions
import prog8tests.helpers.DummyMemsizer
import prog8tests.helpers.DummyStringEncoder
import kotlin.io.path.* import kotlin.io.path.*
import kotlin.test.assertContains
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.fail
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ -28,7 +32,7 @@ class TestModuleImporter {
private lateinit var program: Program private lateinit var program: Program
@BeforeEach @BeforeEach
fun beforeEach() { fun beforeEach() {
program = Program("foo", DummyFunctions, DummyMemsizer) program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
} }
private fun makeImporter(errors: IErrorReporter?, vararg searchIn: String): ModuleImporter { private fun makeImporter(errors: IErrorReporter?, vararg searchIn: String): ModuleImporter {
@ -36,7 +40,7 @@ class TestModuleImporter {
} }
private fun makeImporter(errors: IErrorReporter? = null, searchIn: Iterable<String>) = private fun makeImporter(errors: IErrorReporter? = null, searchIn: Iterable<String>) =
ModuleImporter(program, "blah", errors ?: ErrorReporterForTests(), searchIn.toList()) ModuleImporter(program, "blah", errors ?: ErrorReporterForTests(false), searchIn.toList())
@Nested @Nested
inner class Constructor { inner class Constructor {
@ -243,7 +247,7 @@ class TestModuleImporter {
@Test @Test
fun testWithNonExistingName() { fun testWithNonExistingName() {
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir)) val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
val errors = ErrorReporterForTests() val errors = ErrorReporterForTests(false)
val importer = makeImporter(errors, searchIn.invariantSeparatorsPathString) val importer = makeImporter(errors, searchIn.invariantSeparatorsPathString)
val filenameNoExt = assumeNotExists(fixturesDir, "i_do_not_exist").name val filenameNoExt = assumeNotExists(fixturesDir, "i_do_not_exist").name
val filenameWithExt = assumeNotExists(fixturesDir, "i_do_not_exist.p8").name val filenameWithExt = assumeNotExists(fixturesDir, "i_do_not_exist.p8").name
@ -252,14 +256,14 @@ class TestModuleImporter {
val result = importer.importLibraryModule(filenameNoExt) val result = importer.importLibraryModule(filenameNoExt)
assertThat(count[n] + " call / NO .p8 extension", result, Is(nullValue())) assertThat(count[n] + " call / NO .p8 extension", result, Is(nullValue()))
assertFalse(errors.noErrors(), count[n] + " call / NO .p8 extension") assertFalse(errors.noErrors(), count[n] + " call / NO .p8 extension")
assertEquals(errors.errors.single(), "no module found with name i_do_not_exist") assertContains(errors.errors.single(), "0:0: no module found with name i_do_not_exist")
errors.report() errors.report()
assertThat(program.modules.size, equalTo(1)) assertThat(program.modules.size, equalTo(1))
val result2 = importer.importLibraryModule(filenameWithExt) val result2 = importer.importLibraryModule(filenameWithExt)
assertThat(count[n] + " call / with .p8 extension", result2, Is(nullValue())) assertThat(count[n] + " call / with .p8 extension", result2, Is(nullValue()))
assertFalse(importer.errors.noErrors(), count[n] + " call / with .p8 extension") assertFalse(importer.errors.noErrors(), count[n] + " call / with .p8 extension")
assertEquals(errors.errors.single(), "no module found with name i_do_not_exist.p8") assertContains(errors.errors.single(), "0:0: no module found with name i_do_not_exist.p8")
errors.report() errors.report()
assertThat(program.modules.size, equalTo(1)) assertThat(program.modules.size, equalTo(1))
} }

View File

@ -5,7 +5,7 @@ import org.junit.jupiter.api.TestInstance
import prog8.ast.statements.Block import prog8.ast.statements.Block
import prog8.ast.statements.Subroutine import prog8.ast.statements.Subroutine
import prog8.compiler.target.C64Target import prog8.compiler.target.C64Target
import prog8.optimizer.CallGraph import prog8.compilerinterface.CallGraph
import prog8tests.helpers.assertSuccess import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText import prog8tests.helpers.compileText
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -26,11 +26,11 @@ class TestCallgraph {
} }
""" """
val result = compileText(C64Target, false, sourcecode).assertSuccess() val result = compileText(C64Target, false, sourcecode).assertSuccess()
val graph = CallGraph(result.programAst) val graph = CallGraph(result.program)
assertEquals(1, graph.imports.size) assertEquals(1, graph.imports.size)
assertEquals(1, graph.importedBy.size) assertEquals(1, graph.importedBy.size)
val toplevelModule = result.programAst.toplevelModule val toplevelModule = result.program.toplevelModule
val importedModule = graph.imports.getValue(toplevelModule).single() val importedModule = graph.imports.getValue(toplevelModule).single()
assertEquals("string", importedModule.name) assertEquals("string", importedModule.name)
val importedBy = graph.importedBy.getValue(importedModule).single() val importedBy = graph.importedBy.getValue(importedModule).single()
@ -45,7 +45,7 @@ class TestCallgraph {
assertFalse(sub in graph.calls) assertFalse(sub in graph.calls)
assertFalse(sub in graph.calledBy) assertFalse(sub in graph.calledBy)
if(sub === result.programAst.entrypoint) if(sub === result.program.entrypoint)
assertFalse(graph.unused(sub), "start() should always be marked as used to avoid having it removed") assertFalse(graph.unused(sub), "start() should always be marked as used to avoid having it removed")
else else
assertTrue(graph.unused(sub)) assertTrue(graph.unused(sub))
@ -66,11 +66,11 @@ class TestCallgraph {
} }
""" """
val result = compileText(C64Target, false, sourcecode).assertSuccess() val result = compileText(C64Target, false, sourcecode).assertSuccess()
val graph = CallGraph(result.programAst) val graph = CallGraph(result.program)
assertEquals(1, graph.imports.size) assertEquals(1, graph.imports.size)
assertEquals(1, graph.importedBy.size) assertEquals(1, graph.importedBy.size)
val toplevelModule = result.programAst.toplevelModule val toplevelModule = result.program.toplevelModule
val importedModule = graph.imports.getValue(toplevelModule).single() val importedModule = graph.imports.getValue(toplevelModule).single()
assertEquals("string", importedModule.name) assertEquals("string", importedModule.name)
val importedBy = graph.importedBy.getValue(importedModule).single() val importedBy = graph.importedBy.getValue(importedModule).single()

View File

@ -7,11 +7,13 @@ import prog8.ast.base.DataType
import prog8.ast.base.VarDeclType import prog8.ast.base.VarDeclType
import prog8.ast.expressions.IdentifierReference import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.Assignment
import prog8.compiler.target.Cx16Target import prog8.compiler.target.Cx16Target
import prog8tests.helpers.assertSuccess import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText import prog8tests.helpers.compileText
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertIs import kotlin.test.assertIs
import kotlin.test.assertNull
/** /**
@ -34,7 +36,7 @@ class TestCompilerOnCharLit {
} }
""").assertSuccess() """).assertSuccess()
val program = result.programAst val program = result.program
val startSub = program.entrypoint val startSub = program.entrypoint
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0] val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]
@ -58,7 +60,7 @@ class TestCompilerOnCharLit {
} }
""").assertSuccess() """).assertSuccess()
val program = result.programAst val program = result.program
val startSub = program.entrypoint val startSub = program.entrypoint
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0] val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]
@ -73,9 +75,11 @@ class TestCompilerOnCharLit {
// val initializerValue = decl.value as CharLiteral // val initializerValue = decl.value as CharLiteral
// assertEquals('\n', (initializerValue as CharLiteral).value) // assertEquals('\n', (initializerValue as CharLiteral).value)
assertIs<NumericLiteralValue>(decl.value, assertNull(decl.value, "initializer value should have been moved to separate assignment")
"char literal should have been replaced by ubyte literal") val assignInitialValue = decl.nextSibling() as Assignment
val initializerValue = decl.value as NumericLiteralValue assertEquals(listOf("ch"), assignInitialValue.target.identifier!!.nameInSource)
assertIs<NumericLiteralValue>(assignInitialValue.value, "char literal should have been replaced by ubyte literal")
val initializerValue = assignInitialValue.value as NumericLiteralValue
assertEquals(DataType.UBYTE, initializerValue.type) assertEquals(DataType.UBYTE, initializerValue.type)
assertEquals(platform.encodeString("\n", false)[0], initializerValue.number.toShort()) assertEquals(platform.encodeString("\n", false)[0], initializerValue.number.toShort())
} }
@ -93,7 +97,7 @@ class TestCompilerOnCharLit {
} }
""").assertSuccess() """).assertSuccess()
val program = result.programAst val program = result.program
val startSub = program.entrypoint val startSub = program.entrypoint
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0] val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]

View File

@ -1,6 +1,5 @@
package prog8tests package prog8tests
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.DynamicTest import org.junit.jupiter.api.DynamicTest
import org.junit.jupiter.api.DynamicTest.dynamicTest import org.junit.jupiter.api.DynamicTest.dynamicTest
import org.junit.jupiter.api.TestFactory import org.junit.jupiter.api.TestFactory
@ -8,8 +7,12 @@ import org.junit.jupiter.api.TestInstance
import prog8.compiler.compileProgram import prog8.compiler.compileProgram
import prog8.compiler.target.C64Target import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target import prog8.compiler.target.Cx16Target
import prog8.compiler.target.ICompilationTarget import prog8.compilerinterface.ICompilationTarget
import prog8tests.helpers.* import prog8tests.ast.helpers.assumeDirectory
import prog8tests.ast.helpers.mapCombinations
import prog8tests.ast.helpers.outputDir
import prog8tests.ast.helpers.workingDir
import prog8tests.helpers.assertSuccess
import kotlin.io.path.absolute import kotlin.io.path.absolute
import kotlin.io.path.exists import kotlin.io.path.exists
@ -39,8 +42,10 @@ class TestCompilerOnExamples {
compileProgram( compileProgram(
filepath, filepath,
optimize, optimize,
optimizeFloatExpressions = false,
writeAssembly = true, writeAssembly = true,
slowCodegenWarnings = false, slowCodegenWarnings = false,
quietAssembler = true,
compilationTarget = platform.name, compilationTarget = platform.name,
sourceDirs = listOf(), sourceDirs = listOf(),
outputDir outputDir

View File

@ -8,7 +8,10 @@ import prog8.ast.expressions.StringLiteralValue
import prog8.ast.statements.FunctionCallStatement import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Label import prog8.ast.statements.Label
import prog8.compiler.target.Cx16Target import prog8.compiler.target.Cx16Target
import prog8tests.helpers.* import prog8tests.ast.helpers.*
import prog8tests.helpers.assertFailure
import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileFile
import kotlin.io.path.name import kotlin.io.path.name
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertNotEquals import kotlin.test.assertNotEquals
@ -34,7 +37,7 @@ class TestCompilerOnImportsAndIncludes {
val result = compileFile(platform, optimize = false, fixturesDir, filepath.name) val result = compileFile(platform, optimize = false, fixturesDir, filepath.name)
.assertSuccess() .assertSuccess()
val program = result.programAst val program = result.program
val startSub = program.entrypoint val startSub = program.entrypoint
val strLits = startSub.statements val strLits = startSub.statements
.filterIsInstance<FunctionCallStatement>() .filterIsInstance<FunctionCallStatement>()
@ -59,7 +62,7 @@ class TestCompilerOnImportsAndIncludes {
val result = compileFile(platform, optimize = false, fixturesDir, filepath.name) val result = compileFile(platform, optimize = false, fixturesDir, filepath.name)
.assertSuccess() .assertSuccess()
val program = result.programAst val program = result.program
val startSub = program.entrypoint val startSub = program.entrypoint
val args = startSub.statements val args = startSub.statements
.filterIsInstance<FunctionCallStatement>() .filterIsInstance<FunctionCallStatement>()
@ -105,8 +108,8 @@ class TestCompilerOnImportsAndIncludes {
val (where, p8Str, binStr) = it val (where, p8Str, binStr) = it
dynamicTest("%asmbinary from ${where}folder") { dynamicTest("%asmbinary from ${where}folder") {
val p8Path = assumeReadableFile(fixturesDir, p8Str) val p8Path = assumeReadableFile(fixturesDir, p8Str)
val binPath = assumeReadableFile(fixturesDir, binStr) // val binPath = assumeReadableFile(fixturesDir, binStr)
assertNotEquals( // the bug we're testing for (#54) was hidden if outputDir == workinDir assertNotEquals( // the bug we're testing for (#54) was hidden if outputDir == workingDir
workingDir.normalize().toAbsolutePath(), workingDir.normalize().toAbsolutePath(),
outputDir.normalize().toAbsolutePath(), outputDir.normalize().toAbsolutePath(),
"sanity check: workingDir and outputDir should not be the same folder" "sanity check: workingDir and outputDir should not be the same folder"

View File

@ -5,18 +5,21 @@ import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestFactory import org.junit.jupiter.api.TestFactory
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
import prog8.ast.base.DataType import prog8.ast.base.DataType
import prog8.ast.base.Position
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.ForLoop import prog8.ast.statements.ForLoop
import prog8.ast.statements.Subroutine import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDecl import prog8.ast.statements.VarDecl
import prog8.compiler.astprocessing.size
import prog8.compiler.astprocessing.toConstantIntegerRange
import prog8.compiler.target.C64Target import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target import prog8.compiler.target.Cx16Target
import prog8.compilerinterface.size
import prog8.compilerinterface.toConstantIntegerRange
import prog8tests.ast.helpers.mapCombinations
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.assertFailure import prog8tests.helpers.assertFailure
import prog8tests.helpers.assertSuccess import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText import prog8tests.helpers.compileText
import prog8tests.helpers.mapCombinations import kotlin.test.assertContains
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -40,7 +43,7 @@ class TestCompilerOnRanges {
} }
""").assertSuccess() """).assertSuccess()
val program = result.programAst val program = result.program
val startSub = program.entrypoint val startSub = program.entrypoint
val decl = startSub val decl = startSub
.statements.filterIsInstance<VarDecl>()[0] .statements.filterIsInstance<VarDecl>()[0]
@ -69,7 +72,7 @@ class TestCompilerOnRanges {
} }
""").assertSuccess() """).assertSuccess()
val program = result.programAst val program = result.program
val startSub = program.entrypoint val startSub = program.entrypoint
val decl = startSub val decl = startSub
.statements.filterIsInstance<VarDecl>()[0] .statements.filterIsInstance<VarDecl>()[0]
@ -151,7 +154,7 @@ class TestCompilerOnRanges {
} }
""").assertSuccess() """).assertSuccess()
val program = result.programAst val program = result.program
val startSub = program.entrypoint val startSub = program.entrypoint
val iterable = startSub val iterable = startSub
.statements.filterIsInstance<ForLoop>() .statements.filterIsInstance<ForLoop>()
@ -162,10 +165,10 @@ class TestCompilerOnRanges {
val expectedEnd = platform.encodeString("f", false)[0].toInt() val expectedEnd = platform.encodeString("f", false)[0].toInt()
val expectedStr = "$expectedStart .. $expectedEnd" val expectedStr = "$expectedStart .. $expectedEnd"
val intProgression = rangeExpr.toConstantIntegerRange(platform) val intProgression = rangeExpr.toConstantIntegerRange()
val actualStr = "${intProgression?.first} .. ${intProgression?.last}" val actualStr = "${intProgression?.first} .. ${intProgression?.last}"
assertEquals(expectedStr, actualStr,".first .. .last") assertEquals(expectedStr, actualStr,".first .. .last")
assertEquals(expectedEnd - expectedStart + 1, rangeExpr.size(platform), "rangeExpr.size()") assertEquals(expectedEnd - expectedStart + 1, rangeExpr.size(), "rangeExpr.size()")
} }
@Test @Test
@ -182,15 +185,15 @@ class TestCompilerOnRanges {
} }
""").assertSuccess() """).assertSuccess()
val program = result.programAst val program = result.program
val startSub = program.entrypoint val startSub = program.entrypoint
val rangeExpr = startSub val rangeExpr = startSub
.statements.filterIsInstance<ForLoop>() .statements.filterIsInstance<ForLoop>()
.map { it.iterable } .map { it.iterable }
.filterIsInstance<RangeExpr>()[0] .filterIsInstance<RangeExpr>()[0]
assertEquals(2, rangeExpr.size(platform)) assertEquals(2, rangeExpr.size())
val intProgression = rangeExpr.toConstantIntegerRange(platform) val intProgression = rangeExpr.toConstantIntegerRange()
assertEquals(0, intProgression?.first) assertEquals(0, intProgression?.first)
assertEquals(1, intProgression?.last) assertEquals(1, intProgression?.last)
} }
@ -209,21 +212,22 @@ class TestCompilerOnRanges {
} }
""").assertSuccess() """).assertSuccess()
val program = result.programAst val program = result.program
val startSub = program.entrypoint val startSub = program.entrypoint
val rangeExpr = startSub val rangeExpr = startSub
.statements.filterIsInstance<ForLoop>() .statements.filterIsInstance<ForLoop>()
.map { it.iterable } .map { it.iterable }
.filterIsInstance<RangeExpr>()[0] .filterIsInstance<RangeExpr>()[0]
assertEquals(9, rangeExpr.size(platform)) assertEquals(9, rangeExpr.size())
val intProgression = rangeExpr.toConstantIntegerRange(platform) val intProgression = rangeExpr.toConstantIntegerRange()
assertEquals(1, intProgression?.first) assertEquals(1, intProgression?.first)
assertEquals(9, intProgression?.last) assertEquals(9, intProgression?.last)
} }
@Test @Test
fun testForLoopWithRange_str_downto_str() { fun testForLoopWithRange_str_downto_str() {
val errors = ErrorReporterForTests()
compileText(Cx16Target, true, """ compileText(Cx16Target, true, """
main { main {
sub start() { sub start() {
@ -233,8 +237,10 @@ class TestCompilerOnRanges {
} }
} }
} }
""").assertFailure() """, errors, false).assertFailure()
//TODO("test exact compile error(s)") assertEquals(2, errors.errors.size)
assertContains(errors.errors[0], ".p8:5:29: range expression from value must be integer")
assertContains(errors.errors[1], ".p8:5:44: range expression to value must be integer")
} }
@Test @Test
@ -250,7 +256,7 @@ class TestCompilerOnRanges {
} }
""").assertSuccess() """).assertSuccess()
val program = result.programAst val program = result.program
val startSub = program.entrypoint val startSub = program.entrypoint
val iterable = startSub val iterable = startSub
.statements.filterIsInstance<ForLoop>() .statements.filterIsInstance<ForLoop>()
@ -260,5 +266,15 @@ class TestCompilerOnRanges {
assertEquals(DataType.STR, iterable.inferType(program).getOr(DataType.UNDEFINED)) assertEquals(DataType.STR, iterable.inferType(program).getOr(DataType.UNDEFINED))
} }
@Test
fun testRangeExprNumericSize() {
val expr = RangeExpr(
NumericLiteralValue.optimalInteger(10, Position.DUMMY),
NumericLiteralValue.optimalInteger(20, Position.DUMMY),
NumericLiteralValue.optimalInteger(2, Position.DUMMY),
Position.DUMMY)
assertEquals(6, expr.size())
expr.toConstantIntegerRange()
}
} }

View File

@ -6,7 +6,11 @@ import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
import prog8.compiler.compileProgram import prog8.compiler.compileProgram
import prog8.compiler.target.Cx16Target import prog8.compiler.target.Cx16Target
import prog8tests.helpers.* import prog8tests.ast.helpers.assumeReadableFile
import prog8tests.ast.helpers.fixturesDir
import prog8tests.ast.helpers.outputDir
import prog8tests.ast.helpers.workingDir
import prog8tests.helpers.assertSuccess
import java.nio.file.Path import java.nio.file.Path
import kotlin.io.path.absolute import kotlin.io.path.absolute
import kotlin.io.path.createTempFile import kotlin.io.path.createTempFile
@ -43,8 +47,10 @@ class TestCompilerOptionSourcedirs {
compileProgram( compileProgram(
filepath = filePath, filepath = filePath,
optimize = false, optimize = false,
optimizeFloatExpressions = false,
writeAssembly = true, writeAssembly = true,
slowCodegenWarnings = false, slowCodegenWarnings = false,
quietAssembler = true,
compilationTarget = Cx16Target.name, compilationTarget = Cx16Target.name,
sourceDirs, sourceDirs,
outputDir outputDir

View File

@ -3,14 +3,14 @@ package prog8tests
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
import prog8.ast.internedStringsModuleName import prog8.ast.internedStringsModuleName
import prog8.compiler.ErrorReporter
import prog8.compiler.ZeropageType
import prog8.compiler.determineCompilationOptions import prog8.compiler.determineCompilationOptions
import prog8.compiler.parseImports import prog8.compiler.parseImports
import prog8.compiler.target.C64Target import prog8.compiler.target.C64Target
import prog8.compilerinterface.ZeropageType
import prog8tests.ast.helpers.outputDir
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.assertSuccess import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText import prog8tests.helpers.compileText
import prog8tests.helpers.outputDir
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertTrue import kotlin.test.assertTrue
@ -30,9 +30,9 @@ main {
} }
} }
""").assertSuccess() """).assertSuccess()
assertTrue(result.programAst.toplevelModule.name.startsWith("on_the_fly_test")) assertTrue(result.program.toplevelModule.name.startsWith("on_the_fly_test"))
val moduleNames = result.programAst.modules.map { it.name } val moduleNames = result.program.modules.map { it.name }
assertTrue(moduleNames[0].startsWith("on_the_fly_test"), "main module must be first") assertTrue(moduleNames[0].startsWith("on_the_fly_test"), "main module must be first")
assertEquals(listOf( assertEquals(listOf(
"prog8_interned_strings", "prog8_interned_strings",
@ -44,7 +44,7 @@ main {
"prog8_lib" "prog8_lib"
), moduleNames.drop(1), "module order in parse tree") ), moduleNames.drop(1), "module order in parse tree")
assertTrue(result.programAst.toplevelModule.name.startsWith("on_the_fly_test")) assertTrue(result.program.toplevelModule.name.startsWith("on_the_fly_test"))
} }
@Test @Test
@ -61,8 +61,8 @@ main {
} }
} }
""").assertSuccess() """).assertSuccess()
assertTrue(result.programAst.toplevelModule.name.startsWith("on_the_fly_test")) assertTrue(result.program.toplevelModule.name.startsWith("on_the_fly_test"))
val options = determineCompilationOptions(result.programAst, C64Target) val options = determineCompilationOptions(result.program, C64Target)
assertTrue(options.floats) assertTrue(options.floats)
assertEquals(ZeropageType.DONTUSE, options.zeropage) assertEquals(ZeropageType.DONTUSE, options.zeropage)
assertTrue(options.noSysInit) assertTrue(options.noSysInit)
@ -70,7 +70,7 @@ main {
@Test @Test
fun testModuleOrderAndCompilationOptionsCorrectWithJustImports() { fun testModuleOrderAndCompilationOptionsCorrectWithJustImports() {
val errors = ErrorReporter() val errors = ErrorReporterForTests()
val sourceText = """ val sourceText = """
%import textio %import textio
%import floats %import floats

View File

@ -12,11 +12,12 @@ import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.PrefixExpression import prog8.ast.expressions.PrefixExpression
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.astprocessing.isInRegularRAMof
import prog8.compiler.target.C64Target import prog8.compiler.target.C64Target
import prog8.compilerinterface.isInRegularRAMof
import prog8.parser.SourceCode import prog8.parser.SourceCode
import prog8tests.helpers.DummyFunctions import prog8tests.helpers.DummyFunctions
import prog8tests.helpers.DummyMemsizer import prog8tests.helpers.DummyMemsizer
import prog8tests.helpers.DummyStringEncoder
import kotlin.test.assertFalse import kotlin.test.assertFalse
import kotlin.test.assertTrue import kotlin.test.assertTrue
@ -70,7 +71,7 @@ class TestMemory {
@Test @Test
fun testInValidRamC64_memory_identifiers() { fun testInValidRamC64_memory_identifiers() {
val program = Program("test", DummyFunctions, DummyMemsizer) val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
var target = createTestProgramForMemoryRefViaVar(program, 0x1000, VarDeclType.VAR) var target = createTestProgramForMemoryRefViaVar(program, 0x1000, VarDeclType.VAR)
assertTrue(target.isInRegularRAMof(C64Target.machine)) assertTrue(target.isInRegularRAMof(C64Target.machine))
@ -89,7 +90,7 @@ class TestMemory {
val memexpr = IdentifierReference(listOf("address"), Position.DUMMY) val memexpr = IdentifierReference(listOf("address"), Position.DUMMY)
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY) val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY) val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test")) val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
module.linkIntoProgram(program) module.linkIntoProgram(program)
return target return target
@ -107,9 +108,9 @@ class TestMemory {
val decl = VarDecl(VarDeclType.VAR, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, false, false, false, Position.DUMMY) val decl = VarDecl(VarDeclType.VAR, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, false, false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY) val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY) val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY) val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test")) val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer) val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module) .addModule(module)
module.linkIntoProgram(program) module.linkIntoProgram(program)
assertTrue(target.isInRegularRAMof(C64Target.machine)) assertTrue(target.isInRegularRAMof(C64Target.machine))
@ -121,9 +122,9 @@ class TestMemory {
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY) val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY) val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY) val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY) val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test")) val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer) val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module) .addModule(module)
module.linkIntoProgram(program) module.linkIntoProgram(program)
assertTrue(target.isInRegularRAMof(C64Target.machine)) assertTrue(target.isInRegularRAMof(C64Target.machine))
@ -135,9 +136,9 @@ class TestMemory {
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY) val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY) val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY) val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY) val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test")) val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer) val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module) .addModule(module)
module.linkIntoProgram(program) module.linkIntoProgram(program)
assertFalse(target.isInRegularRAMof(C64Target.machine)) assertFalse(target.isInRegularRAMof(C64Target.machine))
@ -149,9 +150,9 @@ class TestMemory {
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY) val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY) val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY) val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY) val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test")) val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer) val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module) .addModule(module)
module.linkIntoProgram(program) module.linkIntoProgram(program)
assertTrue(target.isInRegularRAMof(C64Target.machine)) assertTrue(target.isInRegularRAMof(C64Target.machine))
@ -164,9 +165,9 @@ class TestMemory {
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY) val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY) val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY) val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY) val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test")) val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer) val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module) .addModule(module)
module.linkIntoProgram(program) module.linkIntoProgram(program)
assertTrue(target.isInRegularRAMof(C64Target.machine)) assertTrue(target.isInRegularRAMof(C64Target.machine))
@ -179,9 +180,9 @@ class TestMemory {
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY) val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY) val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY) val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY) val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test")) val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer) val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module) .addModule(module)
module.linkIntoProgram(program) module.linkIntoProgram(program)
assertFalse(target.isInRegularRAMof(C64Target.machine)) assertFalse(target.isInRegularRAMof(C64Target.machine))

View File

@ -6,10 +6,10 @@ import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
import prog8.ast.toHex import prog8.ast.toHex
import prog8.compiler.CompilerException
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_NEGATIVE import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_POSITIVE import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_POSITIVE
import prog8.compiler.target.c64.C64MachineDefinition.Mflpt5 import prog8.compiler.target.c64.C64MachineDefinition.Mflpt5
import prog8.compilerinterface.InternalCompilerException
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
@ -81,10 +81,10 @@ class TestNumbers {
assertThat(Mflpt5.fromNumber(1.7e-39), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00))) assertThat(Mflpt5.fromNumber(1.7e-39), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)))
assertThat(Mflpt5.fromNumber(-1.7e-38), equalTo(Mflpt5(0x03, 0xb9, 0x1d, 0x15, 0x63))) assertThat(Mflpt5.fromNumber(-1.7e-38), equalTo(Mflpt5(0x03, 0xb9, 0x1d, 0x15, 0x63)))
assertThat(Mflpt5.fromNumber(-1.7e-39), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00))) assertThat(Mflpt5.fromNumber(-1.7e-39), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)))
assertFailsWith<CompilerException> { Mflpt5.fromNumber(1.7014118346e+38) } assertFailsWith<InternalCompilerException> { Mflpt5.fromNumber(1.7014118346e+38) }
assertFailsWith<CompilerException> { Mflpt5.fromNumber(-1.7014118346e+38) } assertFailsWith<InternalCompilerException> { Mflpt5.fromNumber(-1.7014118346e+38) }
assertFailsWith<CompilerException> { Mflpt5.fromNumber(1.7014118347e+38) } assertFailsWith<InternalCompilerException> { Mflpt5.fromNumber(1.7014118347e+38) }
assertFailsWith<CompilerException> { Mflpt5.fromNumber(-1.7014118347e+38) } assertFailsWith<InternalCompilerException> { Mflpt5.fromNumber(-1.7014118347e+38) }
} }
@Test @Test

View File

@ -1,18 +1,21 @@
package prog8tests package prog8tests
import org.hamcrest.CoreMatchers.instanceOf
import org.hamcrest.MatcherAssert.assertThat
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
import prog8.ast.statements.Block import prog8.ast.Program
import prog8.ast.statements.Return import prog8.ast.base.DataType
import prog8.ast.statements.Subroutine import prog8.ast.base.ParentSentinel
import prog8.ast.base.Position
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.*
import prog8.compiler.target.C64Target import prog8.compiler.target.C64Target
import prog8tests.helpers.DummyFunctions
import prog8tests.helpers.DummyMemsizer
import prog8tests.helpers.DummyStringEncoder
import prog8tests.helpers.assertSuccess import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText import prog8tests.helpers.compileText
import kotlin.test.assertEquals import kotlin.test.*
import kotlin.test.assertSame
import kotlin.test.assertTrue
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestOptimization { class TestOptimization {
@ -28,10 +31,10 @@ class TestOptimization {
} }
""" """
val result = compileText(C64Target, true, sourcecode).assertSuccess() val result = compileText(C64Target, true, sourcecode).assertSuccess()
val toplevelModule = result.programAst.toplevelModule val toplevelModule = result.program.toplevelModule
val mainBlock = toplevelModule.statements.single() as Block val mainBlock = toplevelModule.statements.single() as Block
val startSub = mainBlock.statements.single() as Subroutine val startSub = mainBlock.statements.single() as Subroutine
assertSame(result.programAst.entrypoint, startSub) assertSame(result.program.entrypoint, startSub)
assertEquals("start", startSub.name, "only start sub should remain") assertEquals("start", startSub.name, "only start sub should remain")
assertTrue(startSub.statements.single() is Return, "compiler has inserted return in empty subroutines") assertTrue(startSub.statements.single() is Return, "compiler has inserted return in empty subroutines")
} }
@ -50,13 +53,71 @@ class TestOptimization {
} }
""" """
val result = compileText(C64Target, true, sourcecode).assertSuccess() val result = compileText(C64Target, true, sourcecode).assertSuccess()
val toplevelModule = result.programAst.toplevelModule val toplevelModule = result.program.toplevelModule
val mainBlock = toplevelModule.statements.single() as Block val mainBlock = toplevelModule.statements.single() as Block
val startSub = mainBlock.statements[0] as Subroutine val startSub = mainBlock.statements[0] as Subroutine
val emptySub = mainBlock.statements[1] as Subroutine val emptySub = mainBlock.statements[1] as Subroutine
assertSame(result.programAst.entrypoint, startSub) assertSame(result.program.entrypoint, startSub)
assertEquals("start", startSub.name) assertEquals("start", startSub.name)
assertEquals("empty", emptySub.name) assertEquals("empty", emptySub.name)
assertTrue(emptySub.statements.single() is Return, "compiler has inserted return in empty subroutines") assertTrue(emptySub.statements.single() is Return, "compiler has inserted return in empty subroutines")
} }
@Test
fun testGeneratedConstvalueInheritsProperParentLinkage()
{
val number = NumericLiteralValue(DataType.UBYTE, 11, Position.DUMMY)
val tc = TypecastExpression(number, DataType.BYTE, false, Position.DUMMY)
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
tc.linkParents(ParentSentinel)
assertNotNull(tc.parent)
assertNotNull(number.parent)
assertSame(tc, number.parent)
val constvalue = tc.constValue(program)!!
assertIs<NumericLiteralValue>(constvalue)
assertEquals(11, constvalue.number.toInt())
assertEquals(DataType.BYTE, constvalue.type)
assertSame(tc, constvalue.parent)
}
@Test
fun testConstantFoldedAndSilentlyTypecastedForInitializerValues() {
val sourcecode = """
main {
sub start() {
const ubyte TEST = 10
byte x1 = TEST as byte + 1
byte x2 = 1 + TEST as byte
ubyte y1 = TEST + 1 as byte
ubyte y2 = 1 as byte + TEST
}
}
"""
val result = compileText(C64Target, true, sourcecode).assertSuccess()
val mainsub = result.program.entrypoint
assertEquals(10, mainsub.statements.size)
val declTest = mainsub.statements[0] as VarDecl
val declX1 = mainsub.statements[1] as VarDecl
val initX1 = mainsub.statements[2] as Assignment
val declX2 = mainsub.statements[3] as VarDecl
val initX2 = mainsub.statements[4] as Assignment
val declY1 = mainsub.statements[5] as VarDecl
val initY1 = mainsub.statements[6] as Assignment
val declY2 = mainsub.statements[7] as VarDecl
val initY2 = mainsub.statements[8] as Assignment
assertIs<Return>(mainsub.statements[9])
assertEquals(10.0, (declTest.value as NumericLiteralValue).number.toDouble())
assertNull(declX1.value)
assertNull(declX2.value)
assertNull(declY1.value)
assertNull(declY2.value)
assertEquals(DataType.BYTE, (initX1.value as NumericLiteralValue).type)
assertEquals(11.0, (initX1.value as NumericLiteralValue).number.toDouble())
assertEquals(DataType.BYTE, (initX2.value as NumericLiteralValue).type)
assertEquals(11.0, (initX2.value as NumericLiteralValue).number.toDouble())
assertEquals(DataType.UBYTE, (initY1.value as NumericLiteralValue).type)
assertEquals(11.0, (initY1.value as NumericLiteralValue).number.toDouble())
assertEquals(DataType.UBYTE, (initY2.value as NumericLiteralValue).type)
assertEquals(11.0, (initY2.value as NumericLiteralValue).number.toDouble())
}
} }

View File

@ -1,16 +1,17 @@
package prog8tests package prog8tests
import com.github.michaelbull.result.Ok import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.expect
import com.github.michaelbull.result.expectError
import com.github.michaelbull.result.getOrElse
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
import prog8.ast.base.DataType
import prog8.ast.base.Position
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.StringLiteralValue
import prog8.compiler.target.cbm.Petscii import prog8.compiler.target.cbm.Petscii
import kotlin.test.* import java.io.CharConversionException
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ -34,8 +35,6 @@ class TestPetscii {
assertThat("expect lowercase error fallback", Petscii.encodePetscii("", true), equalTo(Ok(listOf<Short>(0xd3)))) assertThat("expect lowercase error fallback", Petscii.encodePetscii("", true), equalTo(Ok(listOf<Short>(0xd3))))
assertThat(Petscii.decodePetscii(listOf(72, 0xd7, 0x5c, 0xfa, 0x12), true), equalTo("hW£✓\uF11A")) assertThat(Petscii.decodePetscii(listOf(72, 0xd7, 0x5c, 0xfa, 0x12), true), equalTo("hW£✓\uF11A"))
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(-1), true) }
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(256), true) }
} }
@Test @Test
@ -48,8 +47,6 @@ class TestPetscii {
assertThat("expecting fallback", Petscii.encodePetscii(""), equalTo(Ok(listOf<Short>(250)))) assertThat("expecting fallback", Petscii.encodePetscii(""), equalTo(Ok(listOf<Short>(250))))
assertThat(Petscii.decodePetscii(listOf(72, 0x5c, 0xd3, 0xff)), equalTo("H£♥π")) assertThat(Petscii.decodePetscii(listOf(72, 0x5c, 0xd3, 0xff)), equalTo("H£♥π"))
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(-1)) }
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(256)) }
} }
@Test @Test
@ -62,8 +59,6 @@ class TestPetscii {
assertThat("expect fallback", Petscii.encodeScreencode("π", true), equalTo(Ok(listOf<Short>(94)))) assertThat("expect fallback", Petscii.encodeScreencode("π", true), equalTo(Ok(listOf<Short>(94))))
assertThat(Petscii.decodeScreencode(listOf(0x08, 0x57, 0x1c, 0x7a), true), equalTo("hW£✓")) assertThat(Petscii.decodeScreencode(listOf(0x08, 0x57, 0x1c, 0x7a), true), equalTo("hW£✓"))
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(-1), true) }
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(256), true) }
} }
@Test @Test
@ -77,33 +72,129 @@ class TestPetscii {
assertThat("expecting fallback", Petscii.encodeScreencode(""), equalTo(Ok(listOf<Short>(122)))) assertThat("expecting fallback", Petscii.encodeScreencode(""), equalTo(Ok(listOf<Short>(122))))
assertThat(Petscii.decodeScreencode(listOf(0x17, 0x1c, 0x53, 0x5e)), equalTo("W£♥π")) assertThat(Petscii.decodeScreencode(listOf(0x17, 0x1c, 0x53, 0x5e)), equalTo("W£♥π"))
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(-1)) }
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(256)) }
} }
@Test @Test
fun testLiteralValueComparisons() { fun testErrorCases() {
val ten = NumericLiteralValue(DataType.UWORD, 10, Position.DUMMY) Petscii.encodePetscii("~", true).expectError { "shouldn't be able to encode tilde" }
val nine = NumericLiteralValue(DataType.UBYTE, 9, Position.DUMMY) Petscii.encodePetscii("~", false).expectError { "shouldn't be able to encode tilde" }
assertEquals(ten, ten) Petscii.encodeScreencode("~", true).expectError { "shouldn't be able to encode tilde" }
assertNotEquals(ten, nine) Petscii.encodeScreencode("~", false).expectError { "shouldn't be able to encode tilde" }
assertFalse(ten != ten)
assertTrue(ten != nine)
assertTrue(ten > nine) assertFailsWith<CharConversionException> { Petscii.decodePetscii(listOf<Short>(-1), true) }
assertTrue(ten >= nine) assertFailsWith<CharConversionException> { Petscii.decodePetscii(listOf<Short>(256), true) }
assertTrue(ten >= ten) assertFailsWith<CharConversionException> { Petscii.decodePetscii(listOf<Short>(-1), false) }
assertFalse(ten > ten) assertFailsWith<CharConversionException> { Petscii.decodePetscii(listOf<Short>(256), false) }
assertFailsWith<CharConversionException> { Petscii.decodeScreencode(listOf<Short>(-1), true) }
assertFailsWith<CharConversionException> { Petscii.decodeScreencode(listOf<Short>(256), true) }
assertFailsWith<CharConversionException> { Petscii.decodeScreencode(listOf<Short>(-1), false) }
assertFailsWith<CharConversionException> { Petscii.decodeScreencode(listOf<Short>(256), false) }
assertFalse(ten < nine) Petscii.scr2petscii(-1).expectError { "-1 should error" }
assertFalse(ten <= nine) Petscii.scr2petscii(256).expectError { "256 should error" }
assertTrue(ten <= ten) Petscii.petscii2scr(-1, true).expectError { "-1 should error" }
assertFalse(ten < ten) Petscii.petscii2scr(256, true).expectError { "256 should error" }
Petscii.petscii2scr(-1, false).expectError { "-1 should error" }
val abc = StringLiteralValue("abc", false, Position.DUMMY) Petscii.petscii2scr(256, false).expectError { "256 should error" }
val abd = StringLiteralValue("abd", false, Position.DUMMY)
assertEquals(abc, abc)
assertTrue(abc!=abd)
assertFalse(abc!=abc)
} }
@Test
fun testSpecialReplacements()
{
fun encodeP(c: Char, lower: Boolean) = Petscii.encodePetscii(c.toString(), lower).getOrElse { throw it }.single()
fun encodeS(c: Char, lower: Boolean) = Petscii.encodeScreencode(c.toString(), lower).getOrElse { throw it }.single()
Petscii.encodePetscii("`", false).expectError { "shouldn't have translation for backtick" }
Petscii.encodePetscii("`", true).expectError { "shouldn't have translation for backtick" }
Petscii.encodePetscii("~", false).expectError { "shouldn't have translation for tilde" }
Petscii.encodePetscii("~", true).expectError { "shouldn't have translation for tilde" }
assertEquals(94, encodeP('^', false))
assertEquals(94, encodeP('^', true))
assertEquals(30, encodeS('^', false))
assertEquals(30, encodeS('^', true))
assertEquals(228, encodeP('_', false))
assertEquals(228, encodeP('_', true))
assertEquals(100, encodeS('_', false))
assertEquals(100, encodeS('_', true))
assertEquals(243, encodeP('{', false))
assertEquals(243, encodeP('{', true))
assertEquals(115, encodeS('{', false))
assertEquals(115, encodeS('{', true))
assertEquals(235, encodeP('}', false))
assertEquals(235, encodeP('}', true))
assertEquals(107, encodeS('}', false))
assertEquals(107, encodeS('}', true))
assertEquals(221, encodeP('|', false))
assertEquals(221, encodeP('|', true))
assertEquals(93, encodeS('|', false))
assertEquals(93, encodeS('|', true))
assertEquals(205, encodeP('\\', false))
assertEquals(205, encodeP('\\', true))
assertEquals(77, encodeS('\\', false))
assertEquals(77, encodeS('\\', true))
}
@Test
fun testBoxDrawingCharsEncoding() {
fun encodeP(c: Char, lower: Boolean) = Petscii.encodePetscii(c.toString(), lower).getOrElse { throw it }.single()
fun encodeS(c: Char, lower: Boolean) = Petscii.encodeScreencode(c.toString(), lower).getOrElse { throw it }.single()
// pipe char
assertEquals(221, encodeP('|', false))
assertEquals(221, encodeP('|', true))
assertEquals(93, encodeS('|', false))
assertEquals(93, encodeS('|', true))
// ... same as '│', 0x7D -> BOX DRAWINGS LIGHT VERTICAL
assertEquals(221, encodeP('│', false))
assertEquals(221, encodeP('│', true))
assertEquals(93, encodeS('│', false))
assertEquals(93, encodeS('│', true))
// underscore
assertEquals(228, encodeP('_', false))
assertEquals(228, encodeP('_', true))
assertEquals(100, encodeS('_', false))
assertEquals(100, encodeS('_', true))
// ... same as '▁', 0xE4 LOWER ONE EIGHTH BLOCK
assertEquals(228, encodeP('▁', false))
assertEquals(228, encodeP('▁', true))
assertEquals(100, encodeS('▁', false))
assertEquals(100, encodeS('▁', true))
// ─ 0xC0 -> BOX DRAWINGS LIGHT HORIZONTAL
assertEquals(192, encodeP('─', false))
assertEquals(192, encodeP('─', true))
assertEquals(64, encodeS('─', false))
assertEquals(64, encodeS('─', true))
// │ 0x62 -> BOX DRAWINGS LIGHT VERTICAL
assertEquals(221, encodeP('│', false))
assertEquals(221, encodeP('│', true))
assertEquals(93, encodeS('│', false))
assertEquals(93, encodeS('│', true))
}
@Test
fun testBoxDrawingCharsDecoding() {
// ─ 0xC0 -> BOX DRAWINGS LIGHT HORIZONTAL
assertEquals('\uf13b', Petscii.decodePetscii(listOf(195), false).single(), "BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)")
assertEquals('C', Petscii.decodePetscii(listOf(195), true).single())
assertEquals('─', Petscii.decodePetscii(listOf(192), false).single())
assertEquals('─', Petscii.decodePetscii(listOf(192), true).single())
assertEquals('\uf13b', Petscii.decodeScreencode(listOf(67), false).single(), "BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)")
assertEquals('C', Petscii.decodeScreencode(listOf(67), true).single())
assertEquals('─', Petscii.decodeScreencode(listOf(64), false).single())
assertEquals('─', Petscii.decodeScreencode(listOf(64), true).single())
// │ 0x62 -> BOX DRAWINGS LIGHT VERTICAL
assertEquals('│', Petscii.decodePetscii(listOf(125), false).single())
assertEquals('│', Petscii.decodePetscii(listOf(125), true).single())
assertEquals('│', Petscii.decodePetscii(listOf(221), false).single())
assertEquals('│', Petscii.decodePetscii(listOf(221), true).single())
assertEquals('│', Petscii.decodeScreencode(listOf(93), false).single())
assertEquals('│', Petscii.decodeScreencode(listOf(93), true).single())
assertEquals('\uf13c', Petscii.decodeScreencode(listOf(66), false).single(), "BOX DRAWINGS LIGHT VERTICAL ONE EIGHTH LEFT (CUS)")
assertEquals('B', Petscii.decodeScreencode(listOf(66), true).single())
}
} }

View File

@ -0,0 +1,126 @@
package prog8tests
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.GlobalNamespace
import prog8.ast.base.ParentSentinel
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.*
import prog8.compiler.target.C64Target
import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText
import kotlin.test.*
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestScoping {
@Test
fun testModulesParentIsGlobalNamespace() {
val src = """
main {
sub start() {
}
}
"""
val result = compileText(C64Target, false, src, writeAssembly = false).assertSuccess()
val module = result.program.toplevelModule
assertIs<GlobalNamespace>(module.parent)
assertSame(result.program, module.program)
assertIs<ParentSentinel>(module.parent.parent)
}
@Test
fun testAnonScopeVarsMovedIntoSubroutineScope() {
val src = """
main {
sub start() {
repeat {
ubyte xx = 99
xx++
}
}
}
"""
val result = compileText(C64Target, false, src, writeAssembly = false).assertSuccess()
val module = result.program.toplevelModule
val mainBlock = module.statements.single() as Block
val start = mainBlock.statements.single() as Subroutine
val repeatbody = start.statements.filterIsInstance<RepeatLoop>().single().body
assertFalse(mainBlock.statements.any { it is VarDecl }, "no vars moved to main block")
val subroutineVars = start.statements.filterIsInstance<VarDecl>()
assertEquals(1, subroutineVars.size, "var from repeat anonscope must be moved up to subroutine")
assertEquals("xx", subroutineVars[0].name)
assertFalse(repeatbody.statements.any { it is VarDecl }, "var should have been removed from repeat anonscope")
val initassign = repeatbody.statements[0] as? Assignment
assertEquals(listOf("xx"), initassign?.target?.identifier?.nameInSource, "vardecl in repeat should be replaced by init assignment")
assertEquals(99, (initassign?.value as? NumericLiteralValue)?.number?.toInt(), "vardecl in repeat should be replaced by init assignment")
assertTrue(repeatbody.statements[1] is PostIncrDecr)
}
@Test
fun testLabelsWithAnonScopes() {
val src = """
main {
sub start() {
uword addr
goto labeloutside
if true {
if true {
addr = &iflabel
addr = &labelinside
addr = &labeloutside
addr = &main.start.nested.nestedlabel
addr = &nested.nestedlabel
goto labeloutside
goto iflabel
goto main.start.nested.nestedlabel
goto nested.nestedlabel
}
iflabel:
}
repeat {
addr = &iflabel
addr = &labelinside
addr = &labeloutside
addr = &main.start.nested.nestedlabel
addr = &nested.nestedlabel
goto iflabel
goto labelinside
goto main.start.nested.nestedlabel
goto nested.nestedlabel
labelinside:
}
sub nested () {
nestedlabel:
addr = &nestedlabel
goto nestedlabel
goto main.start.nested.nestedlabel
}
labeloutside:
addr = &iflabel
addr = &labelinside
addr = &labeloutside
addr = &main.start.nested.nestedlabel
addr = &nested.nestedlabel
goto main.start.nested.nestedlabel
goto nested.nestedlabel
}
}
"""
val result = compileText(C64Target, false, src, writeAssembly = true).assertSuccess()
val module = result.program.toplevelModule
val mainBlock = module.statements.single() as Block
val start = mainBlock.statements.single() as Subroutine
val labels = start.statements.filterIsInstance<Label>()
assertEquals(1, labels.size, "only one label in subroutine scope")
}
}

View File

@ -0,0 +1,255 @@
package prog8tests
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.base.DataType
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.target.C64Target
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.assertFailure
import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText
import kotlin.test.*
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestSubroutines {
@Test
fun stringParameter() {
val text = """
main {
sub start() {
str text = "test"
asmfunc("text")
asmfunc(text)
asmfunc($2000)
func("text")
func(text)
func($2000)
}
asmsub asmfunc(str thing @AY) {
}
sub func(str thing) {
uword t2 = thing as uword
asmfunc(thing)
}
}
"""
val result = compileText(C64Target, false, text, writeAssembly = false).assertSuccess()
val module = result.program.toplevelModule
val mainBlock = module.statements.single() as Block
val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"}
val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"}
assertTrue(asmfunc.isAsmSubroutine)
assertEquals(DataType.STR, asmfunc.parameters.single().type)
assertTrue(asmfunc.statements.isEmpty())
assertFalse(func.isAsmSubroutine)
assertEquals(DataType.STR, func.parameters.single().type)
assertEquals(4, func.statements.size)
val paramvar = func.statements[0] as VarDecl
assertEquals("thing", paramvar.name)
assertEquals(DataType.STR, paramvar.datatype)
val assign = func.statements[2] as Assignment
assertEquals(listOf("t2"), assign.target.identifier!!.nameInSource)
assertTrue(assign.value is TypecastExpression, "str param in function body should not be transformed by normal compiler steps")
assertEquals(DataType.UWORD, (assign.value as TypecastExpression).type)
val call = func.statements[3] as FunctionCallStatement
assertEquals("asmfunc", call.target.nameInSource.single())
assertTrue(call.args.single() is IdentifierReference, "str param in function body should not be transformed by normal compiler steps")
assertEquals("thing", (call.args.single() as IdentifierReference).nameInSource.single())
}
@Test
fun stringParameterAsmGen() {
val text = """
main {
sub start() {
str text = "test"
asmfunc("text")
asmfunc(text)
asmfunc($2000)
func("text")
func(text)
func($2000)
}
asmsub asmfunc(str thing @AY) {
}
sub func(str thing) {
uword t2 = thing as uword
asmfunc(thing)
}
}
"""
val result = compileText(C64Target, false, text, writeAssembly = true).assertSuccess()
val module = result.program.toplevelModule
val mainBlock = module.statements.single() as Block
val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"}
val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"}
assertTrue(asmfunc.isAsmSubroutine)
assertEquals(DataType.STR, asmfunc.parameters.single().type)
assertTrue(asmfunc.statements.single() is Return)
assertFalse(func.isAsmSubroutine)
assertEquals(DataType.UWORD, func.parameters.single().type, "asmgen should have changed str to uword type")
assertTrue(asmfunc.statements.last() is Return)
assertEquals(5, func.statements.size)
assertTrue(func.statements[4] is Return)
val paramvar = func.statements[0] as VarDecl
assertEquals("thing", paramvar.name)
assertEquals(DataType.UWORD, paramvar.datatype, "pre-asmgen should have changed str to uword type")
val assign = func.statements[2] as Assignment
assertEquals(listOf("t2"), assign.target.identifier!!.nameInSource)
assertTrue(assign.value is IdentifierReference, "str param in function body should be treated as plain uword before asmgen")
assertEquals("thing", (assign.value as IdentifierReference).nameInSource.single())
val call = func.statements[3] as FunctionCallStatement
assertEquals("asmfunc", call.target.nameInSource.single())
assertTrue(call.args.single() is IdentifierReference, "str param in function body should be treated as plain uword and not been transformed")
assertEquals("thing", (call.args.single() as IdentifierReference).nameInSource.single())
}
@Test
fun arrayParameterNotYetAllowed_ButShouldPerhapsBe() {
// note: the *parser* accepts this as it is valid *syntax*,
// however, it's not (yet) valid for the compiler
val text = """
main {
sub start() {
}
asmsub asmfunc(ubyte[] thing @AY) {
}
sub func(ubyte[22] thing) {
}
}
"""
val errors = ErrorReporterForTests()
compileText(C64Target, false, text, errors, false).assertFailure("currently array dt in signature is invalid") // TODO should not be invalid?
assertEquals(0, errors.warnings.size)
assertContains(errors.errors.single(), ".p8:9:16: Non-string pass-by-reference types cannot occur as a parameter type directly")
}
@Test
@Disabled("TODO: allow array parameter in signature") // TODO allow this?
fun arrayParameter() {
val text = """
main {
sub start() {
ubyte[] array = [1,2,3]
asmfunc(array)
asmfunc([4,5,6])
asmfunc($2000)
asmfunc(12.345)
func(array)
func([4,5,6])
func($2000)
func(12.345)
}
asmsub asmfunc(ubyte[] thing @AY) {
}
sub func(ubyte[22] thing) {
}
}
"""
val result = compileText(C64Target, false, text, writeAssembly = false).assertSuccess()
val module = result.program.toplevelModule
val mainBlock = module.statements.single() as Block
val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"}
val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"}
assertTrue(asmfunc.isAsmSubroutine)
assertEquals(DataType.ARRAY_UB, asmfunc.parameters.single().type)
assertTrue(asmfunc.statements.isEmpty())
assertFalse(func.isAsmSubroutine)
assertEquals(DataType.ARRAY_UB, func.parameters.single().type)
assertTrue(func.statements.isEmpty())
}
@Test
fun testUwordParameterAndNormalVarIndexedAsArrayWorkAsDirectMemoryRead() {
val text="""
main {
sub thing(uword rr) {
ubyte xx = rr[1] ; should still work as var initializer that will be rewritten
ubyte yy
yy = rr[2]
uword other
ubyte zz = other[3]
}
sub start() {
ubyte[] array=[1,2,3]
thing(array)
}
}
"""
val result = compileText(C64Target, false, text, writeAssembly = true).assertSuccess()
val module = result.program.toplevelModule
val block = module.statements.single() as Block
val thing = block.statements.filterIsInstance<Subroutine>().single {it.name=="thing"}
assertEquals("main", block.name)
assertEquals(10, thing.statements.size, "rr, xx, xx assign, yy, yy assign, other, other assign 0, zz, zz assign, return")
val xx = thing.statements[1] as VarDecl
assertNull(xx.value, "vardecl init values must have been moved to separate assignments")
val assignXX = thing.statements[2] as Assignment
val assignYY = thing.statements[4] as Assignment
val assignZZ = thing.statements[8] as Assignment
assertEquals(listOf("xx"), assignXX.target.identifier!!.nameInSource)
assertEquals(listOf("yy"), assignYY.target.identifier!!.nameInSource)
assertEquals(listOf("zz"), assignZZ.target.identifier!!.nameInSource)
val valueXXexpr = (assignXX.value as DirectMemoryRead).addressExpression as BinaryExpression
val valueYYexpr = (assignYY.value as DirectMemoryRead).addressExpression as BinaryExpression
val valueZZexpr = (assignZZ.value as DirectMemoryRead).addressExpression as BinaryExpression
assertEquals(listOf("rr"), (valueXXexpr.left as IdentifierReference).nameInSource)
assertEquals(listOf("rr"), (valueYYexpr.left as IdentifierReference).nameInSource)
assertEquals(listOf("other"), (valueZZexpr.left as IdentifierReference).nameInSource)
assertEquals(1, (valueXXexpr.right as NumericLiteralValue).number.toInt())
assertEquals(2, (valueYYexpr.right as NumericLiteralValue).number.toInt())
assertEquals(3, (valueZZexpr.right as NumericLiteralValue).number.toInt())
}
@Test
fun testUwordParameterAndNormalVarIndexedAsArrayWorkAsMemoryWrite() {
val text="""
main {
sub thing(uword rr) {
rr[10] = 42
}
sub start() {
ubyte[] array=[1,2,3]
thing(array)
}
}
"""
val result = compileText(C64Target, false, text, writeAssembly = true).assertSuccess()
val module = result.program.toplevelModule
val block = module.statements.single() as Block
val thing = block.statements.filterIsInstance<Subroutine>().single {it.name=="thing"}
assertEquals("main", block.name)
assertEquals(3, thing.statements.size, "rr, rr assign, return void")
val assignRR = thing.statements[1] as Assignment
assertEquals(42, (assignRR.value as NumericLiteralValue).number.toInt())
val memwrite = assignRR.target.memoryAddress
assertNotNull(memwrite, "memwrite to set byte array value")
val addressExpr = memwrite.addressExpression as BinaryExpression
assertEquals(listOf("rr"), (addressExpr.left as IdentifierReference).nameInSource)
assertEquals("+", addressExpr.operator)
assertEquals(10, (addressExpr.right as NumericLiteralValue).number.toInt())
}
}

View File

@ -3,21 +3,77 @@ package prog8tests
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
import prog8.ast.base.DataType import prog8.ast.base.DataType
import prog8.compiler.*
import prog8.compiler.target.C64Target import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target import prog8.compiler.target.Cx16Target
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
import prog8.compiler.target.cx16.CX16MachineDefinition.CX16Zeropage import prog8.compiler.target.cx16.CX16MachineDefinition.CX16Zeropage
import prog8.compilerinterface.*
import prog8tests.helpers.ErrorReporterForTests
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
import kotlin.test.assertFalse import kotlin.test.assertFalse
import kotlin.test.assertTrue import kotlin.test.assertTrue
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestAbstractZeropage {
@Test
fun testAbstractZeropage() {
val compTarget = DummyCompilationTarget()
val zp = DummyZeropage(
CompilationOptions(
OutputType.RAW,
LauncherType.NONE,
ZeropageType.FULL,
listOf((0x50..0x5f)),
false,
false,
compTarget
)
)
assertEquals(256-6-16, zp.free.size)
}
class DummyCompilationTarget: ICompilationTarget {
override val name: String = "dummy"
override val machine: IMachineDefinition
get() = throw NotImplementedError("dummy")
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
throw NotImplementedError("dummy")
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String {
throw NotImplementedError("dummy")
}
override fun memorySize(dt: DataType): Int {
throw NotImplementedError("dummy")
}
}
class DummyZeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1: Int = 0x10
override val SCRATCH_REG: Int = 0x11
override val SCRATCH_W1: Int= 0x20
override val SCRATCH_W2: Int = 0x30
init {
free.addAll(0..255)
removeReservedFromFreePool()
}
}
}
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestC64Zeropage { class TestC64Zeropage {
private val errors = ErrorReporter() private val errors = ErrorReporterForTests()
@Test @Test
fun testNames() { fun testNames() {
@ -35,11 +91,11 @@ class TestC64Zeropage {
@Test @Test
fun testZpFloatEnable() { fun testZpFloatEnable() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target)) val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertFailsWith<CompilerException> { assertFailsWith<InternalCompilerException> {
zp.allocate("", DataType.FLOAT, null, errors) zp.allocate("", DataType.FLOAT, null, errors)
} }
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false, C64Target)) val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false, C64Target))
assertFailsWith<CompilerException> { assertFailsWith<InternalCompilerException> {
zp2.allocate("", DataType.FLOAT, null, errors) zp2.allocate("", DataType.FLOAT, null, errors)
} }
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target)) val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target))
@ -54,10 +110,10 @@ class TestC64Zeropage {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target))
assertFailsWith<CompilerException> { assertFailsWith<InternalCompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true, false, C64Target)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true, false, C64Target))
} }
assertFailsWith<CompilerException> { assertFailsWith<InternalCompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false, C64Target)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false, C64Target))
} }
} }
@ -67,7 +123,7 @@ class TestC64Zeropage {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false, C64Target)) val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false, C64Target))
println(zp.free) println(zp.free)
assertEquals(0, zp.availableBytes()) assertEquals(0, zp.availableBytes())
assertFailsWith<CompilerException> { assertFailsWith<InternalCompilerException> {
zp.allocate("", DataType.BYTE, null, errors) zp.allocate("", DataType.BYTE, null, errors)
} }
} }
@ -216,7 +272,7 @@ class TestC64Zeropage {
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestCx16Zeropage { class TestCx16Zeropage {
private val errors = ErrorReporter() private val errors = ErrorReporterForTests()
@Test @Test
fun testReservedLocations() { fun testReservedLocations() {

View File

@ -1,21 +1,36 @@
package prog8tests.helpers package prog8tests.helpers
import prog8.ast.IBuiltinFunctions import prog8.ast.IBuiltinFunctions
import prog8.ast.IMemSizer import prog8.ast.base.DataType
import prog8.ast.base.Position import prog8.ast.base.Position
import prog8.ast.expressions.Expression import prog8.ast.expressions.Expression
import prog8.ast.expressions.InferredTypes import prog8.ast.expressions.InferredTypes
import prog8.ast.expressions.NumericLiteralValue import prog8.ast.expressions.NumericLiteralValue
import prog8.compilerinterface.IMemSizer
import prog8.compilerinterface.IStringEncoding
val DummyFunctions = object : IBuiltinFunctions { internal val DummyFunctions = object : IBuiltinFunctions {
override val names: Set<String> = emptySet() override val names: Set<String> = emptySet()
override val purefunctionNames: Set<String> = emptySet() override val purefunctionNames: Set<String> = emptySet()
override fun constValue( override fun constValue(
name: String, name: String,
args: List<Expression>, args: List<Expression>,
position: Position, position: Position,
memsizer: IMemSizer
): NumericLiteralValue? = null ): NumericLiteralValue? = null
override fun returnType(name: String, args: MutableList<Expression>) = InferredTypes.InferredType.unknown() override fun returnType(name: String, args: MutableList<Expression>) = InferredTypes.InferredType.unknown()
} }
internal val DummyMemsizer = object : IMemSizer {
override fun memorySize(dt: DataType): Int = 0
}
internal val DummyStringEncoder = object : IStringEncoding {
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
return emptyList()
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String {
return ""
}
}

View File

@ -1,25 +1,29 @@
package prog8tests.helpers package prog8tests.helpers
import prog8.ast.base.Position import prog8.ast.base.Position
import prog8.compiler.IErrorReporter import prog8.compilerinterface.IErrorReporter
class ErrorReporterForTests: IErrorReporter { internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: Boolean=true): IErrorReporter {
val errors = mutableListOf<String>() val errors = mutableListOf<String>()
val warnings = mutableListOf<String>() val warnings = mutableListOf<String>()
override fun err(msg: String, position: Position) { override fun err(msg: String, position: Position) {
errors.add(msg) errors.add("${position.toClickableStr()} $msg")
} }
override fun warn(msg: String, position: Position) { override fun warn(msg: String, position: Position) {
warnings.add(msg) warnings.add("${position.toClickableStr()} $msg")
} }
override fun noErrors(): Boolean = errors.isEmpty() override fun noErrors(): Boolean = errors.isEmpty()
override fun report() { override fun report() {
warnings.forEach { println("UNITTEST COMPILATION REPORT: WARNING: $it") }
errors.forEach { println("UNITTEST COMPILATION REPORT: ERROR: $it") }
if(throwExceptionAtReportIfErrors)
finalizeNumErrors(errors.size, warnings.size)
errors.clear() errors.clear()
warnings.clear() warnings.clear()
} }

View File

@ -2,7 +2,10 @@ package prog8tests.helpers
import prog8.compiler.CompilationResult import prog8.compiler.CompilationResult
import prog8.compiler.compileProgram import prog8.compiler.compileProgram
import prog8.compiler.target.ICompilationTarget import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
import prog8tests.ast.helpers.assumeReadableFile
import prog8tests.ast.helpers.outputDir
import java.nio.file.Path import java.nio.file.Path
import kotlin.io.path.name import kotlin.io.path.name
import kotlin.test.assertFalse import kotlin.test.assertFalse
@ -28,18 +31,23 @@ internal fun compileFile(
optimize: Boolean, optimize: Boolean,
fileDir: Path, fileDir: Path,
fileName: String, fileName: String,
outputDir: Path = prog8tests.helpers.outputDir outputDir: Path = prog8tests.ast.helpers.outputDir,
errors: IErrorReporter? = null,
writeAssembly: Boolean = true
) : CompilationResult { ) : CompilationResult {
val filepath = fileDir.resolve(fileName) val filepath = fileDir.resolve(fileName)
assumeReadableFile(filepath) assumeReadableFile(filepath)
return compileProgram( return compileProgram(
filepath, filepath,
optimize, optimize,
writeAssembly = true, optimizeFloatExpressions = false,
writeAssembly = writeAssembly,
slowCodegenWarnings = false, slowCodegenWarnings = false,
quietAssembler = true,
platform.name, platform.name,
sourceDirs = listOf(), sourceDirs = listOf(),
outputDir outputDir,
errors = errors ?: ErrorReporterForTests()
) )
} }
@ -51,10 +59,12 @@ internal fun compileFile(
internal fun compileText( internal fun compileText(
platform: ICompilationTarget, platform: ICompilationTarget,
optimize: Boolean, optimize: Boolean,
sourceText: String sourceText: String,
errors: IErrorReporter? = null,
writeAssembly: Boolean = true
) : CompilationResult { ) : CompilationResult {
val filePath = outputDir.resolve("on_the_fly_test_" + sourceText.hashCode().toUInt().toString(16) + ".p8") val filePath = outputDir.resolve("on_the_fly_test_" + sourceText.hashCode().toUInt().toString(16) + ".p8")
// we don't assumeNotExists(filePath) - should be ok to just overwrite it // we don't assumeNotExists(filePath) - should be ok to just overwrite it
filePath.toFile().writeText(sourceText) filePath.toFile().writeText(sourceText)
return compileFile(platform, optimize, filePath.parent, filePath.name) return compileFile(platform, optimize, filePath.parent, filePath.name, errors=errors, writeAssembly=writeAssembly)
} }

View File

@ -3,14 +3,13 @@ plugins {
id "org.jetbrains.kotlin.jvm" id "org.jetbrains.kotlin.jvm"
} }
targetCompatibility = 11
sourceCompatibility = 11
repositories { java {
mavenCentral() toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
} }
dependencies { dependencies {
implementation 'org.antlr:antlr4-runtime:4.9.2' implementation 'org.antlr:antlr4-runtime:4.9.2'
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.12" implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.12"
@ -26,22 +25,6 @@ configurations.all {
exclude group: 'com.ibm.icu', module: 'icu4j' exclude group: 'com.ibm.icu', module: 'icu4j'
} }
compileKotlin {
kotlinOptions {
jvmTarget = "11"
useIR = true
// verbose = true
// freeCompilerArgs += "-XXLanguage:+NewInference"
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "11"
useIR = true
}
}
sourceSets { sourceSets {
main { main {
java { java {

View File

@ -12,9 +12,8 @@ import prog8.ast.walk.IAstVisitor
/** /**
* Produces Prog8 source text from a [Program] (AST node), * Produces Prog8 source text from a [Program] (AST node),
* passing it as a String to the specified receiver function. * passing it as a String to the specified receiver function.
* TODO: rename/refactor to make proper sense in the presence of class [prog8.parser.SourceCode]
*/ */
class AstToSourceCode(val output: (text: String) -> Unit, val program: Program): IAstVisitor { class AstToSourceTextConverter(val output: (text: String) -> Unit, val program: Program): IAstVisitor {
private var scopelevel = 0 private var scopelevel = 0
private fun indent(s: String) = " ".repeat(scopelevel) + s private fun indent(s: String) = " ".repeat(scopelevel) + s
@ -98,7 +97,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
override fun visit(decl: VarDecl) { override fun visit(decl: VarDecl) {
// if the vardecl is a parameter of a subroutine, don't output it again // if the vardecl is a parameter of a subroutine, don't output it again
val paramNames = (decl.definingScope as? Subroutine)?.parameters?.map { it.name } val paramNames = decl.definingSubroutine?.parameters?.map { it.name }
if(paramNames!=null && decl.name in paramNames) if(paramNames!=null && decl.name in paramNames)
return return

View File

@ -3,7 +3,6 @@ package prog8.ast
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.Expression import prog8.ast.expressions.Expression
import prog8.ast.expressions.IdentifierReference import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.StringLiteralValue
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.walk.AstWalker import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstVisitor import prog8.ast.walk.IAstVisitor
@ -21,117 +20,12 @@ interface IFunctionCall {
var args: MutableList<Expression> var args: MutableList<Expression>
} }
interface INameScope { interface IStatementContainer {
val name: String
val position: Position val position: Position
val statements: MutableList<Statement>
val parent: Node val parent: Node
val statements: MutableList<Statement>
fun linkParents(parent: Node) fun linkParents(parent: Node)
fun subScope(name: String): INameScope? {
for(stmt in statements) {
when(stmt) {
// NOTE: if other nodes are introduced that are a scope, or contain subscopes, they must be added here!
is ForLoop -> if(stmt.body.name==name) return stmt.body
is UntilLoop -> if(stmt.body.name==name) return stmt.body
is WhileLoop -> if(stmt.body.name==name) return stmt.body
is BranchStatement -> {
if(stmt.truepart.name==name) return stmt.truepart
if(stmt.elsepart.containsCodeOrVars && stmt.elsepart.name==name) return stmt.elsepart
}
is IfStatement -> {
if(stmt.truepart.name==name) return stmt.truepart
if(stmt.elsepart.containsCodeOrVars && stmt.elsepart.name==name) return stmt.elsepart
}
is WhenStatement -> {
val scope = stmt.choices.firstOrNull { it.statements.name==name }
if(scope!=null)
return scope.statements
}
is INameScope -> if(stmt.name==name) return stmt
else -> {}
}
}
return null
}
fun getLabelOrVariable(name: String): Statement? {
// this is called A LOT and could perhaps be optimized a bit more,
// but adding a memoization cache didn't make much of a practical runtime difference
for (stmt in statements) {
if (stmt is VarDecl && stmt.name==name) return stmt
if (stmt is Label && stmt.name==name) return stmt
if (stmt is AnonymousScope) {
val sub = stmt.getLabelOrVariable(name)
if(sub!=null)
return sub
}
}
return null
}
val allDefinedSymbols: List<Pair<String, Statement>>
get() {
return statements.mapNotNull {
when (it) {
is Label -> it.name to it
is VarDecl -> it.name to it
is Subroutine -> it.name to it
is Block -> it.name to it
else -> null
}
}
}
fun lookup(scopedName: List<String>, localContext: Node) : Statement? {
if(scopedName.size>1) {
// a scoped name refers to a name in another module.
// it's a qualified name, look it up from the root of the module's namespace (consider all modules in the program)
for(module in localContext.definingModule.program.modules) {
var scope: INameScope? = module
for(name in scopedName.dropLast(1)) {
scope = scope?.subScope(name)
if(scope==null)
break
}
if(scope!=null) {
val result = scope.getLabelOrVariable(scopedName.last())
if(result!=null)
return result
return scope.subScope(scopedName.last()) as Statement?
}
}
return null
} else {
// unqualified name
// special case: the do....until statement can also look INSIDE the anonymous scope
if(localContext.parent.parent is UntilLoop) {
val symbolFromInnerScope = (localContext.parent.parent as UntilLoop).body.getLabelOrVariable(scopedName[0])
if(symbolFromInnerScope!=null)
return symbolFromInnerScope
}
// find the scope the localContext is in, look in that first
var statementScope = localContext
while(statementScope !is ParentSentinel) {
val localScope = statementScope.definingScope
val result = localScope.getLabelOrVariable(scopedName[0])
if (result != null)
return result
val subscope = localScope.subScope(scopedName[0]) as Statement?
if (subscope != null)
return subscope
// not found in this scope, look one higher up
statementScope = statementScope.parent
}
return null
}
}
val containsCodeOrVars get() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm" }
val containsNoCodeNorVars get() = !containsCodeOrVars
fun remove(stmt: Statement) { fun remove(stmt: Statement) {
if(!statements.remove(stmt)) if(!statements.remove(stmt))
throw FatalAstException("stmt to remove wasn't found in scope") throw FatalAstException("stmt to remove wasn't found in scope")
@ -140,11 +34,11 @@ interface INameScope {
fun getAllLabels(label: String): List<Label> { fun getAllLabels(label: String): List<Label> {
val result = mutableListOf<Label>() val result = mutableListOf<Label>()
fun find(scope: INameScope) { fun find(scope: IStatementContainer) {
scope.statements.forEach { scope.statements.forEach {
when(it) { when(it) {
is Label -> result.add(it) is Label -> result.add(it)
is INameScope -> find(it) is IStatementContainer -> find(it)
is IfStatement -> { is IfStatement -> {
find(it.truepart) find(it.truepart)
find(it.elsepart) find(it.elsepart)
@ -162,22 +56,6 @@ interface INameScope {
return result return result
} }
fun nextSibling(stmt: Statement): Statement? {
val nextIdx = statements.indexOfFirst { it===stmt } + 1
return if(nextIdx < statements.size)
statements[nextIdx]
else
null
}
fun previousSibling(stmt: Statement): Statement? {
val previousIdx = statements.indexOfFirst { it===stmt } - 1
return if(previousIdx>=0)
statements[previousIdx]
else
null
}
fun indexOfChild(stmt: Statement): Int { fun indexOfChild(stmt: Statement): Int {
val idx = statements.indexOfFirst { it===stmt } val idx = statements.indexOfFirst { it===stmt }
if(idx>=0) if(idx>=0)
@ -185,6 +63,159 @@ interface INameScope {
else else
throw FatalAstException("attempt to find a non-child") throw FatalAstException("attempt to find a non-child")
} }
fun isEmpty(): Boolean = statements.isEmpty()
fun isNotEmpty(): Boolean = statements.isNotEmpty()
fun searchSymbol(name: String): Statement? {
// this is called quite a lot and could perhaps be optimized a bit more,
// but adding a memoization cache didn't make much of a practical runtime difference...
for (stmt in statements) {
when(stmt) {
// is INamedStatement -> {
// if(stmt.name==name) return stmt
// }
is VarDecl -> {
if(stmt.name==name) return stmt
}
is Label -> {
if(stmt.name==name) return stmt
}
is Subroutine -> {
if(stmt.name==name)
return stmt
}
is AnonymousScope -> {
val found = stmt.searchSymbol(name)
if(found!=null)
return found
}
is IfStatement -> {
val found = stmt.truepart.searchSymbol(name) ?: stmt.elsepart.searchSymbol(name)
if(found!=null)
return found
}
is BranchStatement -> {
val found = stmt.truepart.searchSymbol(name) ?: stmt.elsepart.searchSymbol(name)
if(found!=null)
return found
}
is ForLoop -> {
val found = stmt.body.searchSymbol(name)
if(found!=null)
return found
}
is WhileLoop -> {
val found = stmt.body.searchSymbol(name)
if(found!=null)
return found
}
is RepeatLoop -> {
val found = stmt.body.searchSymbol(name)
if(found!=null)
return found
}
is UntilLoop -> {
val found = stmt.body.searchSymbol(name)
if(found!=null)
return found
}
is WhenStatement -> {
stmt.choices.forEach {
val found = it.statements.searchSymbol(name)
if(found!=null)
return found
}
}
else -> {
// NOTE: when other nodes containing AnonymousScope are introduced,
// these should be added here as well to look into!
}
}
}
return null
}
val allDefinedSymbols: List<Pair<String, Statement>>
get() {
return statements.filterIsInstance<INamedStatement>().map { Pair(it.name, it as Statement) }
}
}
interface INameScope: IStatementContainer, INamedStatement {
fun subScope(name: String): INameScope? = statements.firstOrNull { it is INameScope && it.name==name } as? INameScope
fun lookup(scopedName: List<String>) : Statement? {
return if(scopedName.size>1)
lookupQualified(scopedName)
else {
lookupUnqualified(scopedName[0])
}
}
private fun lookupQualified(scopedName: List<String>): Statement? {
// a scoped name refers to a name in another namespace.
// look "up" from our current scope to search for the correct one.
val localScope = this.subScope(scopedName[0])
if(localScope!=null)
return resolveLocally(localScope, scopedName.drop(1))
var statementScope = this
while(statementScope !is GlobalNamespace) {
if(statementScope !is Module && statementScope.name==scopedName[0]) {
return resolveLocally(statementScope, scopedName.drop(1))
} else {
statementScope = (statementScope as Node).definingScope
}
}
// not found, try again but now assume it's a globally scoped name starting with the name of a block
for(module in (this as Node).definingModule.program.modules) {
module.statements.forEach {
if(it is Block && it.name==scopedName[0])
return it.lookup(scopedName)
}
}
return null
}
private fun resolveLocally(startScope: INameScope, name: List<String>): Statement? {
var scope: INameScope? = startScope
for(part in name.dropLast(1)) {
scope = scope!!.subScope(part)
if(scope==null)
return null
}
return scope!!.searchSymbol(name.last())
}
private fun lookupUnqualified(name: String): Statement? {
val builtinFunctionsNames = (this as Node).definingModule.program.builtinFunctions.names
if(name in builtinFunctionsNames) {
// builtin functions always exist, return a dummy placeholder for them
val builtinPlaceholder = Label("builtin::$name", this.position)
builtinPlaceholder.parent = ParentSentinel
return builtinPlaceholder
}
// search for the unqualified name in the current scope (and possibly in any anonymousscopes it may contain)
// if it's not found there, jump up one higher in the namespaces and try again.
var statementScope = this
while(statementScope !is GlobalNamespace) {
val symbol = statementScope.searchSymbol(name)
if(symbol!=null)
return symbol
else
statementScope = (statementScope as Node).definingScope
}
return null
}
// private fun getNamedSymbol(name: String): Statement? =
// statements.singleOrNull { it is INamedStatement && it.name==name }
val containsCodeOrVars get() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm" }
val containsNoCodeNorVars get() = !containsCodeOrVars
} }
@ -234,119 +265,6 @@ interface Node {
} }
/*********** Everything starts from here, the Program; zero or more modules *************/
class Program(val name: String,
val builtinFunctions: IBuiltinFunctions,
val memsizer: IMemSizer): Node {
private val _modules = mutableListOf<Module>()
val modules: List<Module> = _modules
val namespace: GlobalNamespace = GlobalNamespace(modules, builtinFunctions.names)
init {
// insert a container module for all interned strings later
val internedStringsModule = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated(internedStringsModuleName))
val block = Block(internedStringsModuleName, null, mutableListOf(), true, Position.DUMMY)
internedStringsModule.statements.add(block)
_modules.add(0, internedStringsModule)
internedStringsModule.linkParents(namespace)
internedStringsModule.program = this
}
fun addModule(module: Module): Program {
require(null == _modules.firstOrNull { it.name == module.name })
{ "module '${module.name}' already present" }
_modules.add(module)
module.linkIntoProgram(this)
return this
}
fun removeModule(module: Module) = _modules.remove(module)
fun moveModuleToFront(module: Module): Program {
require(_modules.contains(module))
{ "Not a module of this program: '${module.name}'"}
_modules.remove(module)
_modules.add(0, module)
return this
}
val allBlocks: List<Block>
get() = modules.flatMap { it.statements.filterIsInstance<Block>() }
val entrypoint: Subroutine
get() {
val mainBlocks = allBlocks.filter { it.name == "main" }
return when (mainBlocks.size) {
0 -> throw FatalAstException("no 'main' block")
1 -> mainBlocks[0].subScope("start") as Subroutine
else -> throw FatalAstException("more than one 'main' block")
}
}
val toplevelModule: Module
get() = modules.first { it.name!=internedStringsModuleName }
val definedLoadAddress: Int
get() = toplevelModule.loadAddress
var actualLoadAddress: Int = 0
private val internedStringsUnique = mutableMapOf<Pair<String, Boolean>, List<String>>()
fun internString(string: StringLiteralValue): List<String> {
// Move a string literal into the internal, deduplicated, string pool
// replace it with a variable declaration that points to the entry in the pool.
if(string.parent is VarDecl) {
// deduplication can only be performed safely for known-const strings (=string literals OUTSIDE OF A VARDECL)!
throw FatalAstException("cannot intern a string literal that's part of a vardecl")
}
fun getScopedName(string: StringLiteralValue): List<String> {
val internedStringsBlock = modules
.first { it.name == internedStringsModuleName }.statements
.first { it is Block && it.name == internedStringsModuleName } as Block
val varName = "string_${internedStringsBlock.statements.size}"
val decl = VarDecl(
VarDeclType.VAR, DataType.STR, ZeropageWish.NOT_IN_ZEROPAGE, null, varName, string,
isArray = false, autogeneratedDontRemove = true, sharedWithAsm = false, position = string.position
)
internedStringsBlock.statements.add(decl)
decl.linkParents(internedStringsBlock)
return listOf(internedStringsModuleName, decl.name)
}
val key = Pair(string.value, string.altEncoding)
val existing = internedStringsUnique[key]
if (existing != null)
return existing
val scopedName = getScopedName(string)
internedStringsUnique[key] = scopedName
return scopedName
}
override val position: Position = Position.DUMMY
override var parent: Node
get() = throw FatalAstException("program has no parent")
set(_) = throw FatalAstException("can't set parent of program")
override fun linkParents(parent: Node) {
modules.forEach {
it.linkParents(namespace)
}
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(node is Module && replacement is Module)
val idx = _modules.indexOfFirst { it===node }
_modules[idx] = replacement
replacement.linkIntoProgram(this)
}
}
open class Module(final override var statements: MutableList<Statement>, open class Module(final override var statements: MutableList<Statement>,
final override val position: Position, final override val position: Position,
val source: SourceCode) : Node, INameScope { val source: SourceCode) : Node, INameScope {
@ -393,12 +311,16 @@ open class Module(final override var statements: MutableList<Statement>,
} }
class GlobalNamespace(val modules: Iterable<Module>, private val builtinFunctionNames: Set<String>): Node, INameScope { class GlobalNamespace(val modules: Iterable<Module>): Node, INameScope {
override val name = "<<<global>>>" override val name = "<<<global>>>"
override val position = Position("<<<global>>>", 0, 0, 0) override val position = Position("<<<global>>>", 0, 0, 0)
override val statements = mutableListOf<Statement>() // not used override val statements = mutableListOf<Statement>() // not used
override var parent: Node = ParentSentinel override var parent: Node = ParentSentinel
override fun lookup(scopedName: List<String>): Statement? {
throw NotImplementedError("use lookup on actual ast node instead")
}
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
modules.forEach { it.linkParents(this) } modules.forEach { it.linkParents(this) }
} }
@ -406,29 +328,6 @@ class GlobalNamespace(val modules: Iterable<Module>, private val builtinFunction
override fun replaceChildNode(node: Node, replacement: Node) { override fun replaceChildNode(node: Node, replacement: Node) {
throw FatalAstException("cannot replace anything in the namespace") throw FatalAstException("cannot replace anything in the namespace")
} }
override fun lookup(scopedName: List<String>, localContext: Node): Statement? {
if (scopedName.size == 1 && scopedName[0] in builtinFunctionNames) {
// builtin functions always exist, return a dummy localContext for them
val builtinPlaceholder = Label("builtin::${scopedName.last()}", localContext.position)
builtinPlaceholder.parent = ParentSentinel
return builtinPlaceholder
}
// special case: the do....until statement can also look INSIDE the anonymous scope
if(localContext.parent.parent is UntilLoop) {
val symbolFromInnerScope = (localContext.parent.parent as UntilLoop).body.lookup(scopedName, localContext)
if(symbolFromInnerScope!=null)
return symbolFromInnerScope
}
// lookup something from the module.
return when (val stmt = localContext.definingModule.lookup(scopedName, localContext)) {
is Label, is VarDecl, is Block, is Subroutine -> stmt
null -> null
else -> throw SyntaxError("invalid identifier target type", stmt.position)
}
}
} }
object BuiltinFunctionScopePlaceholder : INameScope { object BuiltinFunctionScopePlaceholder : INameScope {

View File

@ -8,6 +8,6 @@ import prog8.ast.expressions.NumericLiteralValue
interface IBuiltinFunctions { interface IBuiltinFunctions {
val names: Set<String> val names: Set<String>
val purefunctionNames: Set<String> val purefunctionNames: Set<String>
fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue? fun constValue(name: String, args: List<Expression>, position: Position): NumericLiteralValue?
fun returnType(name: String, args: MutableList<Expression>): InferredTypes.InferredType fun returnType(name: String, args: MutableList<Expression>): InferredTypes.InferredType
} }

View File

@ -1,7 +0,0 @@
package prog8.ast
import prog8.ast.base.DataType
interface IMemSizer {
fun memorySize(dt: DataType): Int
}

View File

@ -0,0 +1,112 @@
package prog8.ast
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.base.Position
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.StringLiteralValue
import prog8.ast.statements.Block
import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDecl
import prog8.ast.statements.ZeropageWish
import prog8.compilerinterface.IMemSizer
import prog8.compilerinterface.IStringEncoding
import prog8.parser.SourceCode
/*********** Everything starts from here, the Program; zero or more modules *************/
class Program(val name: String,
val builtinFunctions: IBuiltinFunctions,
val memsizer: IMemSizer,
val encoding: IStringEncoding
) {
private val _modules = mutableListOf<Module>()
val modules: List<Module> = _modules
val namespace: GlobalNamespace = GlobalNamespace(modules)
init {
// insert a container module for all interned strings later
val internedStringsModule =
Module(mutableListOf(), Position.DUMMY, SourceCode.Generated(internedStringsModuleName))
val block = Block(internedStringsModuleName, null, mutableListOf(), true, Position.DUMMY)
internedStringsModule.statements.add(block)
_modules.add(0, internedStringsModule)
internedStringsModule.linkParents(namespace)
internedStringsModule.program = this
}
fun addModule(module: Module): Program {
require(null == _modules.firstOrNull { it.name == module.name })
{ "module '${module.name}' already present" }
_modules.add(module)
module.linkIntoProgram(this)
return this
}
fun removeModule(module: Module) = _modules.remove(module)
fun moveModuleToFront(module: Module): Program {
require(_modules.contains(module))
{ "Not a module of this program: '${module.name}'"}
_modules.remove(module)
_modules.add(0, module)
return this
}
val allBlocks: List<Block>
get() = modules.flatMap { it.statements.filterIsInstance<Block>() }
val entrypoint: Subroutine
get() {
val mainBlocks = allBlocks.filter { it.name == "main" }
return when (mainBlocks.size) {
0 -> throw FatalAstException("no 'main' block")
1 -> mainBlocks[0].subScope("start") as Subroutine
else -> throw FatalAstException("more than one 'main' block")
}
}
val toplevelModule: Module
get() = modules.first { it.name!= internedStringsModuleName }
val definedLoadAddress: Int
get() = toplevelModule.loadAddress
var actualLoadAddress: Int = 0
private val internedStringsUnique = mutableMapOf<Pair<String, Boolean>, List<String>>()
fun internString(string: StringLiteralValue): List<String> {
// Move a string literal into the internal, deduplicated, string pool
// replace it with a variable declaration that points to the entry in the pool.
if(string.parent is VarDecl) {
// deduplication can only be performed safely for known-const strings (=string literals OUTSIDE OF A VARDECL)!
throw FatalAstException("cannot intern a string literal that's part of a vardecl")
}
fun getScopedName(string: StringLiteralValue): List<String> {
val internedStringsBlock = modules
.first { it.name == internedStringsModuleName }.statements
.first { it is Block && it.name == internedStringsModuleName } as Block
val varName = "string_${internedStringsBlock.statements.size}"
val decl = VarDecl(
VarDeclType.VAR, DataType.STR, ZeropageWish.NOT_IN_ZEROPAGE, null, varName, string,
isArray = false, autogeneratedDontRemove = true, sharedWithAsm = false, position = string.position
)
internedStringsBlock.statements.add(decl)
decl.linkParents(internedStringsBlock)
return listOf(internedStringsModuleName, decl.name)
}
val key = Pair(string.value, string.altEncoding)
val existing = internedStringsUnique[key]
if (existing != null)
return existing
val scopedName = getScopedName(string)
internedStringsUnique[key] = scopedName
return scopedName
}
}

View File

@ -29,7 +29,7 @@ private fun ParserRuleContext.toPosition() : Position {
pathString pathString
} }
// note: beware of TAB characters in the source text, they count as 1 column... // note: beware of TAB characters in the source text, they count as 1 column...
return Position(filename, start.line, start.charPositionInLine, stop.charPositionInLine + stop.text.length) return Position(filename, start.line, start.charPositionInLine, start.charPositionInLine + start.stopIndex - start.startIndex)
} }
internal fun Prog8ANTLRParser.BlockContext.toAst(isInLibrary: Boolean) : Block { internal fun Prog8ANTLRParser.BlockContext.toAst(isInLibrary: Boolean) : Block {
@ -186,7 +186,7 @@ private fun Prog8ANTLRParser.AsmsubroutineContext.toAst(): Subroutine {
val inline = this.inline()!=null val inline = this.inline()!=null
val subdecl = asmsub_decl().toAst() val subdecl = asmsub_decl().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf() val statements = statement_block()?.toAst() ?: mutableListOf()
return Subroutine(subdecl.name, subdecl.parameters, subdecl.returntypes, return Subroutine(subdecl.name, subdecl.parameters.toMutableList(), subdecl.returntypes,
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters, subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
subdecl.asmClobbers, null, true, inline, statements, toPosition()) subdecl.asmClobbers, null, true, inline, statements, toPosition())
} }
@ -194,7 +194,7 @@ private fun Prog8ANTLRParser.AsmsubroutineContext.toAst(): Subroutine {
private fun Prog8ANTLRParser.RomsubroutineContext.toAst(): Subroutine { private fun Prog8ANTLRParser.RomsubroutineContext.toAst(): Subroutine {
val subdecl = asmsub_decl().toAst() val subdecl = asmsub_decl().toAst()
val address = integerliteral().toAst().number.toInt() val address = integerliteral().toAst().number.toInt()
return Subroutine(subdecl.name, subdecl.parameters, subdecl.returntypes, return Subroutine(subdecl.name, subdecl.parameters.toMutableList(), subdecl.returntypes,
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters, subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
subdecl.asmClobbers, address, true, inline = false, statements = mutableListOf(), position = toPosition() subdecl.asmClobbers, address, true, inline = false, statements = mutableListOf(), position = toPosition()
) )
@ -252,7 +252,9 @@ private fun Prog8ANTLRParser.Asmsub_returnsContext.toAst(): List<AsmSubroutineRe
private fun Prog8ANTLRParser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter> private fun Prog8ANTLRParser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter>
= asmsub_param().map { = asmsub_param().map {
val vardecl = it.vardecl() val vardecl = it.vardecl()
val datatype = vardecl.datatype()?.toAst() ?: DataType.UNDEFINED var datatype = vardecl.datatype()?.toAst() ?: DataType.UNDEFINED
if(vardecl.ARRAYSIG()!=null || vardecl.arrayindex()!=null)
datatype = ElementToArrayTypes.getValue(datatype)
val register = it.register().text val register = it.register().text
var registerorpair: RegisterOrPair? = null var registerorpair: RegisterOrPair? = null
var statusregister: Statusflag? = null var statusregister: Statusflag? = null
@ -304,7 +306,7 @@ private fun Prog8ANTLRParser.SubroutineContext.toAst() : Subroutine {
val inline = inline()!=null val inline = inline()!=null
val returntypes = sub_return_part()?.toAst() ?: emptyList() val returntypes = sub_return_part()?.toAst() ?: emptyList()
return Subroutine(identifier().text, return Subroutine(identifier().text,
sub_params()?.toAst() ?: emptyList(), sub_params()?.toAst()?.toMutableList() ?: mutableListOf(),
returntypes, returntypes,
statement_block()?.toAst() ?: mutableListOf(), statement_block()?.toAst() ?: mutableListOf(),
inline, inline,
@ -318,7 +320,9 @@ private fun Prog8ANTLRParser.Sub_return_partContext.toAst(): List<DataType> {
private fun Prog8ANTLRParser.Sub_paramsContext.toAst(): List<SubroutineParameter> = private fun Prog8ANTLRParser.Sub_paramsContext.toAst(): List<SubroutineParameter> =
vardecl().map { vardecl().map {
val datatype = it.datatype()?.toAst() ?: DataType.UNDEFINED var datatype = it.datatype()?.toAst() ?: DataType.UNDEFINED
if(it.ARRAYSIG()!=null || it.arrayindex()!=null)
datatype = ElementToArrayTypes.getValue(datatype)
SubroutineParameter(it.varname.text, datatype, it.toPosition()) SubroutineParameter(it.varname.text, datatype, it.toPosition())
} }

View File

@ -6,6 +6,7 @@ import prog8.ast.base.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.walk.AstWalker import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstVisitor import prog8.ast.walk.IAstVisitor
import prog8.compilerinterface.IMemSizer
import java.util.* import java.util.*
@ -436,9 +437,18 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
class CastValue(val isValid: Boolean, private val value: NumericLiteralValue?) { class CastValue(val isValid: Boolean, private val value: NumericLiteralValue?) {
fun valueOrZero() = if(isValid) value!! else NumericLiteralValue(DataType.UBYTE, 0, Position.DUMMY) fun valueOrZero() = if(isValid) value!! else NumericLiteralValue(DataType.UBYTE, 0, Position.DUMMY)
fun linkParent(parent: Node) {
value?.linkParents(parent)
}
} }
fun cast(targettype: DataType): CastValue { fun cast(targettype: DataType): CastValue {
val result = internalCast(targettype)
result.linkParent(this.parent)
return result
}
private fun internalCast(targettype: DataType): CastValue {
if(type==targettype) if(type==targettype)
return CastValue(true, this) return CastValue(true, this)
val numval = number.toDouble() val numval = number.toDouble()
@ -513,7 +523,10 @@ class CharLiteral(val value: Char,
} }
override fun referencesIdentifier(vararg scopedName: String) = false override fun referencesIdentifier(vararg scopedName: String) = false
override fun constValue(program: Program): NumericLiteralValue? = null // TODO: CharLiteral.constValue can't be NumericLiteralValue... override fun constValue(program: Program): NumericLiteralValue {
val bytevalue = program.encoding.encodeString(value.toString(), altEncoding).single()
return NumericLiteralValue(DataType.UBYTE, bytevalue, position)
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
@ -730,7 +743,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
if(nameInSource.size==1 && nameInSource[0] in program.builtinFunctions.names) if(nameInSource.size==1 && nameInSource[0] in program.builtinFunctions.names)
BuiltinFunctionStatementPlaceholder(nameInSource[0], position, parent) BuiltinFunctionStatementPlaceholder(nameInSource[0], position, parent)
else else
program.namespace.lookup(nameInSource, this) definingScope.lookup(nameInSource)
fun targetVarDecl(program: Program): VarDecl? = targetStatement(program) as? VarDecl fun targetVarDecl(program: Program): VarDecl? = targetStatement(program) as? VarDecl
fun targetSubroutine(program: Program): Subroutine? = targetStatement(program) as? Subroutine fun targetSubroutine(program: Program): Subroutine? = targetStatement(program) as? Subroutine
@ -747,8 +760,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
} }
override fun constValue(program: Program): NumericLiteralValue? { override fun constValue(program: Program): NumericLiteralValue? {
val node = program.namespace.lookup(nameInSource, this) val node = definingScope.lookup(nameInSource) ?: throw UndefinedSymbolError(this)
?: throw UndefinedSymbolError(this)
val vardecl = node as? VarDecl val vardecl = node as? VarDecl
if(vardecl==null) { if(vardecl==null) {
return null return null
@ -816,7 +828,7 @@ class FunctionCall(override var target: IdentifierReference,
// lenghts of arrays and strings are constants that are determined at compile time! // lenghts of arrays and strings are constants that are determined at compile time!
if(target.nameInSource.size>1) if(target.nameInSource.size>1)
return null return null
val resultValue: NumericLiteralValue? = program.builtinFunctions.constValue(target.nameInSource[0], args, position, program.memsizer) val resultValue: NumericLiteralValue? = program.builtinFunctions.constValue(target.nameInSource[0], args, position)
if(withDatatypeCheck) { if(withDatatypeCheck) {
val resultDt = this.inferType(program) val resultDt = this.inferType(program)
if(resultValue==null || resultDt istype resultValue.type) if(resultValue==null || resultDt istype resultValue.type)

View File

@ -7,7 +7,7 @@ import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstVisitor import prog8.ast.walk.IAstVisitor
interface ISymbolStatement { interface INamedStatement {
val name: String val name: String
} }
@ -32,6 +32,24 @@ sealed class Statement : Node {
scope.add(name) scope.add(name)
return scope.joinToString(".") return scope.joinToString(".")
} }
fun nextSibling(): Statement? {
val statements = (parent as? IStatementContainer)?.statements ?: return null
val nextIdx = statements.indexOfFirst { it===this } + 1
return if(nextIdx < statements.size)
statements[nextIdx]
else
null
}
fun previousSibling(): Statement? {
val statements = (parent as? IStatementContainer)?.statements ?: return null
val previousIdx = statements.indexOfFirst { it===this } - 1
return if(previousIdx >= 0)
statements[previousIdx]
else
null
}
} }
@ -52,7 +70,7 @@ class Block(override val name: String,
val address: Int?, val address: Int?,
override var statements: MutableList<Statement>, override var statements: MutableList<Statement>,
val isInLibrary: Boolean, val isInLibrary: Boolean,
override val position: Position) : Statement(), INameScope, ISymbolStatement { override val position: Position) : Statement(), INameScope {
override lateinit var parent: Node override lateinit var parent: Node
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
@ -99,7 +117,7 @@ data class DirectiveArg(val str: String?, val name: String?, val int: Int?, over
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here") override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
} }
data class Label(override val name: String, override val position: Position) : Statement(), ISymbolStatement { data class Label(override val name: String, override val position: Position) : Statement(), INamedStatement {
override lateinit var parent: Node override lateinit var parent: Node
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
@ -166,7 +184,7 @@ open class VarDecl(val type: VarDeclType,
val isArray: Boolean, val isArray: Boolean,
val autogeneratedDontRemove: Boolean, val autogeneratedDontRemove: Boolean,
val sharedWithAsm: Boolean, val sharedWithAsm: Boolean,
final override val position: Position) : Statement(), ISymbolStatement { final override val position: Position) : Statement(), INamedStatement {
override lateinit var parent: Node override lateinit var parent: Node
var allowInitializeWithZero = true var allowInitializeWithZero = true
@ -386,7 +404,7 @@ data class AssignTarget(var identifier: IdentifierReference?,
fun inferType(program: Program): InferredTypes.InferredType { fun inferType(program: Program): InferredTypes.InferredType {
if (identifier != null) { if (identifier != null) {
val symbol = program.namespace.lookup(identifier!!.nameInSource, this) ?: return InferredTypes.unknown() val symbol = definingScope.lookup(identifier!!.nameInSource) ?: return InferredTypes.unknown()
if (symbol is VarDecl) return InferredTypes.knownFor(symbol.datatype) if (symbol is VarDecl) return InferredTypes.knownFor(symbol.datatype)
} }
@ -538,19 +556,9 @@ class InlineAssembly(val assembly: String, override val position: Position) : St
} }
class AnonymousScope(override var statements: MutableList<Statement>, class AnonymousScope(override var statements: MutableList<Statement>,
override val position: Position) : INameScope, Statement() { // TODO this isn't really a namescope...(names are scoped to the subroutine level) override val position: Position) : IStatementContainer, Statement() {
override val name: String = "<anon-$sequenceNumber>"
override lateinit var parent: Node override lateinit var parent: Node
companion object {
private var sequenceNumber = 1
}
init {
// make sure it's an invalid soruce code identifier so user source code can never produce it
sequenceNumber++
}
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
statements.forEach { it.linkParents(this) } statements.forEach { it.linkParents(this) }
@ -597,7 +605,7 @@ class AsmGenInfo {
// and also the predefined/ROM/register-based subroutines. // and also the predefined/ROM/register-based subroutines.
// (multiple return types can only occur for the latter type) // (multiple return types can only occur for the latter type)
class Subroutine(override val name: String, class Subroutine(override val name: String,
val parameters: List<SubroutineParameter>, val parameters: MutableList<SubroutineParameter>,
val returntypes: List<DataType>, val returntypes: List<DataType>,
val asmParameterRegisters: List<RegisterOrStatusflag>, val asmParameterRegisters: List<RegisterOrStatusflag>,
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>, val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
@ -606,9 +614,9 @@ class Subroutine(override val name: String,
val isAsmSubroutine: Boolean, val isAsmSubroutine: Boolean,
val inline: Boolean, val inline: Boolean,
override var statements: MutableList<Statement>, override var statements: MutableList<Statement>,
override val position: Position) : Statement(), INameScope, ISymbolStatement { override val position: Position) : Statement(), INameScope {
constructor(name: String, parameters: List<SubroutineParameter>, returntypes: List<DataType>, statements: MutableList<Statement>, inline: Boolean, position: Position) constructor(name: String, parameters: MutableList<SubroutineParameter>, returntypes: List<DataType>, statements: MutableList<Statement>, inline: Boolean, position: Position)
: this(name, parameters, returntypes, emptyList(), determineReturnRegisters(returntypes), emptySet(), null, false, inline, statements, position) : this(name, parameters, returntypes, emptyList(), determineReturnRegisters(returntypes), emptySet(), null, false, inline, statements, position)
companion object { companion object {
@ -635,10 +643,19 @@ class Subroutine(override val name: String,
} }
override fun replaceChildNode(node: Node, replacement: Node) { override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Statement) when(replacement) {
val idx = statements.indexOfFirst { it===node } is SubroutineParameter -> {
statements[idx] = replacement val idx = parameters.indexOf(node)
replacement.parent = this parameters[idx] = replacement
replacement.parent = this
}
is Statement -> {
val idx = statements.indexOfFirst { it===node }
statements[idx] = replacement
replacement.parent = this
}
else -> throw FatalAstException("can't replace")
}
} }
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)

View File

@ -2,6 +2,7 @@ package prog8.ast.walk
import prog8.ast.* import prog8.ast.*
import prog8.ast.base.FatalAstException import prog8.ast.base.FatalAstException
import prog8.ast.base.ParentSentinel
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
@ -9,7 +10,7 @@ import prog8.ast.statements.*
interface IAstModification { interface IAstModification {
fun perform() fun perform()
class Remove(val node: Node, val parent: INameScope) : IAstModification { class Remove(val node: Node, val parent: IStatementContainer) : IAstModification {
override fun perform() { override fun perform() {
if (!parent.statements.remove(node) && parent !is GlobalNamespace) if (!parent.statements.remove(node) && parent !is GlobalNamespace)
throw FatalAstException("attempt to remove non-existing node $node") throw FatalAstException("attempt to remove non-existing node $node")
@ -24,21 +25,21 @@ interface IAstModification {
} }
} }
class InsertFirst(private val stmt: Statement, private val parent: INameScope) : IAstModification { class InsertFirst(private val stmt: Statement, private val parent: IStatementContainer) : IAstModification {
override fun perform() { override fun perform() {
parent.statements.add(0, stmt) parent.statements.add(0, stmt)
stmt.linkParents(parent as Node) stmt.linkParents(parent as Node)
} }
} }
class InsertLast(private val stmt: Statement, private val parent: INameScope) : IAstModification { class InsertLast(private val stmt: Statement, private val parent: IStatementContainer) : IAstModification {
override fun perform() { override fun perform() {
parent.statements.add(stmt) parent.statements.add(stmt)
stmt.linkParents(parent as Node) stmt.linkParents(parent as Node)
} }
} }
class InsertAfter(private val after: Statement, private val stmt: Statement, private val parent: INameScope) : class InsertAfter(private val after: Statement, private val stmt: Statement, private val parent: IStatementContainer) :
IAstModification { IAstModification {
override fun perform() { override fun perform() {
val idx = parent.statements.indexOfFirst { it===after } + 1 val idx = parent.statements.indexOfFirst { it===after } + 1
@ -47,7 +48,7 @@ interface IAstModification {
} }
} }
class InsertBefore(private val before: Statement, private val stmt: Statement, private val parent: INameScope) : class InsertBefore(private val before: Statement, private val stmt: Statement, private val parent: IStatementContainer) :
IAstModification { IAstModification {
override fun perform() { override fun perform() {
val idx = parent.statements.indexOfFirst { it===before } val idx = parent.statements.indexOfFirst { it===before }
@ -105,7 +106,7 @@ abstract class AstWalker {
open fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> = noModifications open fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> = noModifications
open fun before(numLiteral: NumericLiteralValue, parent: Node): Iterable<IAstModification> = noModifications open fun before(numLiteral: NumericLiteralValue, parent: Node): Iterable<IAstModification> = noModifications
open fun before(postIncrDecr: PostIncrDecr, parent: Node): Iterable<IAstModification> = noModifications open fun before(postIncrDecr: PostIncrDecr, parent: Node): Iterable<IAstModification> = noModifications
open fun before(program: Program, parent: Node): Iterable<IAstModification> = noModifications open fun before(program: Program): Iterable<IAstModification> = noModifications
open fun before(range: RangeExpr, parent: Node): Iterable<IAstModification> = noModifications open fun before(range: RangeExpr, parent: Node): Iterable<IAstModification> = noModifications
open fun before(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> = noModifications open fun before(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> = noModifications
open fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> = noModifications open fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> = noModifications
@ -146,7 +147,7 @@ abstract class AstWalker {
open fun after(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> = noModifications open fun after(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> = noModifications
open fun after(numLiteral: NumericLiteralValue, parent: Node): Iterable<IAstModification> = noModifications open fun after(numLiteral: NumericLiteralValue, parent: Node): Iterable<IAstModification> = noModifications
open fun after(postIncrDecr: PostIncrDecr, parent: Node): Iterable<IAstModification> = noModifications open fun after(postIncrDecr: PostIncrDecr, parent: Node): Iterable<IAstModification> = noModifications
open fun after(program: Program, parent: Node): Iterable<IAstModification> = noModifications open fun after(program: Program): Iterable<IAstModification> = noModifications
open fun after(range: RangeExpr, parent: Node): Iterable<IAstModification> = noModifications open fun after(range: RangeExpr, parent: Node): Iterable<IAstModification> = noModifications
open fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> = noModifications open fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> = noModifications
open fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> = noModifications open fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> = noModifications
@ -196,9 +197,9 @@ abstract class AstWalker {
} }
fun visit(program: Program) { fun visit(program: Program) {
track(before(program, program), program, program) track(before(program), ParentSentinel, program.namespace)
program.modules.forEach { it.accept(this, program) } program.modules.forEach { it.accept(this, program.namespace) }
track(after(program, program), program, program) track(after(program), ParentSentinel, program.namespace)
} }
fun visit(module: Module, parent: Node) { fun visit(module: Module, parent: Node) {

View File

@ -0,0 +1,11 @@
package prog8.compilerinterface
import prog8.ast.base.DataType
// note: this is a separate interface in the compilerAst module because
// otherwise a cyclic dependency with the compilerInterfaces module would be needed.
interface IMemSizer {
fun memorySize(dt: DataType): Int
}

View File

@ -1,6 +1,6 @@
package prog8.compiler package prog8.compilerinterface
interface IStringEncoding { interface IStringEncoding {
fun encodeString(str: String, altEncoding: Boolean): List<Short> fun encodeString(str: String, altEncoding: Boolean): List<Short>
fun decodeString(bytes: List<Short>, altEncoding: Boolean): String fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
} }

View File

@ -8,14 +8,7 @@ import prog8.ast.statements.Block
import prog8.ast.statements.Directive import prog8.ast.statements.Directive
open class ParsingFailedError(override var message: String) : Exception(message) class ParseError(override var message: String, val position: Position, cause: RuntimeException): Exception(message, cause)
class ParseError(override var message: String, val position: Position, cause: RuntimeException)
: ParsingFailedError("${position.toClickableStr()}$message") {
init {
initCause(cause)
}
}
object Prog8Parser { object Prog8Parser {
@ -107,7 +100,7 @@ object Prog8Parser {
val offending = this.offendingToken val offending = this.offendingToken
val line = offending.line val line = offending.line
val beginCol = offending.charPositionInLine val beginCol = offending.charPositionInLine
val endCol = beginCol + offending.stopIndex - offending.startIndex // TODO: point to col *after* token? / why, what's wrong with endCol being inclusive val endCol = beginCol + offending.stopIndex - offending.startIndex
return Position(file, line, beginCol, endCol) return Position(file, line, beginCol, endCol)
} }

View File

@ -8,9 +8,7 @@ import java.nio.channels.Channels
import java.nio.charset.CodingErrorAction import java.nio.charset.CodingErrorAction
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.nio.file.Path import java.nio.file.Path
import kotlin.io.path.exists import kotlin.io.path.*
import kotlin.io.path.isDirectory
import kotlin.io.path.isReadable
/** /**
* Encapsulates - and ties together - actual source code (=text) * Encapsulates - and ties together - actual source code (=text)
@ -45,9 +43,8 @@ sealed class SourceCode {
/** /**
* The source code as plain string. * The source code as plain string.
* *Note: this is meant for testing and debugging, do NOT use in application code!*
*/ */
fun asString() = this.getCharStream().toString() abstract fun readText(): String
/** /**
* Deliberately does NOT return the actual text. * Deliberately does NOT return the actual text.
@ -62,7 +59,7 @@ sealed class SourceCode {
*/ */
const val libraryFilePrefix = "library:" const val libraryFilePrefix = "library:"
const val stringSourcePrefix = "<String@" const val stringSourcePrefix = "<String@"
val curdir: Path = Path.of(".").toAbsolutePath() val curdir: Path = Path(".").toAbsolutePath()
fun relative(path: Path): Path = curdir.relativize(path.toAbsolutePath()) fun relative(path: Path): Path = curdir.relativize(path.toAbsolutePath())
fun isRegularFilesystemPath(pathString: String) = fun isRegularFilesystemPath(pathString: String) =
!(pathString.startsWith(libraryFilePrefix) || pathString.startsWith(stringSourcePrefix)) !(pathString.startsWith(libraryFilePrefix) || pathString.startsWith(stringSourcePrefix))
@ -77,6 +74,7 @@ sealed class SourceCode {
override val isFromFilesystem = false override val isFromFilesystem = false
override val origin = "$stringSourcePrefix${System.identityHashCode(text).toString(16)}>" override val origin = "$stringSourcePrefix${System.identityHashCode(text).toString(16)}>"
override fun getCharStream(): CharStream = CharStreams.fromString(text, origin) override fun getCharStream(): CharStream = CharStreams.fromString(text, origin)
override fun readText() = text
} }
/** /**
@ -107,6 +105,7 @@ sealed class SourceCode {
override val isFromFilesystem = true override val isFromFilesystem = true
override val origin = relative(normalized).toString() override val origin = relative(normalized).toString()
override fun getCharStream(): CharStream = CharStreams.fromPath(normalized) override fun getCharStream(): CharStream = CharStreams.fromPath(normalized)
override fun readText() = normalized.readText()
} }
/** /**
@ -129,12 +128,17 @@ sealed class SourceCode {
override val isFromResources = true override val isFromResources = true
override val isFromFilesystem = false override val isFromFilesystem = false
override val origin = "$libraryFilePrefix$normalized" override val origin = "$libraryFilePrefix$normalized"
override fun getCharStream(): CharStream { public override fun getCharStream(): CharStream {
val inpStr = object {}.javaClass.getResourceAsStream(normalized)!! val inpStr = object {}.javaClass.getResourceAsStream(normalized)!!
// CharStreams.fromStream() doesn't allow us to set the stream name properly, so we use a lower level api // CharStreams.fromStream() doesn't allow us to set the stream name properly, so we use a lower level api
val channel = Channels.newChannel(inpStr) val channel = Channels.newChannel(inpStr)
return CharStreams.fromChannel(channel, StandardCharsets.UTF_8, 4096, CodingErrorAction.REPLACE, origin, -1); return CharStreams.fromChannel(channel, StandardCharsets.UTF_8, 4096, CodingErrorAction.REPLACE, origin, -1);
} }
override fun readText(): String {
val stream = object {}.javaClass.getResourceAsStream(normalized)
return stream!!.bufferedReader().use { r -> r.readText() }
}
} }
/** /**
@ -145,15 +149,6 @@ sealed class SourceCode {
override val isFromResources: Boolean = false override val isFromResources: Boolean = false
override val isFromFilesystem: Boolean = false override val isFromFilesystem: Boolean = false
override val origin: String = name override val origin: String = name
override fun readText() = throw IOException("generated code nodes don't have a text representation")
} }
// TODO: possibly more, like fromURL(..)
/* // For `jar:..` URLs
// see https://stackoverflow.com/questions/22605666/java-access-files-in-jar-causes-java-nio-file-filesystemnotfoundexception
var url = URL("jar:file:/E:/x16/prog8(meisl)/compiler/build/libs/prog8compiler-7.0-BETA3-all.jar!/prog8lib/c64/textio.p8")
val uri = url.toURI()
val parts = uri.toString().split("!")
val fs = FileSystems.newFileSystem(URI.create(parts[0]), mutableMapOf(Pair("", "")) )
val path = fs.getPath(parts[1])
*/
} }

View File

@ -1,28 +1,29 @@
package prog8tests package prog8tests.ast
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
import prog8.ast.AstToSourceCode import prog8.ast.AstToSourceTextConverter
import prog8.ast.Module import prog8.ast.Module
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.internedStringsModuleName import prog8.ast.internedStringsModuleName
import prog8.parser.ParseError import prog8.parser.ParseError
import prog8.parser.Prog8Parser.parseModule import prog8.parser.Prog8Parser.parseModule
import prog8.parser.SourceCode import prog8.parser.SourceCode
import prog8tests.helpers.DummyFunctions import prog8tests.ast.helpers.DummyFunctions
import prog8tests.helpers.DummyMemsizer import prog8tests.ast.helpers.DummyMemsizer
import prog8tests.ast.helpers.DummyStringEncoder
import kotlin.test.assertContains import kotlin.test.assertContains
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestAstToSourceCode { class TestAstToSourceText {
private fun generateP8(module: Module) : String { private fun generateP8(module: Module) : String {
val program = Program("test", DummyFunctions, DummyMemsizer) val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module) .addModule(module)
var generatedText = "" var generatedText = ""
val it = AstToSourceCode({ str -> generatedText += str }, program) val it = AstToSourceTextConverter({ str -> generatedText += str }, program)
it.visit(program) it.visit(program)
return generatedText return generatedText

View File

@ -1,4 +1,4 @@
package prog8tests package prog8tests.ast
import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
@ -6,6 +6,8 @@ import org.junit.jupiter.api.TestInstance
import prog8.ast.IFunctionCall import prog8.ast.IFunctionCall
import prog8.ast.Module import prog8.ast.Module
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.Position import prog8.ast.base.Position
import prog8.ast.expressions.CharLiteral import prog8.ast.expressions.CharLiteral
import prog8.ast.expressions.NumericLiteralValue import prog8.ast.expressions.NumericLiteralValue
@ -15,9 +17,9 @@ import prog8.ast.statements.*
import prog8.parser.ParseError import prog8.parser.ParseError
import prog8.parser.Prog8Parser.parseModule import prog8.parser.Prog8Parser.parseModule
import prog8.parser.SourceCode import prog8.parser.SourceCode
import prog8tests.helpers.assumeNotExists import prog8tests.ast.helpers.*
import prog8tests.helpers.assumeReadableFile import prog8tests.ast.helpers.DummyFunctions
import prog8tests.helpers.fixturesDir import prog8tests.ast.helpers.DummyMemsizer
import kotlin.io.path.Path import kotlin.io.path.Path
import kotlin.io.path.isRegularFile import kotlin.io.path.isRegularFile
import kotlin.io.path.name import kotlin.io.path.name
@ -285,24 +287,16 @@ class TestProg8Parser {
fun `in ParseError from bad string source code`() { fun `in ParseError from bad string source code`() {
val srcText = "bad * { }\n" val srcText = "bad * { }\n"
assertFailsWith<ParseError> { parseModule(SourceCode.Text(srcText)) } val e = assertFailsWith<ParseError> { parseModule(SourceCode.Text(srcText)) }
try { assertPosition(e.position, Regex("^<String@[0-9a-f\\-]+>$"), 1, 4, 4)
parseModule(SourceCode.Text(srcText))
} catch (e: ParseError) {
assertPosition(e.position, Regex("^<String@[0-9a-f\\-]+>$"), 1, 4, 4)
}
} }
@Test @Test
fun `in ParseError from bad file source code`() { fun `in ParseError from bad file source code`() {
val path = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8") val path = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
assertFailsWith<ParseError> { parseModule(SourceCode.File(path)) } val e = assertFailsWith<ParseError> { parseModule(SourceCode.File(path)) }
try { assertPosition(e.position, SourceCode.relative(path).toString(), 2, 6)
parseModule(SourceCode.File(path))
} catch (e: ParseError) {
assertPosition(e.position, SourceCode.relative(path).toString(), 2, 6) // TODO: endCol wrong
}
} }
@Test @Test
@ -310,16 +304,16 @@ class TestProg8Parser {
val srcText = """ val srcText = """
main { main {
} }
""".trimIndent() """
val module = parseModule(SourceCode.Text(srcText)) val module = parseModule(SourceCode.Text(srcText))
assertPositionOf(module, Regex("^<String@[0-9a-f\\-]+>$"), 1, 0) // TODO: endCol wrong assertPositionOf(module, Regex("^<String@[0-9a-f\\-]+>$"), 1, 0)
} }
@Test @Test
fun `of Module parsed from a file`() { fun `of Module parsed from a file`() {
val path = assumeReadableFile(fixturesDir, "simple_main.p8") val path = assumeReadableFile(fixturesDir, "simple_main.p8")
val module = parseModule(SourceCode.File(path)) val module = parseModule(SourceCode.File(path))
assertPositionOf(module, SourceCode.relative(path).toString(), 1, 0) // TODO: endCol wrong assertPositionOf(module, SourceCode.relative(path).toString(), 1, 0)
} }
@Test @Test
@ -328,27 +322,24 @@ class TestProg8Parser {
val module = parseModule(SourceCode.File(path)) val module = parseModule(SourceCode.File(path))
val mpf = module.position.file val mpf = module.position.file
assertPositionOf(module, SourceCode.relative(path).toString(), 1, 0) // TODO: endCol wrong assertPositionOf(module, SourceCode.relative(path).toString(), 1, 0)
val mainBlock = module.statements.filterIsInstance<Block>()[0] val mainBlock = module.statements.filterIsInstance<Block>()[0]
assertPositionOf(mainBlock, mpf, 2, 0) // TODO: endCol wrong! assertPositionOf(mainBlock, mpf, 2, 0, 3)
val startSub = mainBlock.statements.filterIsInstance<Subroutine>()[0] val startSub = mainBlock.statements.filterIsInstance<Subroutine>()[0]
assertPositionOf(startSub, mpf, 3, 4) // TODO: endCol wrong! assertPositionOf(startSub, mpf, 3, 4, 6)
} }
/**
* TODO: this test is testing way too much at once
*/
@Test @Test
fun `of non-root Nodes parsed from a string`() { fun `of non-root Nodes parsed from a string`() {
val srcText = """ val srcText = """
%zeropage basicsafe ; DirectiveArg directly inherits from Node - neither an Expression nor a Statement..? %zeropage basicsafe
main { main {
sub start() { sub start() {
ubyte foo = 42 ubyte foo = 42
ubyte bar ubyte bar
when (foo) { when (foo) {
23 -> bar = 'x' ; WhenChoice, also directly inheriting Node 23 -> bar = 'x'
42 -> bar = 'y' 42 -> bar = 'y'
else -> bar = 'z' else -> bar = 'z'
} }
@ -359,22 +350,22 @@ class TestProg8Parser {
val mpf = module.position.file val mpf = module.position.file
val targetDirective = module.statements.filterIsInstance<Directive>()[0] val targetDirective = module.statements.filterIsInstance<Directive>()[0]
assertPositionOf(targetDirective, mpf, 1, 0) // TODO: endCol wrong! assertPositionOf(targetDirective, mpf, 1, 0, 8)
val mainBlock = module.statements.filterIsInstance<Block>()[0] val mainBlock = module.statements.filterIsInstance<Block>()[0]
assertPositionOf(mainBlock, mpf, 2, 0) // TODO: endCol wrong! assertPositionOf(mainBlock, mpf, 2, 0, 3)
val startSub = mainBlock.statements.filterIsInstance<Subroutine>()[0] val startSub = mainBlock.statements.filterIsInstance<Subroutine>()[0]
assertPositionOf(startSub, mpf, 3, 4) // TODO: endCol wrong! assertPositionOf(startSub, mpf, 3, 4, 6)
val declFoo = startSub.statements.filterIsInstance<VarDecl>()[0] val declFoo = startSub.statements.filterIsInstance<VarDecl>()[0]
assertPositionOf(declFoo, mpf, 4, 8) // TODO: endCol wrong! assertPositionOf(declFoo, mpf, 4, 8, 12)
val rhsFoo = declFoo.value!! val rhsFoo = declFoo.value!!
assertPositionOf(rhsFoo, mpf, 4, 20) // TODO: endCol wrong! assertPositionOf(rhsFoo, mpf, 4, 20, 21)
val declBar = startSub.statements.filterIsInstance<VarDecl>()[1] val declBar = startSub.statements.filterIsInstance<VarDecl>()[1]
assertPositionOf(declBar, mpf, 5, 8) // TODO: endCol wrong! assertPositionOf(declBar, mpf, 5, 8, 12)
val whenStmt = startSub.statements.filterIsInstance<WhenStatement>()[0] val whenStmt = startSub.statements.filterIsInstance<WhenStatement>()[0]
assertPositionOf(whenStmt, mpf, 6, 8) // TODO: endCol wrong! assertPositionOf(whenStmt, mpf, 6, 8, 11)
assertPositionOf(whenStmt.choices[0], mpf, 7, 12) // TODO: endCol wrong! assertPositionOf(whenStmt.choices[0], mpf, 7, 12, 13)
assertPositionOf(whenStmt.choices[1], mpf, 8, 12) // TODO: endCol wrong! assertPositionOf(whenStmt.choices[1], mpf, 8, 12, 13)
assertPositionOf(whenStmt.choices[2], mpf, 9, 12) // TODO: endCol wrong! assertPositionOf(whenStmt.choices[2], mpf, 9, 12, 15)
} }
} }
@ -581,4 +572,95 @@ class TestProg8Parser {
} }
} }
@Test
fun testCharLiteralConstValue() {
val char1 = CharLiteral('A', false, Position.DUMMY)
val char2 = CharLiteral('z', true, Position.DUMMY)
val program = Program("test", DummyFunctions, DummyMemsizer, AsciiStringEncoder)
assertEquals(65, char1.constValue(program).number.toInt())
assertEquals(122, char2.constValue(program).number.toInt())
}
@Test
fun testLiteralValueComparisons() {
val ten = NumericLiteralValue(DataType.UWORD, 10, Position.DUMMY)
val nine = NumericLiteralValue(DataType.UBYTE, 9, Position.DUMMY)
assertEquals(ten, ten)
assertNotEquals(ten, nine)
assertFalse(ten != ten)
assertTrue(ten != nine)
assertTrue(ten > nine)
assertTrue(ten >= nine)
assertTrue(ten >= ten)
assertFalse(ten > ten)
assertFalse(ten < nine)
assertFalse(ten <= nine)
assertTrue(ten <= ten)
assertFalse(ten < ten)
val abc = StringLiteralValue("abc", false, Position.DUMMY)
val abd = StringLiteralValue("abd", false, Position.DUMMY)
assertEquals(abc, abc)
assertTrue(abc!=abd)
assertFalse(abc!=abc)
}
@Test
fun testAnonScopeStillContainsVarsDirectlyAfterParse() {
val src = SourceCode.Text("""
main {
sub start() {
repeat {
ubyte xx = 99
xx++
}
}
}
""")
val module = parseModule(src)
val mainBlock = module.statements.single() as Block
val start = mainBlock.statements.single() as Subroutine
val repeatbody = (start.statements.single() as RepeatLoop).body
assertFalse(mainBlock.statements.any { it is VarDecl }, "no vars moved to main block")
assertFalse(start.statements.any { it is VarDecl }, "no vars moved to start sub")
assertTrue(repeatbody.statements[0] is VarDecl, "var is still in repeat block (anonymousscope)")
val initvalue = (repeatbody.statements[0] as VarDecl).value as? NumericLiteralValue
assertEquals(99, initvalue?.number?.toInt())
assertTrue(repeatbody.statements[1] is PostIncrDecr)
// the ast processing steps used in the compiler, will eventually move the var up to the containing scope (subroutine).
}
@Test
fun testLabelsWithAnonScopesParsesFine() {
val src = SourceCode.Text("""
main {
sub start() {
goto mylabeloutside
if true {
if true {
goto labeloutside
goto iflabel
}
iflabel:
}
repeat {
goto labelinside
labelinside:
}
labeloutside:
}
}
""")
val module = parseModule(src)
val mainBlock = module.statements.single() as Block
val start = mainBlock.statements.single() as Subroutine
val labels = start.statements.filterIsInstance<Label>()
assertEquals(1, labels.size, "only one label in subroutine scope")
}
} }

View File

@ -1,4 +1,4 @@
package prog8tests package prog8tests.ast
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.core.StringStartsWith import org.hamcrest.core.StringStartsWith
@ -6,7 +6,10 @@ import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
import prog8.parser.SourceCode import prog8.parser.SourceCode
import prog8.parser.SourceCode.Companion.libraryFilePrefix import prog8.parser.SourceCode.Companion.libraryFilePrefix
import prog8tests.helpers.* import prog8tests.ast.helpers.assumeNotExists
import prog8tests.ast.helpers.assumeReadableFile
import prog8tests.ast.helpers.fixturesDir
import prog8tests.ast.helpers.resourcesDir
import kotlin.io.path.Path import kotlin.io.path.Path
import kotlin.test.* import kotlin.test.*
@ -55,7 +58,7 @@ class TestSourceCode {
val src = SourceCode.File(path) val src = SourceCode.File(path)
val expectedOrigin = SourceCode.relative(path).toString() val expectedOrigin = SourceCode.relative(path).toString()
assertEquals(expectedOrigin, src.origin) assertEquals(expectedOrigin, src.origin)
assertEquals(path.toFile().readText(), src.asString()) assertEquals(path.toFile().readText(), src.readText())
assertFalse(src.isFromResources) assertFalse(src.isFromResources)
assertTrue(src.isFromFilesystem) assertTrue(src.isFromFilesystem)
} }
@ -68,7 +71,7 @@ class TestSourceCode {
val src = SourceCode.File(path) val src = SourceCode.File(path)
val expectedOrigin = SourceCode.relative(path).toString() val expectedOrigin = SourceCode.relative(path).toString()
assertEquals(expectedOrigin, src.origin) assertEquals(expectedOrigin, src.origin)
assertEquals(srcFile.readText(), src.asString()) assertEquals(srcFile.readText(), src.readText())
} }
@Test @Test
@ -78,7 +81,7 @@ class TestSourceCode {
val src = SourceCode.Resource(pathString) val src = SourceCode.Resource(pathString)
assertEquals("$libraryFilePrefix/$pathString", src.origin) assertEquals("$libraryFilePrefix/$pathString", src.origin)
assertEquals(srcFile.readText(), src.asString()) assertEquals(srcFile.readText(), src.readText())
assertTrue(src.isFromResources) assertTrue(src.isFromResources)
assertFalse(src.isFromFilesystem) assertFalse(src.isFromFilesystem)
} }
@ -90,7 +93,7 @@ class TestSourceCode {
val src = SourceCode.Resource(pathString) val src = SourceCode.Resource(pathString)
assertEquals("$libraryFilePrefix$pathString", src.origin) assertEquals("$libraryFilePrefix$pathString", src.origin)
assertEquals(srcFile.readText(), src.asString()) assertEquals(srcFile.readText(), src.readText())
} }
@Test @Test
@ -100,7 +103,7 @@ class TestSourceCode {
val src = SourceCode.Resource(pathString) val src = SourceCode.Resource(pathString)
assertEquals("$libraryFilePrefix/$pathString", src.origin) assertEquals("$libraryFilePrefix/$pathString", src.origin)
assertEquals(srcFile.readText(), src.asString()) assertEquals(srcFile.readText(), src.readText())
assertTrue(src.isFromResources, ".isFromResources") assertTrue(src.isFromResources, ".isFromResources")
} }
@ -111,7 +114,7 @@ class TestSourceCode {
val src = SourceCode.Resource(pathString) val src = SourceCode.Resource(pathString)
assertEquals("$libraryFilePrefix$pathString", src.origin) assertEquals("$libraryFilePrefix$pathString", src.origin)
assertEquals(srcFile.readText(), src.asString()) assertEquals(srcFile.readText(), src.readText())
} }
@Test @Test
@ -121,7 +124,7 @@ class TestSourceCode {
val src = SourceCode.Resource(pathString) val src = SourceCode.Resource(pathString)
assertEquals("$libraryFilePrefix/prog8lib/math.p8", src.origin) assertEquals("$libraryFilePrefix/prog8lib/math.p8", src.origin)
assertEquals(srcFile.readText(), src.asString()) assertEquals(srcFile.readText(), src.readText())
assertTrue(src.isFromResources, ".isFromResources") assertTrue(src.isFromResources, ".isFromResources")
} }

View File

@ -0,0 +1,69 @@
package prog8tests.ast
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.base.DataType
import prog8.ast.statements.Block
import prog8.ast.statements.Subroutine
import prog8.parser.Prog8Parser.parseModule
import prog8.parser.SourceCode
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestSubroutines {
@Test
fun stringParameterAcceptedInParser() {
// note: the *parser* should accept this as it is valid *syntax*,
// however, the compiler itself may or may not complain about it later.
val text = """
main {
asmsub asmfunc(str thing @AY) {
}
sub func(str thing) {
}
}
"""
val src = SourceCode.Text(text)
val module = parseModule(src)
val mainBlock = module.statements.single() as Block
val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"}
val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"}
assertTrue(asmfunc.isAsmSubroutine)
assertEquals(DataType.STR, asmfunc.parameters.single().type)
assertTrue(asmfunc.statements.isEmpty())
assertFalse(func.isAsmSubroutine)
assertEquals(DataType.STR, func.parameters.single().type)
assertTrue(func.statements.isEmpty())
}
@Test
fun arrayParameterAcceptedInParser() {
// note: the *parser* should accept this as it is valid *syntax*,
// however, the compiler itself may or may not complain about it later.
val text = """
main {
asmsub asmfunc(ubyte[] thing @AY) {
}
sub func(ubyte[22] thing) {
}
}
"""
val src = SourceCode.Text(text)
val module = parseModule(src)
val mainBlock = module.statements.single() as Block
val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"}
val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"}
assertTrue(asmfunc.isAsmSubroutine)
assertEquals(DataType.ARRAY_UB, asmfunc.parameters.single().type)
assertTrue(asmfunc.statements.isEmpty())
assertFalse(func.isAsmSubroutine)
assertEquals(DataType.ARRAY_UB, func.parameters.single().type)
assertTrue(func.statements.isEmpty())
}
}

View File

@ -1,5 +1,4 @@
package prog8tests.ast package prog8tests.ast.ast
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.containsString import org.hamcrest.Matchers.containsString
@ -12,8 +11,9 @@ import prog8.ast.Program
import prog8.ast.base.Position import prog8.ast.base.Position
import prog8.ast.internedStringsModuleName import prog8.ast.internedStringsModuleName
import prog8.parser.SourceCode import prog8.parser.SourceCode
import prog8tests.helpers.DummyFunctions import prog8tests.ast.helpers.DummyFunctions
import prog8tests.helpers.DummyMemsizer import prog8tests.ast.helpers.DummyMemsizer
import prog8tests.ast.helpers.DummyStringEncoder
import kotlin.test.assertContains import kotlin.test.assertContains
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
import kotlin.test.assertSame import kotlin.test.assertSame
@ -26,7 +26,7 @@ class ProgramTests {
inner class Constructor { inner class Constructor {
@Test @Test
fun withNameBuiltinsAndMemsizer() { fun withNameBuiltinsAndMemsizer() {
val program = Program("foo", DummyFunctions, DummyMemsizer) val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
assertThat(program.modules.size, equalTo(1)) assertThat(program.modules.size, equalTo(1))
assertThat(program.modules[0].name, equalTo(internedStringsModuleName)) assertThat(program.modules[0].name, equalTo(internedStringsModuleName))
assertSame(program, program.modules[0].program) assertSame(program, program.modules[0].program)
@ -39,7 +39,7 @@ class ProgramTests {
inner class AddModule { inner class AddModule {
@Test @Test
fun withEmptyModule() { fun withEmptyModule() {
val program = Program("foo", DummyFunctions, DummyMemsizer) val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
val m1 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("bar")) val m1 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("bar"))
val retVal = program.addModule(m1) val retVal = program.addModule(m1)
@ -63,7 +63,7 @@ class ProgramTests {
inner class MoveModuleToFront { inner class MoveModuleToFront {
@Test @Test
fun withInternedStringsModule() { fun withInternedStringsModule() {
val program = Program("foo", DummyFunctions, DummyMemsizer) val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
val m = program.modules[0] val m = program.modules[0]
assertThat(m.name, equalTo(internedStringsModuleName)) assertThat(m.name, equalTo(internedStringsModuleName))
@ -73,14 +73,14 @@ class ProgramTests {
} }
@Test @Test
fun withForeignModule() { fun withForeignModule() {
val program = Program("foo", DummyFunctions, DummyMemsizer) val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
val m = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("bar")) val m = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("bar"))
assertFailsWith<IllegalArgumentException> { program.moveModuleToFront(m) } assertFailsWith<IllegalArgumentException> { program.moveModuleToFront(m) }
} }
@Test @Test
fun withFirstOfPreviouslyAddedModules() { fun withFirstOfPreviouslyAddedModules() {
val program = Program("foo", DummyFunctions, DummyMemsizer) val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
val m1 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("bar")) val m1 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("bar"))
val m2 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("qmbl")) val m2 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("qmbl"))
program.addModule(m1) program.addModule(m1)
@ -92,7 +92,7 @@ class ProgramTests {
} }
@Test @Test
fun withSecondOfPreviouslyAddedModules() { fun withSecondOfPreviouslyAddedModules() {
val program = Program("foo", DummyFunctions, DummyMemsizer) val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
val m1 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("bar")) val m1 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("bar"))
val m2 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("qmbl")) val m2 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("qmbl"))
program.addModule(m1) program.addModule(m1)
@ -108,7 +108,7 @@ class ProgramTests {
inner class Properties { inner class Properties {
@Test @Test
fun modules() { fun modules() {
val program = Program("foo", DummyFunctions, DummyMemsizer) val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
val ms1 = program.modules val ms1 = program.modules
val ms2 = program.modules val ms2 = program.modules

View File

@ -0,0 +1,44 @@
package prog8tests.ast.helpers
import prog8.ast.IBuiltinFunctions
import prog8.ast.base.DataType
import prog8.ast.base.Position
import prog8.ast.expressions.Expression
import prog8.ast.expressions.InferredTypes
import prog8.ast.expressions.NumericLiteralValue
import prog8.compilerinterface.IMemSizer
import prog8.compilerinterface.IStringEncoding
internal val DummyFunctions = object : IBuiltinFunctions {
override val names: Set<String> = emptySet()
override val purefunctionNames: Set<String> = emptySet()
override fun constValue(
name: String,
args: List<Expression>,
position: Position,
): NumericLiteralValue? = null
override fun returnType(name: String, args: MutableList<Expression>) = InferredTypes.InferredType.unknown()
}
internal val DummyMemsizer = object : IMemSizer {
override fun memorySize(dt: DataType): Int = 0
}
internal val DummyStringEncoder = object : IStringEncoding {
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
return emptyList()
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String {
return ""
}
}
internal val AsciiStringEncoder = object : IStringEncoding {
override fun encodeString(str: String, altEncoding: Boolean): List<Short> = str.map { it.code.toShort() }
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String {
return bytes.joinToString()
}
}

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