mirror of
https://github.com/irmen/prog8.git
synced 2025-06-14 11:23:37 +00:00
Compare commits
131 Commits
Author | SHA1 | Date | |
---|---|---|---|
a3a6812608 | |||
2725c4ad4d | |||
c8cd6e9460 | |||
0cd27d6129 | |||
b47fc1c020 | |||
de6ef7ef5e | |||
f95fe8f1da | |||
bd0dee5db5 | |||
c13b7fd883 | |||
f7e74b3088 | |||
343f01d5e1 | |||
08bacdd090 | |||
41b1c80492 | |||
e5d7316e5d | |||
b043c3a6da | |||
98b2855b9c | |||
f3c52c409f | |||
1307bdc612 | |||
8c2e6971fc | |||
1903990f30 | |||
7d67005709 | |||
9acc2f92d1 | |||
72dfb0bda3 | |||
1635612430 | |||
abda837d2f | |||
101fb0b8aa | |||
10de7dc1f9 | |||
d2309b8114 | |||
6bdd81623f | |||
f538c9f0c3 | |||
8ae3bad6f7 | |||
77de99b383 | |||
312949f336 | |||
1ab635bd7e | |||
b35abd548c | |||
30e1c3307c | |||
08e052380a | |||
0e824c35cc | |||
548374ac2d | |||
9ad79fefc9 | |||
49d37c016e | |||
7c70c79a84 | |||
6916b8bff7 | |||
73dfb5f443 | |||
69b9dfa468 | |||
ab61b8ba0a | |||
5c8c64242f | |||
ddf96943f0 | |||
e773be2f58 | |||
f965804e6d | |||
ec078eba72 | |||
1815cb1bc3 | |||
7b3cd71085 | |||
06128b5d07 | |||
990c8e1f18 | |||
a170506356 | |||
5ecf2a3357 | |||
fa48746ba9 | |||
e2b8c069d7 | |||
14407bd1aa | |||
08db72903c | |||
46f9fab140 | |||
b7d06f2c0a | |||
118196a0bf | |||
586ce1fc80 | |||
adb979df38 | |||
3401cb5b4a | |||
ebf1f12e97 | |||
5766208207 | |||
4bf4771f08 | |||
0e87db9eb7 | |||
1e053783f3 | |||
7afc96112b | |||
7bb41a30ed | |||
3d1b0eb843 | |||
5b9af0b5ae | |||
9219ec539d | |||
c8bd57cd4d | |||
53bf8c09fd | |||
651c383668 | |||
9ed7587e3e | |||
674295e800 | |||
6b02f2eea0 | |||
5237e55326 | |||
3b59592110 | |||
72640ae058 | |||
d916027e75 | |||
8966d2aa06 | |||
de7ea04f54 | |||
bf71fabe0e | |||
b3368acb33 | |||
87220c6697 | |||
a3b5c2ad71 | |||
fb4c1473c5 | |||
2bb2502d20 | |||
fe51698579 | |||
0f0f40bff3 | |||
a798fe72d3 | |||
fba98d03a5 | |||
7dd2517f67 | |||
641477d6f6 | |||
8e56656c8d | |||
564a6a1f62 | |||
69f0c80cd7 | |||
6fcb51cea2 | |||
c58b8a4973 | |||
c8f4ab4f06 | |||
e425c4cca8 | |||
056ec986c2 | |||
de3b2fb95b | |||
789e39c719 | |||
b29c3152db | |||
3831679772 | |||
596f9566d8 | |||
124befe9d6 | |||
895534f32b | |||
50c16fe6de | |||
b092d1a5d3 | |||
a9b45630d7 | |||
c1a39c269e | |||
6fa3f0b6cd | |||
9e5e3d1559 | |||
7135205299 | |||
d99d977d2b | |||
7dd7e562bc | |||
75d857027e | |||
17694c1d01 | |||
749ad700d8 | |||
8f3df3039a | |||
02c315c194 | |||
b697375573 |
6
.idea/kotlinc.xml
generated
6
.idea/kotlinc.xml
generated
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Kotlin2JvmCompilerArguments">
|
||||
<option name="jvmTarget" value="11" />
|
||||
</component>
|
||||
</project>
|
4
.idea/modules.xml
generated
4
.idea/modules.xml
generated
@ -2,7 +2,9 @@
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/codeGeneration/codeGeneration.iml" filepath="$PROJECT_DIR$/codeGeneration/codeGeneration.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeGenCpu6502/codeGenCpu6502.iml" filepath="$PROJECT_DIR$/codeGenCpu6502/codeGenCpu6502.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeGenExperimental6502/codeGenExperimental6502.iml" filepath="$PROJECT_DIR$/codeGenExperimental6502/codeGenExperimental6502.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeGenTargets/codeGenTargets.iml" filepath="$PROJECT_DIR$/codeGenTargets/codeGenTargets.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$/compilerAst/compilerAst.iml" filepath="$PROJECT_DIR$/compilerAst/compilerAst.iml" />
|
||||
|
@ -12,6 +12,9 @@
|
||||
* The same argument applies to `IMemSizer`, and - not entirely sure about that - `IBuiltinFunctions`.
|
||||
|
||||
#### Steps to take, in conceptual (!) order:
|
||||
|
||||
(note: all these steps have been implemented, rejected or otherwise solved now.)
|
||||
|
||||
1. introduce an abstraction `SourceCode` that encapsulates the origin and actual loading of Prog8 source code
|
||||
- from the local file system (use case: user programs)
|
||||
- from resources (prog8lib)
|
||||
|
16
codeGenCpu6502/codeGenCpu6502.iml
Normal file
16
codeGenCpu6502/codeGenCpu6502.iml
Normal file
@ -0,0 +1,16 @@
|
||||
<?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" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="module" module-name="compilerAst" />
|
||||
<orderEntry type="module" module-name="compilerInterfaces" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
</component>
|
||||
</module>
|
File diff suppressed because it is too large
Load Diff
@ -1,8 +1,8 @@
|
||||
package prog8.codegen.target.cpu6502.codegen
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.compilerinterface.IMachineDefinition
|
||||
|
||||
@ -10,7 +10,7 @@ import prog8.compilerinterface.IMachineDefinition
|
||||
// note: see https://wiki.nesdev.org/w/index.php/6502_assembly_optimisations
|
||||
|
||||
|
||||
fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefinition, program: Program): Int {
|
||||
internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefinition, program: Program): Int {
|
||||
|
||||
var numberOfOptimizations = 0
|
||||
|
||||
@ -64,6 +64,11 @@ fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefinition, pr
|
||||
return numberOfOptimizations
|
||||
}
|
||||
|
||||
private fun String.isBranch() = this.startsWith("b")
|
||||
private fun String.isStoreReg() = this.startsWith("sta") || this.startsWith("sty") || this.startsWith("stx")
|
||||
private fun String.isStoreRegOrZero() = this.isStoreReg() || this.startsWith("stz")
|
||||
private fun String.isLoadReg() = this.startsWith("lda") || this.startsWith("ldy") || this.startsWith("ldx")
|
||||
|
||||
private class Modification(val lineIndex: Int, val remove: Boolean, val replacement: String?)
|
||||
|
||||
private fun apply(modifications: List<Modification>, lines: MutableList<String>) {
|
||||
@ -196,9 +201,9 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
||||
sta A1
|
||||
sty A2
|
||||
*/
|
||||
if(first.startsWith("st") && second.startsWith("st")
|
||||
&& third.startsWith("ld") && fourth.startsWith("ld")
|
||||
&& fifth.startsWith("st") && sixth.startsWith("st")) {
|
||||
if(first.isStoreReg() && second.isStoreReg()
|
||||
&& third.isLoadReg() && fourth.isLoadReg()
|
||||
&& fifth.isStoreReg() && sixth.isStoreReg()) {
|
||||
val reg1 = first[2]
|
||||
val reg2 = second[2]
|
||||
val reg3 = third[2]
|
||||
@ -227,8 +232,8 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
||||
lda A1 ; can be removed
|
||||
ldy A2 ; can be removed if not followed by a branch instuction
|
||||
*/
|
||||
if(!overlappingMods && first.startsWith("st") && second.startsWith("st")
|
||||
&& third.startsWith("ld") && fourth.startsWith("ld")) {
|
||||
if(!overlappingMods && first.isStoreReg() && second.isStoreReg()
|
||||
&& third.isLoadReg() && fourth.isLoadReg()) {
|
||||
val reg1 = first[2]
|
||||
val reg2 = second[2]
|
||||
val reg3 = third[2]
|
||||
@ -249,13 +254,14 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
sta A1
|
||||
sty A2
|
||||
sty A2 ; ... or stz
|
||||
lda A1 ; can be removed if not followed by a branch instruction
|
||||
*/
|
||||
if(!overlappingMods && first.startsWith("st") && second.startsWith("st")
|
||||
&& third.startsWith("ld") && !fourth.startsWith("b")) {
|
||||
if(!overlappingMods && first.isStoreReg() && second.isStoreRegOrZero()
|
||||
&& third.isLoadReg() && !fourth.isBranch()) {
|
||||
val reg1 = first[2]
|
||||
val reg3 = third[2]
|
||||
if(reg1==reg3) {
|
||||
@ -276,7 +282,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
||||
ldy A1 ; make tay
|
||||
sta A1 ; remove
|
||||
*/
|
||||
if(!overlappingMods && first.startsWith("sta") && second.startsWith("ld")
|
||||
if(!overlappingMods && first.startsWith("sta") && second.isLoadReg()
|
||||
&& third.startsWith("sta") && second.length>4) {
|
||||
val firstvalue = first.substring(4)
|
||||
val secondvalue = second.substring(4)
|
||||
@ -293,10 +299,10 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
||||
}
|
||||
|
||||
/*
|
||||
sta A
|
||||
sta A
|
||||
sta A ; or stz double store, remove this first one
|
||||
sta A ; or stz
|
||||
*/
|
||||
if(!overlappingMods && first.startsWith("st") && second.startsWith("st")) {
|
||||
if(!overlappingMods && first.isStoreRegOrZero() && second.isStoreRegOrZero()) {
|
||||
if(first[2]==second[2]) {
|
||||
val firstvalue = first.substring(4)
|
||||
val secondvalue = second.substring(4)
|
||||
@ -304,7 +310,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
||||
val address = getAddressArg(first, program)
|
||||
if(address==null || !machine.isIOAddress(address)) {
|
||||
overlappingMods = true
|
||||
mods.add(Modification(lines[1].index, true, null))
|
||||
mods.add(Modification(lines[0].index, true, null))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -333,7 +339,7 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>,
|
||||
) {
|
||||
val third = lines[3].value.trimStart()
|
||||
val attemptRemove =
|
||||
if(third.startsWith("b")) {
|
||||
if(third.isBranch()) {
|
||||
// a branch instruction follows, we can only remove the load instruction if
|
||||
// another load instruction of the same register precedes the store instruction
|
||||
// (otherwise wrong cpu flags are used)
|
||||
@ -400,7 +406,7 @@ private fun getAddressArg(line: String, program: Program): UInt? {
|
||||
when(decl.type){
|
||||
VarDeclType.VAR -> null
|
||||
VarDeclType.CONST,
|
||||
VarDeclType.MEMORY -> (decl.value as NumericLiteralValue).number.toUInt()
|
||||
VarDeclType.MEMORY -> (decl.value as NumericLiteral).number.toUInt()
|
||||
}
|
||||
}
|
||||
else null
|
@ -1,12 +1,9 @@
|
||||
package prog8.codegen.target.cbm
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import com.github.michaelbull.result.Ok
|
||||
import com.github.michaelbull.result.Result
|
||||
import com.github.michaelbull.result.mapError
|
||||
import prog8.compilerinterface.CompilationOptions
|
||||
import prog8.compilerinterface.IAssemblyProgram
|
||||
import prog8.compilerinterface.OutputType
|
||||
import prog8.compilerinterface.generatedLabelPrefix
|
||||
import prog8.compilerinterface.*
|
||||
import prog8.parser.SourceCode
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
@ -14,13 +11,10 @@ import kotlin.io.path.Path
|
||||
import kotlin.io.path.isRegularFile
|
||||
|
||||
|
||||
internal fun viceMonListName(baseFilename: String) = "$baseFilename.vice-mon-list"
|
||||
|
||||
class AssemblyProgram(
|
||||
override val valid: Boolean,
|
||||
internal class AssemblyProgram(
|
||||
override val name: String,
|
||||
outputDir: Path,
|
||||
private val compTarget: String) : IAssemblyProgram {
|
||||
private val compTarget: ICompilationTarget) : IAssemblyProgram {
|
||||
|
||||
private val assemblyFile = outputDir.resolve("$name.asm")
|
||||
private val prgFile = outputDir.resolve("$name.prg")
|
||||
@ -28,7 +22,7 @@ class AssemblyProgram(
|
||||
private val viceMonListFile = outputDir.resolve(viceMonListName(name))
|
||||
private val listFile = outputDir.resolve("$name.list")
|
||||
|
||||
override fun assemble(options: CompilationOptions): Int {
|
||||
override fun assemble(options: CompilationOptions): Boolean {
|
||||
// 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",
|
||||
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
|
||||
@ -44,12 +38,12 @@ class AssemblyProgram(
|
||||
val outFile = when (options.output) {
|
||||
OutputType.PRG -> {
|
||||
command.add("--cbm-prg")
|
||||
println("\nCreating prg for target $compTarget.")
|
||||
println("\nCreating prg for target ${compTarget.name}.")
|
||||
prgFile
|
||||
}
|
||||
OutputType.RAW -> {
|
||||
command.add("--nostart")
|
||||
println("\nCreating raw binary for target $compTarget.")
|
||||
println("\nCreating raw binary for target ${compTarget.name}.")
|
||||
binFile
|
||||
}
|
||||
}
|
||||
@ -61,7 +55,7 @@ class AssemblyProgram(
|
||||
removeGeneratedLabelsFromMonlist()
|
||||
generateBreakpointList()
|
||||
}
|
||||
return result
|
||||
return result==0
|
||||
}
|
||||
|
||||
private fun removeGeneratedLabelsFromMonlist() {
|
||||
@ -96,13 +90,13 @@ class AssemblyProgram(
|
||||
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()
|
||||
SourceCode.Resource("/prog8lib/${filename.substring(SourceCode.libraryFilePrefix.length)}").text
|
||||
}.mapError { NoSuchFileException(File(filename)) }
|
||||
} else {
|
||||
val sib = Path(source.origin).resolveSibling(filename)
|
||||
if (sib.isRegularFile())
|
||||
Ok(SourceCode.File(sib).readText())
|
||||
Ok(SourceCode.File(sib).text)
|
||||
else
|
||||
Ok(SourceCode.File(Path(filename)).readText())
|
||||
Ok(SourceCode.File(Path(filename)).text)
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package prog8.codegen.target.cpu6502.codegen
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Node
|
||||
@ -10,16 +10,19 @@ import prog8.ast.statements.DirectMemoryWrite
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.toHex
|
||||
import prog8.codegen.target.AssemblyError
|
||||
import prog8.codegen.target.Cx16Target
|
||||
import prog8.codegen.target.cpu6502.codegen.assignment.*
|
||||
import prog8.codegen.cpu6502.assignment.*
|
||||
import prog8.compilerinterface.AssemblyError
|
||||
import prog8.compilerinterface.BuiltinFunctions
|
||||
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 fun translateFunctioncallExpression(fcall: FunctionCallExpr, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||
internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
private val asmgen: AsmGen,
|
||||
private val assignAsmGen: AssignmentAsmGen,
|
||||
private val allocations: VariableAllocator) {
|
||||
|
||||
internal fun translateFunctioncallExpression(fcall: FunctionCallExpression, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||
translateFunctioncall(fcall, func, discardResult = false, resultToStack = resultToStack, resultRegister = resultRegister)
|
||||
}
|
||||
|
||||
@ -27,6 +30,37 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
translateFunctioncall(fcall, func, discardResult = true, resultToStack = false, resultRegister = null)
|
||||
}
|
||||
|
||||
internal fun translateFunctioncall(name: String, args: List<AsmAssignSource>, isStatement: Boolean, scope: Subroutine): DataType {
|
||||
val func = BuiltinFunctions.getValue(name)
|
||||
val argExpressions = args.map { src ->
|
||||
when(src.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> src.number!!
|
||||
SourceStorageKind.EXPRESSION -> src.expression!!
|
||||
SourceStorageKind.ARRAY -> src.array!!
|
||||
else -> {
|
||||
// TODO make it so that we can assign efficiently from something else as an expression....namely: register(s)
|
||||
// this is useful in pipe expressions for instance, to skip the use of a temporary variable
|
||||
// but for now, just assign it to a temporary variable and use that as a source
|
||||
val tempvar = asmgen.getTempVarName(src.datatype)
|
||||
val assignTempvar = AsmAssignment(
|
||||
src,
|
||||
AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, src.datatype, scope, variableAsmName = asmgen.asmVariableName(tempvar)),
|
||||
false, program.memsizer, Position.DUMMY
|
||||
)
|
||||
assignAsmGen.translateNormalAssignment(assignTempvar)
|
||||
// now use an expression to assign this tempvar
|
||||
val ident = IdentifierReference(tempvar, Position.DUMMY)
|
||||
ident.linkParents(scope)
|
||||
ident
|
||||
}
|
||||
}
|
||||
}.toMutableList()
|
||||
val fcall = FunctionCallExpression(IdentifierReference(listOf(name), Position.DUMMY), argExpressions, Position.DUMMY)
|
||||
fcall.linkParents(scope)
|
||||
translateFunctioncall(fcall, func, discardResult = false, resultToStack = false, null)
|
||||
return if(isStatement) DataType.UNDEFINED else func.known_returntype!!
|
||||
}
|
||||
|
||||
private fun translateFunctioncall(fcall: IFunctionCall, func: FSignature, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||
if (discardResult && func.pure)
|
||||
return // can just ignore the whole function call altogether
|
||||
@ -66,6 +100,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
"peekw" -> funcPeekW(fcall, resultToStack, resultRegister)
|
||||
"peek" -> throw AssemblyError("peek() should have been replaced by @()")
|
||||
"pokew" -> funcPokeW(fcall)
|
||||
"pokemon" -> { /* meme function */ }
|
||||
"poke" -> throw AssemblyError("poke() should have been replaced by @()")
|
||||
"push", "pushw" -> funcPush(fcall, func)
|
||||
"pop", "popw" -> funcPop(fcall, func)
|
||||
@ -243,7 +278,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
}
|
||||
|
||||
private fun funcCallFar(fcall: IFunctionCall) {
|
||||
if(asmgen.options.compTarget !is Cx16Target)
|
||||
if(asmgen.options.compTarget.name != "cx16")
|
||||
throw AssemblyError("callfar only works on cx16 target at this time")
|
||||
|
||||
val bank = fcall.args[0].constValue(program)?.number?.toInt()
|
||||
@ -274,7 +309,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
.byte ${bank.toHex()}
|
||||
sta ${asmgen.asmVariableName(argAddrArg.identifier)}""")
|
||||
}
|
||||
is NumericLiteralValue -> {
|
||||
is NumericLiteral -> {
|
||||
asmgen.out("""
|
||||
lda ${argAddrArg.number.toHex()}
|
||||
jsr cx16.jsrfar
|
||||
@ -288,7 +323,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
}
|
||||
|
||||
private fun funcCallRom(fcall: IFunctionCall) {
|
||||
if(asmgen.options.compTarget !is Cx16Target)
|
||||
if(asmgen.options.compTarget.name != "cx16")
|
||||
throw AssemblyError("callrom only works on cx16 target at this time")
|
||||
|
||||
val bank = fcall.args[0].constValue(program)?.number?.toInt()
|
||||
@ -327,7 +362,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
pla
|
||||
sta $01""")
|
||||
}
|
||||
is NumericLiteralValue -> {
|
||||
is NumericLiteral -> {
|
||||
asmgen.out("""
|
||||
lda $01
|
||||
pha
|
||||
@ -356,12 +391,12 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||
asmgen.out(" cmp ${asmgen.asmVariableName(arg2)}")
|
||||
}
|
||||
is NumericLiteralValue -> {
|
||||
is NumericLiteral -> {
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||
asmgen.out(" cmp #${arg2.number.toInt()}")
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
if(arg2.addressExpression is NumericLiteralValue) {
|
||||
if(arg2.addressExpression is NumericLiteral) {
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||
asmgen.out(" cmp ${arg2.addressExpression.constValue(program)!!.number.toHex()}")
|
||||
} else {
|
||||
@ -390,7 +425,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
cmp ${asmgen.asmVariableName(arg2)}
|
||||
+""")
|
||||
}
|
||||
is NumericLiteralValue -> {
|
||||
is NumericLiteral -> {
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
|
||||
asmgen.out("""
|
||||
cpy #>${arg2.number.toInt()}
|
||||
@ -414,16 +449,16 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
}
|
||||
|
||||
private fun funcMemory(fcall: IFunctionCall, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||
if(discardResult || fcall !is FunctionCallExpr)
|
||||
if(discardResult || fcall !is FunctionCallExpression)
|
||||
throw AssemblyError("should not discard result of memory allocation at $fcall")
|
||||
val nameRef = fcall.args[0] as IdentifierReference
|
||||
val name = (nameRef.targetVarDecl(program)!!.value as StringLiteralValue).value
|
||||
val name = (fcall.args[0] as StringLiteral).value
|
||||
require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name"}
|
||||
val size = (fcall.args[1] as NumericLiteralValue).number.toUInt()
|
||||
val size = (fcall.args[1] as NumericLiteral).number.toUInt()
|
||||
val align = (fcall.args[2] as NumericLiteral).number.toUInt()
|
||||
|
||||
val existingSize = asmgen.slabs[name]
|
||||
if(existingSize!=null && existingSize!=size)
|
||||
throw AssemblyError("memory slab '$name' already exists with a different size ($size) at ${fcall.position}")
|
||||
val existing = allocations.getMemorySlab(name)
|
||||
if(existing!=null && (existing.first!=size || existing.second!=align))
|
||||
throw AssemblyError("memory slab '$name' already exists with a different size or alignment at ${fcall.position}")
|
||||
|
||||
val slabname = IdentifierReference(listOf("prog8_slabs", name), fcall.position)
|
||||
slabname.linkParents(fcall)
|
||||
@ -435,7 +470,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, null, program, asmgen)
|
||||
val assign = AsmAssignment(src, target, false, program.memsizer, fcall.position)
|
||||
asmgen.translateNormalAssignment(assign)
|
||||
asmgen.slabs[name] = size
|
||||
allocations.allocateMemorySlab(name, size, align)
|
||||
}
|
||||
|
||||
private fun funcSqrt16(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
|
||||
@ -547,8 +582,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
asmgen.out(" jsr prog8_lib.ror2_array_ub")
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
if (what.addressExpression is NumericLiteralValue) {
|
||||
val number = (what.addressExpression as NumericLiteralValue).number
|
||||
if (what.addressExpression is NumericLiteral) {
|
||||
val number = (what.addressExpression as NumericLiteral).number
|
||||
asmgen.out(" lda ${number.toHex()} | lsr a | bcc + | ora #\$80 |+ | sta ${number.toHex()}")
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(what.addressExpression, RegisterOrPair.AY)
|
||||
@ -590,8 +625,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
asmgen.out(" jsr prog8_lib.ror_array_ub")
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
if (what.addressExpression is NumericLiteralValue) {
|
||||
val number = (what.addressExpression as NumericLiteralValue).number
|
||||
if (what.addressExpression is NumericLiteral) {
|
||||
val number = (what.addressExpression as NumericLiteral).number
|
||||
asmgen.out(" ror ${number.toHex()}")
|
||||
} else {
|
||||
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.addressExpression)
|
||||
@ -648,8 +683,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
asmgen.out(" jsr prog8_lib.rol2_array_ub")
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
if (what.addressExpression is NumericLiteralValue) {
|
||||
val number = (what.addressExpression as NumericLiteralValue).number
|
||||
if (what.addressExpression is NumericLiteral) {
|
||||
val number = (what.addressExpression as NumericLiteral).number
|
||||
asmgen.out(" lda ${number.toHex()} | cmp #\$80 | rol a | sta ${number.toHex()}")
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(what.addressExpression, RegisterOrPair.AY)
|
||||
@ -691,8 +726,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
asmgen.out(" jsr prog8_lib.rol_array_ub")
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
if (what.addressExpression is NumericLiteralValue) {
|
||||
val number = (what.addressExpression as NumericLiteralValue).number
|
||||
if (what.addressExpression is NumericLiteral) {
|
||||
val number = (what.addressExpression as NumericLiteral).number
|
||||
asmgen.out(" rol ${number.toHex()}")
|
||||
} else {
|
||||
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.addressExpression)
|
||||
@ -921,8 +956,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
|
||||
// optimized simple case: swap two memory locations
|
||||
if(first is DirectMemoryRead && second is DirectMemoryRead) {
|
||||
val addr1 = (first.addressExpression as? NumericLiteralValue)?.number?.toHex()
|
||||
val addr2 = (second.addressExpression as? NumericLiteralValue)?.number?.toHex()
|
||||
val addr1 = (first.addressExpression as? NumericLiteral)?.number?.toHex()
|
||||
val addr2 = (second.addressExpression as? NumericLiteral)?.number?.toHex()
|
||||
val name1 = if(first.addressExpression is IdentifierReference) asmgen.asmVariableName(first.addressExpression as IdentifierReference) else null
|
||||
val name2 = if(second.addressExpression is IdentifierReference) asmgen.asmVariableName(second.addressExpression as IdentifierReference) else null
|
||||
|
||||
@ -953,10 +988,10 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
if(pointerVariable != null
|
||||
&& pointerVariable isSameAs secondExpr.left
|
||||
&& firstExpr.operator == "+" && secondExpr.operator == "+"
|
||||
&& (firstOffset is NumericLiteralValue || firstOffset is IdentifierReference || firstOffset is TypecastExpression)
|
||||
&& (secondOffset is NumericLiteralValue || secondOffset is IdentifierReference || secondOffset is TypecastExpression)
|
||||
&& (firstOffset is NumericLiteral || firstOffset is IdentifierReference || firstOffset is TypecastExpression)
|
||||
&& (secondOffset is NumericLiteral || secondOffset is IdentifierReference || secondOffset is TypecastExpression)
|
||||
) {
|
||||
if(firstOffset is NumericLiteralValue && secondOffset is NumericLiteralValue) {
|
||||
if(firstOffset is NumericLiteral && secondOffset is NumericLiteral) {
|
||||
if(firstOffset!=secondOffset) {
|
||||
swapArrayValues(
|
||||
DataType.UBYTE,
|
||||
@ -995,9 +1030,9 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
val elementIDt = first.inferType(program)
|
||||
val elementDt = elementIDt.getOrElse { throw AssemblyError("unknown dt") }
|
||||
|
||||
val firstNum = first.indexer.indexExpr as? NumericLiteralValue
|
||||
val firstNum = first.indexer.indexExpr as? NumericLiteral
|
||||
val firstVar = first.indexer.indexExpr as? IdentifierReference
|
||||
val secondNum = second.indexer.indexExpr as? NumericLiteralValue
|
||||
val secondNum = second.indexer.indexExpr as? NumericLiteral
|
||||
val secondVar = second.indexer.indexExpr as? IdentifierReference
|
||||
|
||||
if(firstNum!=null && secondNum!=null) {
|
||||
@ -1066,7 +1101,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
}
|
||||
}
|
||||
|
||||
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexValue1: NumericLiteralValue, arrayVarName2: String, indexValue2: NumericLiteralValue) {
|
||||
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexValue1: NumericLiteral, arrayVarName2: String, indexValue2: NumericLiteral) {
|
||||
val index1 = indexValue1.number.toInt() * program.memsizer.memorySize(elementDt)
|
||||
val index2 = indexValue2.number.toInt() * program.memsizer.memorySize(elementDt)
|
||||
when(elementDt) {
|
||||
@ -1180,7 +1215,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
}
|
||||
}
|
||||
|
||||
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexValue1: NumericLiteralValue, arrayVarName2: String, indexName2: IdentifierReference) {
|
||||
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexValue1: NumericLiteral, arrayVarName2: String, indexName2: IdentifierReference) {
|
||||
val index1 = indexValue1.number.toInt() * program.memsizer.memorySize(elementDt)
|
||||
val idxAsmName2 = asmgen.asmVariableName(indexName2)
|
||||
when(elementDt) {
|
||||
@ -1238,7 +1273,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
}
|
||||
}
|
||||
|
||||
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexName1: IdentifierReference, arrayVarName2: String, indexValue2: NumericLiteralValue) {
|
||||
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexName1: IdentifierReference, arrayVarName2: String, indexValue2: NumericLiteral) {
|
||||
val idxAsmName1 = asmgen.asmVariableName(indexName1)
|
||||
val index2 = indexValue2.number.toInt() * program.memsizer.memorySize(elementDt)
|
||||
when(elementDt) {
|
||||
@ -1349,7 +1384,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
|
||||
private fun funcPokeW(fcall: IFunctionCall) {
|
||||
when(val addrExpr = fcall.args[0]) {
|
||||
is NumericLiteralValue -> {
|
||||
is NumericLiteral -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AY)
|
||||
val addr = addrExpr.number.toHex()
|
||||
asmgen.out(" sta $addr | sty ${addr}+1")
|
||||
@ -1380,13 +1415,13 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
}
|
||||
}
|
||||
is BinaryExpression -> {
|
||||
if(addrExpr.operator=="+" && addrExpr.left is IdentifierReference && addrExpr.right is NumericLiteralValue) {
|
||||
if(addrExpr.operator=="+" && addrExpr.left is IdentifierReference && addrExpr.right is NumericLiteral) {
|
||||
val varname = asmgen.asmVariableName(addrExpr.left as IdentifierReference)
|
||||
if(asmgen.isZpVar(addrExpr.left as IdentifierReference)) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine!!)
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AX)
|
||||
val index = (addrExpr.right as NumericLiteralValue).number.toHex()
|
||||
val index = (addrExpr.right as NumericLiteral).number.toHex()
|
||||
asmgen.out("""
|
||||
ldy #$index
|
||||
sta ($varname),y
|
||||
@ -1408,7 +1443,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
|
||||
private fun funcPeekW(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||
when(val addrExpr = fcall.args[0]) {
|
||||
is NumericLiteralValue -> {
|
||||
is NumericLiteral -> {
|
||||
val addr = addrExpr.number.toHex()
|
||||
asmgen.out(" lda $addr | ldy ${addr}+1")
|
||||
}
|
||||
@ -1438,11 +1473,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
}
|
||||
}
|
||||
is BinaryExpression -> {
|
||||
if(addrExpr.operator=="+" && addrExpr.left is IdentifierReference && addrExpr.right is NumericLiteralValue) {
|
||||
if(addrExpr.operator=="+" && addrExpr.left is IdentifierReference && addrExpr.right is NumericLiteral) {
|
||||
val varname = asmgen.asmVariableName(addrExpr.left as IdentifierReference)
|
||||
if(asmgen.isZpVar(addrExpr.left as IdentifierReference)) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
val index = (addrExpr.right as NumericLiteralValue).number.toHex()
|
||||
val index = (addrExpr.right as NumericLiteral).number.toHex()
|
||||
asmgen.out("""
|
||||
ldy #$index
|
||||
lda ($varname),y
|
||||
@ -1489,14 +1524,14 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
|
||||
} else {
|
||||
val reg = resultRegister ?: RegisterOrPair.AY
|
||||
var needAsave = !(fcall.args[0] is DirectMemoryRead || fcall.args[0] is NumericLiteralValue || fcall.args[0] is IdentifierReference)
|
||||
var needAsave = !(fcall.args[0] is DirectMemoryRead || fcall.args[0] is NumericLiteral || fcall.args[0] is IdentifierReference)
|
||||
if(!needAsave) {
|
||||
val mr0 = fcall.args[0] as? DirectMemoryRead
|
||||
val mr1 = fcall.args[1] as? DirectMemoryRead
|
||||
if (mr0 != null)
|
||||
needAsave = mr0.addressExpression !is NumericLiteralValue && mr0.addressExpression !is IdentifierReference
|
||||
needAsave = mr0.addressExpression !is NumericLiteral && mr0.addressExpression !is IdentifierReference
|
||||
if (mr1 != null)
|
||||
needAsave = needAsave or (mr1.addressExpression !is NumericLiteralValue && mr1.addressExpression !is IdentifierReference)
|
||||
needAsave = needAsave or (mr1.addressExpression !is NumericLiteral && mr1.addressExpression !is IdentifierReference)
|
||||
}
|
||||
when(reg) {
|
||||
RegisterOrPair.AX -> {
|
||||
@ -1539,7 +1574,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
val arg = fcall.args.single()
|
||||
if (!arg.inferType(program).isWords)
|
||||
throw AssemblyError("msb required word argument")
|
||||
if (arg is NumericLiteralValue)
|
||||
if (arg is NumericLiteral)
|
||||
throw AssemblyError("msb(const) should have been const-folded away")
|
||||
if (arg is IdentifierReference) {
|
||||
val sourceName = asmgen.asmVariableName(arg)
|
||||
@ -1550,6 +1585,16 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
null, RegisterOrPair.A -> asmgen.out(" lda $sourceName+1")
|
||||
RegisterOrPair.X -> asmgen.out(" ldx $sourceName+1")
|
||||
RegisterOrPair.Y -> asmgen.out(" ldy $sourceName+1")
|
||||
RegisterOrPair.AX -> asmgen.out(" lda $sourceName+1 | ldx #0")
|
||||
RegisterOrPair.AY -> asmgen.out(" lda $sourceName+1 | ldy #0")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName+1 | ldy #0")
|
||||
in Cx16VirtualRegisters -> {
|
||||
val regname = resultRegister.name.lowercase()
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" lda $sourceName+1 | sta cx16.$regname | stz cx16.$regname+1")
|
||||
else
|
||||
asmgen.out(" lda $sourceName+1 | sta cx16.$regname | lda #0 | sta cx16.$regname+1")
|
||||
}
|
||||
else -> throw AssemblyError("invalid reg")
|
||||
}
|
||||
}
|
||||
@ -1583,7 +1628,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
val arg = fcall.args.single()
|
||||
if (!arg.inferType(program).isWords)
|
||||
throw AssemblyError("lsb required word argument")
|
||||
if (arg is NumericLiteralValue)
|
||||
if (arg is NumericLiteral)
|
||||
throw AssemblyError("lsb(const) should have been const-folded away")
|
||||
|
||||
if (arg is IdentifierReference) {
|
||||
@ -1595,6 +1640,16 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
null, RegisterOrPair.A -> asmgen.out(" lda $sourceName")
|
||||
RegisterOrPair.X -> asmgen.out(" ldx $sourceName")
|
||||
RegisterOrPair.Y -> asmgen.out(" ldy $sourceName")
|
||||
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx #0")
|
||||
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy #0")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy #0")
|
||||
in Cx16VirtualRegisters -> {
|
||||
val regname = resultRegister.name.lowercase()
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" lda $sourceName | sta cx16.$regname | stz cx16.$regname+1")
|
||||
else
|
||||
asmgen.out(" lda $sourceName | sta cx16.$regname | lda #0 | sta cx16.$regname+1")
|
||||
}
|
||||
else -> throw AssemblyError("invalid reg")
|
||||
}
|
||||
}
|
||||
@ -1655,14 +1710,14 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
val addr = AddressOf(value, value.position)
|
||||
AsmAssignSource.fromAstSource(addr, program, asmgen)
|
||||
}
|
||||
is NumericLiteralValue -> {
|
||||
is NumericLiteral -> {
|
||||
throw AssemblyError("float literals should have been converted into autovar")
|
||||
}
|
||||
else -> {
|
||||
if(scope==null)
|
||||
throw AssemblyError("cannot use float arguments outside of a subroutine scope")
|
||||
|
||||
scope.asmGenInfo.usedFloatEvalResultVar2 = true
|
||||
allocations.subroutineExtra(scope).usedFloatEvalResultVar2 = true
|
||||
val variable = IdentifierReference(listOf(subroutineFloatEvalResultVar2), value.position)
|
||||
val addr = AddressOf(variable, value.position)
|
||||
addr.linkParents(value)
|
@ -1,4 +1,4 @@
|
||||
package prog8.codegen.target.cpu6502.codegen
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
@ -6,12 +6,14 @@ import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.BuiltinFunctionPlaceholder
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.toHex
|
||||
import prog8.codegen.target.AssemblyError
|
||||
import prog8.compilerinterface.AssemblyError
|
||||
import prog8.compilerinterface.BuiltinFunctions
|
||||
import prog8.compilerinterface.CpuType
|
||||
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,
|
||||
private val allocator: VariableAllocator) {
|
||||
|
||||
@Deprecated("avoid calling this as it generates slow evalstack based code")
|
||||
internal fun translateExpression(expression:Expression) {
|
||||
@ -34,18 +36,19 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
is TypecastExpression -> translateExpression(expression)
|
||||
is AddressOf -> translateExpression(expression)
|
||||
is DirectMemoryRead -> asmgen.translateDirectMemReadExpressionToRegAorStack(expression, true)
|
||||
is NumericLiteralValue -> translateExpression(expression)
|
||||
is NumericLiteral -> translateExpression(expression)
|
||||
is IdentifierReference -> translateExpression(expression)
|
||||
is FunctionCallExpr -> translateFunctionCallResultOntoStack(expression)
|
||||
is FunctionCallExpression -> translateFunctionCallResultOntoStack(expression)
|
||||
is PipeExpression -> asmgen.translatePipeExpression(expression.expressions, expression,false, true)
|
||||
is ContainmentCheck -> throw AssemblyError("containment check as complex expression value is not supported")
|
||||
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 ArrayLiteral, is StringLiteral -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable")
|
||||
is RangeExpression -> 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")
|
||||
else -> TODO("missing expression asmgen for $expression")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateFunctionCallResultOntoStack(call: FunctionCallExpr) {
|
||||
private fun translateFunctionCallResultOntoStack(call: FunctionCallExpression) {
|
||||
// only for use in nested expression evaluation
|
||||
|
||||
val sub = call.target.targetStatement(program)
|
||||
@ -211,7 +214,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
asmgen.out(" lda #<$name | sta P8ESTACK_LO,x | lda #>$name | sta P8ESTACK_HI,x | dex")
|
||||
}
|
||||
|
||||
private fun translateExpression(expr: NumericLiteralValue) {
|
||||
private fun translateExpression(expr: NumericLiteral) {
|
||||
when(expr.type) {
|
||||
DataType.UBYTE, DataType.BYTE -> asmgen.out(" lda #${expr.number.toHex()} | sta P8ESTACK_LO,x | dex")
|
||||
DataType.UWORD, DataType.WORD -> asmgen.out("""
|
||||
@ -222,7 +225,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
dex
|
||||
""")
|
||||
DataType.FLOAT -> {
|
||||
val floatConst = asmgen.getFloatAsmConst(expr.number)
|
||||
val floatConst = allocator.getFloatAsmConst(expr.number)
|
||||
asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.push_float")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
@ -539,6 +542,13 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
}
|
||||
}
|
||||
}
|
||||
in ComparisonOperators -> {
|
||||
if(leftDt in NumericDatatypes && rightDt in NumericDatatypes) {
|
||||
val rightVal = expr.right.constValue(program)?.number?.toInt()
|
||||
if(rightVal==0)
|
||||
return translateComparisonWithZero(expr.left, leftDt, expr.operator)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if((leftDt in ByteDatatypes && rightDt !in ByteDatatypes)
|
||||
@ -561,6 +571,69 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateComparisonWithZero(expr: Expression, dt: DataType, operator: String) {
|
||||
translateExpressionInternal(expr)
|
||||
when(operator) {
|
||||
"==" -> {
|
||||
when(dt) {
|
||||
DataType.UBYTE, DataType.BYTE -> asmgen.out(" jsr prog8_lib.equalzero_b")
|
||||
DataType.UWORD, DataType.WORD -> asmgen.out(" jsr prog8_lib.equalzero_w")
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.equal_zero")
|
||||
else -> throw AssemblyError("wrong dt")
|
||||
}
|
||||
}
|
||||
"!=" -> {
|
||||
when(dt) {
|
||||
DataType.UBYTE, DataType.BYTE -> asmgen.out(" jsr prog8_lib.notequalzero_b")
|
||||
DataType.UWORD, DataType.WORD -> asmgen.out(" jsr prog8_lib.notequalzero_w")
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.notequal_zero")
|
||||
else -> throw AssemblyError("wrong dt")
|
||||
}
|
||||
}
|
||||
"<" -> {
|
||||
if(dt==DataType.UBYTE || dt==DataType.UWORD)
|
||||
return translateExpressionInternal(NumericLiteral.fromBoolean(false, expr.position))
|
||||
when(dt) {
|
||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.lesszero_b")
|
||||
DataType.WORD -> asmgen.out(" jsr prog8_lib.lesszero_w")
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.less_zero")
|
||||
else -> throw AssemblyError("wrong dt")
|
||||
}
|
||||
}
|
||||
">" -> {
|
||||
when(dt) {
|
||||
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.greaterzero_ub")
|
||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.greaterzero_sb")
|
||||
DataType.UWORD -> asmgen.out(" jsr prog8_lib.greaterzero_uw")
|
||||
DataType.WORD -> asmgen.out(" jsr prog8_lib.greaterzero_sw")
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.greater_zero")
|
||||
else -> throw AssemblyError("wrong dt")
|
||||
}
|
||||
}
|
||||
"<=" -> {
|
||||
when(dt) {
|
||||
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.equalzero_b")
|
||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.lessequalzeros_b")
|
||||
DataType.UWORD -> asmgen.out(" jsr prog8_lib.equalzero_w")
|
||||
DataType.WORD -> asmgen.out(" jsr prog8_lib.lessequalzero_sw")
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.lessequal_zero")
|
||||
else -> throw AssemblyError("wrong dt")
|
||||
}
|
||||
}
|
||||
">=" -> {
|
||||
if(dt==DataType.UBYTE || dt==DataType.UWORD)
|
||||
return translateExpressionInternal(NumericLiteral.fromBoolean(true, expr.position))
|
||||
when(dt) {
|
||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.greaterequalzero_sb")
|
||||
DataType.WORD -> asmgen.out(" jsr prog8_lib.greaterequalzero_sw")
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.greaterequal_zero")
|
||||
else -> throw AssemblyError("wrong dt")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invalid comparison operator")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateSquared(variable: IdentifierReference, dt: DataType) {
|
||||
val asmVar = asmgen.asmVariableName(variable)
|
||||
when(dt) {
|
||||
@ -631,7 +704,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | lda $arrayVarName+$indexValue+1 | sta P8ESTACK_HI,x | dex")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue | jsr floats.push_float")
|
||||
asmgen.out(" lda #<($arrayVarName+$indexValue) | ldy #>($arrayVarName+$indexValue) | jsr floats.push_float")
|
||||
}
|
||||
else -> throw AssemblyError("weird element type")
|
||||
}
|
||||
@ -727,7 +800,8 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
"<=" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.lesseq_uw" else " jsr prog8_lib.lesseq_w")
|
||||
">=" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.greatereq_uw" else " jsr prog8_lib.greatereq_w")
|
||||
"==" -> asmgen.out(" jsr prog8_lib.equal_w")
|
||||
"!=" -> asmgen.out(" jsr prog8_lib.notequal_w") "&" -> asmgen.out(" jsr prog8_lib.bitand_w")
|
||||
"!=" -> asmgen.out(" jsr prog8_lib.notequal_w")
|
||||
"&" -> asmgen.out(" jsr prog8_lib.bitand_w")
|
||||
"^" -> asmgen.out(" jsr prog8_lib.bitxor_w")
|
||||
"|" -> asmgen.out(" jsr prog8_lib.bitor_w")
|
||||
"and" -> asmgen.out(" jsr prog8_lib.and_w")
|
@ -1,27 +1,29 @@
|
||||
package prog8.codegen.target.cpu6502.codegen
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import com.github.michaelbull.result.fold
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.ArrayToElementTypes
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.RegisterOrPair
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.RangeExpr
|
||||
import prog8.ast.expressions.RangeExpression
|
||||
import prog8.ast.statements.ForLoop
|
||||
import prog8.ast.toHex
|
||||
import prog8.codegen.target.AssemblyError
|
||||
import prog8.compilerinterface.AssemblyError
|
||||
import prog8.compilerinterface.Zeropage
|
||||
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, private val zeropage: Zeropage) {
|
||||
|
||||
internal fun translate(stmt: ForLoop) {
|
||||
val iterableDt = stmt.iterable.inferType(program)
|
||||
if(!iterableDt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
when(stmt.iterable) {
|
||||
is RangeExpr -> {
|
||||
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange()
|
||||
is RangeExpression -> {
|
||||
val range = (stmt.iterable as RangeExpression).toConstantIntegerRange()
|
||||
if(range==null) {
|
||||
translateForOverNonconstRange(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, stmt.iterable as RangeExpr)
|
||||
translateForOverNonconstRange(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, stmt.iterable as RangeExpression)
|
||||
} else {
|
||||
translateForOverConstRange(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, range)
|
||||
}
|
||||
@ -33,7 +35,7 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateForOverNonconstRange(stmt: ForLoop, iterableDt: DataType, range: RangeExpr) {
|
||||
private fun translateForOverNonconstRange(stmt: ForLoop, iterableDt: DataType, range: RangeExpression) {
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
val modifiedLabel = asmgen.makeLabel("for_modified")
|
||||
@ -288,13 +290,15 @@ $loopLabel sty $indexVar
|
||||
bne $loopLabel
|
||||
beq $endLabel""")
|
||||
}
|
||||
if(length>=16 && asmgen.zeropage.hasByteAvailable()) {
|
||||
// allocate index var on ZP
|
||||
val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors)
|
||||
asmgen.out("""$indexVar = $zpAddr ; auto zp UBYTE""")
|
||||
if(length>=16) {
|
||||
// allocate index var on ZP if possible
|
||||
val result = zeropage.allocate(listOf(indexVar), DataType.UBYTE, stmt.definingScope, null, null, stmt.position, asmgen.errors)
|
||||
result.fold(
|
||||
success = { (address,_)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
|
||||
failure = { asmgen.out("$indexVar .byte 0") }
|
||||
)
|
||||
} else {
|
||||
asmgen.out("""
|
||||
$indexVar .byte 0""")
|
||||
asmgen.out("$indexVar .byte 0")
|
||||
}
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
@ -327,13 +331,15 @@ $loopLabel sty $indexVar
|
||||
bne $loopLabel
|
||||
beq $endLabel""")
|
||||
}
|
||||
if(length>=16 && asmgen.zeropage.hasByteAvailable()) {
|
||||
// allocate index var on ZP
|
||||
val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors)
|
||||
asmgen.out("""$indexVar = $zpAddr ; auto zp UBYTE""")
|
||||
if(length>=16) {
|
||||
// allocate index var on ZP if possible
|
||||
val result = zeropage.allocate(listOf(indexVar), DataType.UBYTE, stmt.definingScope, null, null, stmt.position, asmgen.errors)
|
||||
result.fold(
|
||||
success = { (address,_)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
|
||||
failure = { asmgen.out("$indexVar .byte 0") }
|
||||
)
|
||||
} else {
|
||||
asmgen.out("""
|
||||
$indexVar .byte 0""")
|
||||
asmgen.out("$indexVar .byte 0")
|
||||
}
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
@ -587,7 +593,7 @@ $loopLabel""")
|
||||
asmgen.loopEndLabels.pop()
|
||||
}
|
||||
|
||||
private fun assignLoopvar(stmt: ForLoop, range: RangeExpr) =
|
||||
private fun assignLoopvar(stmt: ForLoop, range: RangeExpression) =
|
||||
asmgen.assignExpressionToVariable(
|
||||
range.from,
|
||||
asmgen.asmVariableName(stmt.loopVar),
|
@ -1,16 +1,19 @@
|
||||
package prog8.codegen.target.cpu6502.codegen
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.expressions.AddressOf
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.statements.*
|
||||
import prog8.codegen.target.AssemblyError
|
||||
import prog8.codegen.target.cpu6502.codegen.assignment.AsmAssignSource
|
||||
import prog8.codegen.target.cpu6502.codegen.assignment.AsmAssignTarget
|
||||
import prog8.codegen.target.cpu6502.codegen.assignment.AsmAssignment
|
||||
import prog8.codegen.target.cpu6502.codegen.assignment.TargetStorageKind
|
||||
import prog8.codegen.cpu6502.assignment.AsmAssignSource
|
||||
import prog8.codegen.cpu6502.assignment.AsmAssignTarget
|
||||
import prog8.codegen.cpu6502.assignment.AsmAssignment
|
||||
import prog8.codegen.cpu6502.assignment.TargetStorageKind
|
||||
import prog8.compilerinterface.AssemblyError
|
||||
import prog8.compilerinterface.CpuType
|
||||
|
||||
|
||||
@ -93,8 +96,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
// NOTE: *if* there is a return statement, it will be the only one, and the very last statement of the subroutine
|
||||
// (this condition has been enforced by an ast check earlier)
|
||||
asmgen.out(" \t; inlined routine follows: ${sub.name}")
|
||||
val assembly = sub.statements.single() as InlineAssembly
|
||||
asmgen.translate(assembly)
|
||||
sub.statements.forEach { asmgen.translate(it as InlineAssembly) }
|
||||
asmgen.out(" \t; inlined routine end: ${sub.name}")
|
||||
} else {
|
||||
asmgen.out(" jsr $subAsmName")
|
||||
@ -126,9 +128,67 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller
|
||||
}
|
||||
|
||||
internal fun translateUnaryFunctionCallWithArgSource(target: IdentifierReference, arg: AsmAssignSource, isStatement: Boolean, scope: Subroutine): DataType {
|
||||
when(val targetStmt = target.targetStatement(program)!!) {
|
||||
is BuiltinFunctionPlaceholder -> {
|
||||
return if(isStatement) {
|
||||
asmgen.translateBuiltinFunctionCallStatement(targetStmt.name, listOf(arg), scope)
|
||||
DataType.UNDEFINED
|
||||
} else {
|
||||
asmgen.translateBuiltinFunctionCallExpression(targetStmt.name, listOf(arg), scope)
|
||||
}
|
||||
}
|
||||
is Subroutine -> {
|
||||
val argDt = targetStmt.parameters.single().type
|
||||
if(targetStmt.isAsmSubroutine) {
|
||||
// argument via registers
|
||||
val argRegister = targetStmt.asmParameterRegisters.single().registerOrPair!!
|
||||
val assignArgument = AsmAssignment(
|
||||
arg,
|
||||
AsmAssignTarget.fromRegisters(argRegister, argDt in SignedDatatypes, scope, program, asmgen),
|
||||
false, program.memsizer, target.position
|
||||
)
|
||||
asmgen.translateNormalAssignment(assignArgument)
|
||||
} else {
|
||||
val assignArgument: AsmAssignment =
|
||||
if(optimizeIntArgsViaRegisters(targetStmt)) {
|
||||
// argument goes via registers as optimization
|
||||
val paramReg: RegisterOrPair = when(argDt) {
|
||||
in ByteDatatypes -> RegisterOrPair.A
|
||||
in WordDatatypes -> RegisterOrPair.AY
|
||||
DataType.FLOAT -> RegisterOrPair.FAC1
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
AsmAssignment(
|
||||
arg,
|
||||
AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, argDt, scope, register = paramReg),
|
||||
false, program.memsizer, target.position
|
||||
)
|
||||
} else {
|
||||
// arg goes via parameter variable
|
||||
val argVarName = asmgen.asmVariableName(targetStmt.scopedName + targetStmt.parameters.single().name)
|
||||
AsmAssignment(
|
||||
arg,
|
||||
AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, argDt, scope, argVarName),
|
||||
false, program.memsizer, target.position
|
||||
)
|
||||
}
|
||||
asmgen.translateNormalAssignment(assignArgument)
|
||||
}
|
||||
if(targetStmt.shouldSaveX())
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, scope)
|
||||
asmgen.out(" jsr ${asmgen.asmSymbolName(target)}")
|
||||
if(targetStmt.shouldSaveX())
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
return if(isStatement) DataType.UNDEFINED else targetStmt.returntypes.single()
|
||||
}
|
||||
else -> throw AssemblyError("invalid call target")
|
||||
}
|
||||
}
|
||||
|
||||
private fun argumentsViaVariables(sub: Subroutine, call: IFunctionCall) {
|
||||
for(arg in sub.parameters.withIndex().zip(call.args))
|
||||
argumentViaVariable(sub, arg.first, arg.second)
|
||||
argumentViaVariable(sub, arg.first.value, arg.second)
|
||||
}
|
||||
|
||||
private fun argumentsViaRegisters(sub: Subroutine, call: IFunctionCall) {
|
||||
@ -272,15 +332,15 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
asmgen.out(" plp") // set the carry flag back to correct value
|
||||
}
|
||||
|
||||
private fun argumentViaVariable(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
|
||||
private fun argumentViaVariable(sub: Subroutine, parameter: SubroutineParameter, value: Expression) {
|
||||
// pass parameter via a regular variable (not via registers)
|
||||
val valueIDt = value.inferType(program)
|
||||
val valueDt = valueIDt.getOrElse { throw AssemblyError("unknown dt") }
|
||||
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
|
||||
if(!isArgumentTypeCompatible(valueDt, parameter.type))
|
||||
throw AssemblyError("argument type incompatible")
|
||||
|
||||
val varName = asmgen.asmVariableName(sub.scopedName + parameter.value.name)
|
||||
asmgen.assignExpressionToVariable(value, varName, parameter.value.type, sub)
|
||||
val varName = asmgen.asmVariableName(sub.scopedName + parameter.name)
|
||||
asmgen.assignExpressionToVariable(value, varName, parameter.type, sub)
|
||||
}
|
||||
|
||||
private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression, registerOverride: RegisterOrPair? = null) {
|
||||
@ -305,7 +365,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
// this param needs to be set last, right before the jsr
|
||||
// for now, this is already enforced on the subroutine definition by the Ast Checker
|
||||
when(value) {
|
||||
is NumericLiteralValue -> {
|
||||
is NumericLiteral -> {
|
||||
val carrySet = value.number.toInt() != 0
|
||||
asmgen.out(if(carrySet) " sec" else " clc")
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
package prog8.codegen.target.cpu6502.codegen
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.statements.PostIncrDecr
|
||||
import prog8.ast.toHex
|
||||
import prog8.codegen.target.AssemblyError
|
||||
import prog8.compilerinterface.AssemblyError
|
||||
|
||||
|
||||
internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||
@ -41,7 +41,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
targetMemory!=null -> {
|
||||
when (val addressExpr = targetMemory.addressExpression) {
|
||||
is NumericLiteralValue -> {
|
||||
is NumericLiteral -> {
|
||||
val what = addressExpr.number.toHex()
|
||||
asmgen.out(if(incr) " inc $what" else " dec $what")
|
||||
}
|
||||
@ -83,7 +83,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out(" lda #<$asmArrayvarname+$indexValue | ldy #>$asmArrayvarname+$indexValue")
|
||||
asmgen.out(" lda #<($asmArrayvarname+$indexValue) | ldy #>($asmArrayvarname+$indexValue)")
|
||||
asmgen.out(if(incr) " jsr floats.inc_var_f" else " jsr floats.dec_var_f")
|
||||
}
|
||||
else -> throw AssemblyError("need numeric type")
|
590
codeGenCpu6502/src/prog8/codegen/cpu6502/ProgramAndVarsGen.kt
Normal file
590
codeGenCpu6502/src/prog8/codegen/cpu6502/ProgramAndVarsGen.kt
Normal file
@ -0,0 +1,590 @@
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.antlr.escape
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.toHex
|
||||
import prog8.codegen.cpu6502.assignment.AsmAssignTarget
|
||||
import prog8.codegen.cpu6502.assignment.TargetStorageKind
|
||||
import prog8.compilerinterface.*
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
/**
|
||||
* Generates the main parts of the program:
|
||||
* - entry/exit code
|
||||
* - initialization routines
|
||||
* - blocks
|
||||
* - subroutines
|
||||
* - all variables (note: VarDecl ast nodes are *NOT* used anymore for this! now uses IVariablesAndConsts data tables!)
|
||||
*/
|
||||
internal class ProgramAndVarsGen(
|
||||
val program: Program,
|
||||
val variables: IVariablesAndConsts,
|
||||
val options: CompilationOptions,
|
||||
val errors: IErrorReporter,
|
||||
private val functioncallAsmGen: FunctionCallAsmGen,
|
||||
private val asmgen: AsmGen,
|
||||
private val allocator: VariableAllocator,
|
||||
private val zeropage: Zeropage
|
||||
) {
|
||||
private val compTarget = options.compTarget
|
||||
private val callGraph = CallGraph(program, true)
|
||||
private val blockVariableInitializers = program.allBlocks.associateWith { it.statements.filterIsInstance<Assignment>() }
|
||||
|
||||
internal fun generate() {
|
||||
val allInitializers = blockVariableInitializers.asSequence().flatMap { it.value }
|
||||
require(allInitializers.all { it.origin==AssignmentOrigin.VARINIT }) {"all block-level assignments must be a variable initializer"}
|
||||
|
||||
allocator.allocateZeropageVariables()
|
||||
|
||||
header()
|
||||
val allBlocks = program.allBlocks
|
||||
if(allBlocks.first().name != "main")
|
||||
throw AssemblyError("first block should be 'main'")
|
||||
|
||||
if(errors.noErrors()) {
|
||||
program.allBlocks.forEach { block2asm(it) }
|
||||
memorySlabs()
|
||||
footer()
|
||||
}
|
||||
}
|
||||
|
||||
private fun header() {
|
||||
val ourName = this.javaClass.name
|
||||
val cpu = when(compTarget.machine.cpu) {
|
||||
CpuType.CPU6502 -> "6502"
|
||||
CpuType.CPU65c02 -> "w65c02"
|
||||
else -> "unsupported"
|
||||
}
|
||||
|
||||
asmgen.out("; $cpu assembly code for '${program.name}'")
|
||||
asmgen.out("; generated by $ourName on ${LocalDateTime.now().withNano(0)}")
|
||||
asmgen.out("; assembler syntax is for the 64tasm cross-assembler")
|
||||
asmgen.out("; output options: output=${options.output} launcher=${options.launcher} zp=${options.zeropage}")
|
||||
asmgen.out("")
|
||||
asmgen.out(".cpu '$cpu'\n.enc 'none'\n")
|
||||
|
||||
program.actualLoadAddress = program.definedLoadAddress
|
||||
if (program.actualLoadAddress == 0u) // fix load address
|
||||
program.actualLoadAddress = if (options.launcher == LauncherType.BASIC)
|
||||
compTarget.machine.BASIC_LOAD_ADDRESS else compTarget.machine.RAW_LOAD_ADDRESS
|
||||
|
||||
// the global prog8 variables needed
|
||||
val zp = zeropage
|
||||
asmgen.out("P8ZP_SCRATCH_B1 = ${zp.SCRATCH_B1}")
|
||||
asmgen.out("P8ZP_SCRATCH_REG = ${zp.SCRATCH_REG}")
|
||||
asmgen.out("P8ZP_SCRATCH_W1 = ${zp.SCRATCH_W1} ; word")
|
||||
asmgen.out("P8ZP_SCRATCH_W2 = ${zp.SCRATCH_W2} ; word")
|
||||
asmgen.out("P8ESTACK_LO = ${compTarget.machine.ESTACK_LO.toHex()}")
|
||||
asmgen.out("P8ESTACK_HI = ${compTarget.machine.ESTACK_HI.toHex()}")
|
||||
|
||||
when {
|
||||
options.launcher == LauncherType.BASIC -> {
|
||||
if (program.actualLoadAddress != options.compTarget.machine.BASIC_LOAD_ADDRESS)
|
||||
throw AssemblyError("BASIC output must have correct load address")
|
||||
asmgen.out("; ---- basic program with sys call ----")
|
||||
asmgen.out("* = ${program.actualLoadAddress.toHex()}")
|
||||
val year = LocalDate.now().year
|
||||
asmgen.out(" .word (+), $year")
|
||||
asmgen.out(" .null $9e, format(' %d ', prog8_entrypoint), $3a, $8f, ' prog8'")
|
||||
asmgen.out("+\t.word 0")
|
||||
asmgen.out("prog8_entrypoint\t; assembly code starts here\n")
|
||||
if(!options.noSysInit)
|
||||
asmgen.out(" jsr ${compTarget.name}.init_system")
|
||||
asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
|
||||
}
|
||||
options.output == OutputType.PRG -> {
|
||||
asmgen.out("; ---- program without basic sys call ----")
|
||||
asmgen.out("* = ${program.actualLoadAddress.toHex()}\n")
|
||||
if(!options.noSysInit)
|
||||
asmgen.out(" jsr ${compTarget.name}.init_system")
|
||||
asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
|
||||
}
|
||||
options.output == OutputType.RAW -> {
|
||||
asmgen.out("; ---- raw assembler program ----")
|
||||
asmgen.out("* = ${program.actualLoadAddress.toHex()}\n")
|
||||
}
|
||||
}
|
||||
|
||||
if(options.zeropage !in arrayOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE)) {
|
||||
asmgen.out("""
|
||||
; zeropage is clobbered so we need to reset the machine at exit
|
||||
lda #>sys.reset_system
|
||||
pha
|
||||
lda #<sys.reset_system
|
||||
pha""")
|
||||
}
|
||||
|
||||
// make sure that on the cx16 and c64, basic rom is banked in again when we exit the program
|
||||
when(compTarget.name) {
|
||||
"cx16" -> {
|
||||
if(options.floats)
|
||||
asmgen.out(" lda #4 | sta $01") // to use floats, make sure Basic rom is banked in
|
||||
asmgen.out(" jsr main.start | lda #4 | sta $01 | rts")
|
||||
}
|
||||
"c64" -> asmgen.out(" jsr main.start | lda #31 | sta $01 | rts")
|
||||
else -> asmgen.jmp("main.start")
|
||||
}
|
||||
}
|
||||
|
||||
private fun memorySlabs() {
|
||||
asmgen.out("; memory slabs")
|
||||
asmgen.out("prog8_slabs\t.block")
|
||||
for((name, info) in allocator.memorySlabs) {
|
||||
if(info.second>1u)
|
||||
asmgen.out("\t.align ${info.second.toHex()}")
|
||||
asmgen.out("$name\t.fill ${info.first}")
|
||||
}
|
||||
asmgen.out("\t.bend")
|
||||
}
|
||||
|
||||
private fun footer() {
|
||||
// the global list of all floating point constants for the whole program
|
||||
asmgen.out("; global float constants")
|
||||
for (flt in allocator.globalFloatConsts) {
|
||||
val floatFill = compTarget.machine.getFloat(flt.key).makeFloatFillAsm()
|
||||
val floatvalue = flt.key
|
||||
asmgen.out("${flt.value}\t.byte $floatFill ; float $floatvalue")
|
||||
}
|
||||
|
||||
// program end
|
||||
asmgen.out("prog8_program_end\t; end of program label for progend()")
|
||||
}
|
||||
|
||||
private fun block2asm(block: Block) {
|
||||
asmgen.out("")
|
||||
asmgen.out("; ---- block: '${block.name}' ----")
|
||||
if(block.address!=null)
|
||||
asmgen.out("* = ${block.address!!.toHex()}")
|
||||
else {
|
||||
if("align_word" in block.options())
|
||||
asmgen.out("\t.align 2")
|
||||
else if("align_page" in block.options())
|
||||
asmgen.out("\t.align $100")
|
||||
}
|
||||
|
||||
asmgen.out("${block.name}\t" + (if("force_output" in block.options()) ".block\n" else ".proc\n"))
|
||||
|
||||
asmgen.outputSourceLine(block)
|
||||
|
||||
zeropagevars2asm(block)
|
||||
memdefsAndConsts2asm(block)
|
||||
asmsubs2asm(block.statements)
|
||||
nonZpVariables2asm(block)
|
||||
|
||||
asmgen.out("")
|
||||
asmgen.out("; subroutines in this block")
|
||||
|
||||
// First translate regular statements, and then put the subroutines at the end.
|
||||
// (regular statements = everything except the initialization assignments;
|
||||
// these will be part of the prog8_init_vars init routine generated below)
|
||||
val initializers = blockVariableInitializers.getValue(block)
|
||||
val statements = block.statements.filterNot { it in initializers }
|
||||
val (subroutine, stmts) = statements.partition { it is Subroutine }
|
||||
stmts.forEach { asmgen.translate(it) }
|
||||
subroutine.forEach { asmgen.translate(it) }
|
||||
|
||||
if(!options.dontReinitGlobals) {
|
||||
// generate subroutine to initialize block-level (global) variables
|
||||
if (initializers.isNotEmpty()) {
|
||||
asmgen.out("prog8_init_vars\t.proc\n")
|
||||
initializers.forEach { assign -> asmgen.translate(assign) }
|
||||
asmgen.out(" rts\n .pend")
|
||||
}
|
||||
}
|
||||
|
||||
asmgen.out(if("force_output" in block.options()) "\n\t.bend\n" else "\n\t.pend\n")
|
||||
}
|
||||
|
||||
internal fun translateSubroutine(sub: Subroutine) {
|
||||
var onlyVariables = false
|
||||
|
||||
if(sub.inline) {
|
||||
if(options.optimize) {
|
||||
if(sub.isAsmSubroutine || callGraph.unused(sub))
|
||||
return
|
||||
|
||||
// from an inlined subroutine only the local variables are generated,
|
||||
// all other code statements are omitted in the subroutine itself
|
||||
// (they've been inlined at the call site, remember?)
|
||||
onlyVariables = true
|
||||
}
|
||||
}
|
||||
|
||||
asmgen.out("")
|
||||
|
||||
if(sub.isAsmSubroutine) {
|
||||
if(sub.asmAddress!=null)
|
||||
return // already done at the memvars section
|
||||
|
||||
// asmsub with most likely just an inline asm in it
|
||||
asmgen.out("${sub.name}\t.proc")
|
||||
sub.statements.forEach { asmgen.translate(it) }
|
||||
asmgen.out(" .pend\n")
|
||||
} else {
|
||||
// regular subroutine
|
||||
asmgen.out("${sub.name}\t.proc")
|
||||
zeropagevars2asm(sub)
|
||||
memdefsAndConsts2asm(sub)
|
||||
asmsubs2asm(sub.statements)
|
||||
|
||||
// the main.start subroutine is the program's entrypoint and should perform some initialization logic
|
||||
if(sub.name=="start" && sub.definingBlock.name=="main")
|
||||
entrypointInitialization()
|
||||
|
||||
if(functioncallAsmGen.optimizeIntArgsViaRegisters(sub)) {
|
||||
asmgen.out("; simple int arg(s) passed via register(s)")
|
||||
if(sub.parameters.size==1) {
|
||||
val dt = sub.parameters[0].type
|
||||
val target = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, sub, variableAsmName = sub.parameters[0].name)
|
||||
if(dt in ByteDatatypes)
|
||||
asmgen.assignRegister(RegisterOrPair.A, target)
|
||||
else
|
||||
asmgen.assignRegister(RegisterOrPair.AY, target)
|
||||
} else {
|
||||
require(sub.parameters.size==2)
|
||||
// 2 simple byte args, first in A, second in Y
|
||||
val target1 = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, sub.parameters[0].type, sub, variableAsmName = sub.parameters[0].name)
|
||||
val target2 = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, sub.parameters[1].type, sub, variableAsmName = sub.parameters[1].name)
|
||||
asmgen.assignRegister(RegisterOrPair.A, target1)
|
||||
asmgen.assignRegister(RegisterOrPair.Y, target2)
|
||||
}
|
||||
}
|
||||
|
||||
if(!onlyVariables) {
|
||||
asmgen.out("; statements")
|
||||
sub.statements.forEach { asmgen.translate(it) }
|
||||
}
|
||||
|
||||
asmgen.out("; variables")
|
||||
val asmGenInfo = allocator.subroutineExtra(sub)
|
||||
for((dt, name, addr) in asmGenInfo.extraVars) {
|
||||
if(addr!=null)
|
||||
asmgen.out("$name = $addr")
|
||||
else when(dt) {
|
||||
DataType.UBYTE -> asmgen.out("$name .byte 0")
|
||||
DataType.UWORD -> asmgen.out("$name .word 0")
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
}
|
||||
if(asmGenInfo.usedRegsaveA) // will probably never occur
|
||||
asmgen.out("prog8_regsaveA .byte 0")
|
||||
if(asmGenInfo.usedRegsaveX)
|
||||
asmgen.out("prog8_regsaveX .byte 0")
|
||||
if(asmGenInfo.usedRegsaveY)
|
||||
asmgen.out("prog8_regsaveY .byte 0")
|
||||
if(asmGenInfo.usedFloatEvalResultVar1)
|
||||
asmgen.out("$subroutineFloatEvalResultVar1 .byte 0,0,0,0,0")
|
||||
if(asmGenInfo.usedFloatEvalResultVar2)
|
||||
asmgen.out("$subroutineFloatEvalResultVar2 .byte 0,0,0,0,0")
|
||||
nonZpVariables2asm(sub)
|
||||
asmgen.out(" .pend\n")
|
||||
}
|
||||
}
|
||||
|
||||
private fun entrypointInitialization() {
|
||||
asmgen.out("; program startup initialization")
|
||||
asmgen.out(" cld")
|
||||
if(!options.dontReinitGlobals) {
|
||||
blockVariableInitializers.forEach {
|
||||
if (it.value.isNotEmpty())
|
||||
asmgen.out(" jsr ${it.key.name}.prog8_init_vars")
|
||||
}
|
||||
}
|
||||
|
||||
// string and array variables in zeropage that have initializer value, should be initialized
|
||||
val stringVarsWithInitInZp = allocator.zeropageVars.filter { it.value.dt==DataType.STR && it.value.initialStringValue!=null }
|
||||
val arrayVarsWithInitInZp = allocator.zeropageVars.filter { it.value.dt in ArrayDatatypes && it.value.initialArrayValue!=null }
|
||||
if(stringVarsWithInitInZp.isNotEmpty() || arrayVarsWithInitInZp.isNotEmpty()) {
|
||||
asmgen.out("; zp str and array initializations")
|
||||
stringVarsWithInitInZp.forEach {
|
||||
val name = asmgen.asmVariableName(it.key)
|
||||
asmgen.out("""
|
||||
lda #<${name}
|
||||
ldy #>${name}
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<${name}_init_value
|
||||
ldy #>${name}_init_value
|
||||
jsr prog8_lib.strcpy""")
|
||||
}
|
||||
arrayVarsWithInitInZp.forEach {
|
||||
val size = it.value.size
|
||||
val name = asmgen.asmVariableName(it.key)
|
||||
asmgen.out("""
|
||||
lda #<${name}_init_value
|
||||
ldy #>${name}_init_value
|
||||
sta cx16.r0L
|
||||
sty cx16.r0H
|
||||
lda #<${name}
|
||||
ldy #>${name}
|
||||
sta cx16.r1L
|
||||
sty cx16.r1H
|
||||
lda #<$size
|
||||
ldy #>$size
|
||||
jsr sys.memcopy""")
|
||||
}
|
||||
asmgen.out(" jmp +")
|
||||
}
|
||||
|
||||
stringVarsWithInitInZp.forEach {
|
||||
val varname = asmgen.asmVariableName(it.key)+"_init_value"
|
||||
val stringvalue = it.value.initialStringValue!!
|
||||
outputStringvar(varname, it.value.dt, stringvalue.encoding, stringvalue.value)
|
||||
}
|
||||
|
||||
arrayVarsWithInitInZp.forEach {
|
||||
val varname = asmgen.asmVariableName(it.key)+"_init_value"
|
||||
arrayVariable2asm(varname, it.value.dt, it.value.initialArrayValue!!, null)
|
||||
}
|
||||
|
||||
asmgen.out("""+ tsx
|
||||
stx prog8_lib.orig_stackpointer ; required for sys.exit()
|
||||
ldx #255 ; init estack ptr
|
||||
clv
|
||||
clc""")
|
||||
}
|
||||
|
||||
private fun zeropagevars2asm(scope: INameScope) {
|
||||
val zpVariables = allocator.zeropageVars.filter { it.value.originalScope==scope }
|
||||
for ((scopedName, zpvar) in zpVariables) {
|
||||
if (scopedName.size == 2 && scopedName[0] == "cx16" && scopedName[1][0] == 'r' && scopedName[1][1].isDigit())
|
||||
continue // The 16 virtual registers of the cx16 are not actual variables in zp, they're memory mapped
|
||||
asmgen.out("${scopedName.last()} \t= ${zpvar.address} \t; zp ${zpvar.dt}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun nonZpVariables2asm(block: Block) {
|
||||
val variables = variables.blockVars[block]?.filter { !allocator.isZpVar(it.scopedname) } ?: emptyList()
|
||||
nonZpVariables2asm(variables)
|
||||
}
|
||||
|
||||
private fun nonZpVariables2asm(sub: Subroutine) {
|
||||
val variables = variables.subroutineVars[sub]?.filter { !allocator.isZpVar(it.scopedname) } ?: emptyList()
|
||||
nonZpVariables2asm(variables)
|
||||
}
|
||||
|
||||
private fun nonZpVariables2asm(variables: List<IVariablesAndConsts.StaticVariable>) {
|
||||
asmgen.out("")
|
||||
asmgen.out("; non-zeropage variables")
|
||||
val (stringvars, othervars) = variables.partition { it.type==DataType.STR }
|
||||
stringvars.forEach {
|
||||
val stringvalue = it.initialValue as StringLiteral
|
||||
outputStringvar(it.scopedname.last(), it.type, stringvalue.encoding, stringvalue.value)
|
||||
}
|
||||
othervars.sortedBy { it.type }.forEach {
|
||||
staticVariable2asm(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun staticVariable2asm(variable: IVariablesAndConsts.StaticVariable) {
|
||||
val name = variable.scopedname.last()
|
||||
val value = variable.initialValue
|
||||
val staticValue: Number =
|
||||
if(value!=null) {
|
||||
if(value is NumericLiteral) {
|
||||
if(value.type== DataType.FLOAT)
|
||||
value.number
|
||||
else
|
||||
value.number.toInt()
|
||||
} else {
|
||||
if(variable.type in NumericDatatypes)
|
||||
throw AssemblyError("can only deal with constant numeric values for global vars")
|
||||
else 0
|
||||
}
|
||||
} else 0
|
||||
|
||||
when (variable.type) {
|
||||
DataType.UBYTE -> asmgen.out("$name\t.byte ${staticValue.toHex()}")
|
||||
DataType.BYTE -> asmgen.out("$name\t.char $staticValue")
|
||||
DataType.UWORD -> asmgen.out("$name\t.word ${staticValue.toHex()}")
|
||||
DataType.WORD -> asmgen.out("$name\t.sint $staticValue")
|
||||
DataType.FLOAT -> {
|
||||
if(staticValue==0) {
|
||||
asmgen.out("$name\t.byte 0,0,0,0,0 ; float")
|
||||
} else {
|
||||
val floatFill = compTarget.machine.getFloat(staticValue).makeFloatFillAsm()
|
||||
asmgen.out("$name\t.byte $floatFill ; float $staticValue")
|
||||
}
|
||||
}
|
||||
DataType.STR -> {
|
||||
throw AssemblyError("all string vars should have been interned into prog")
|
||||
}
|
||||
in ArrayDatatypes -> arrayVariable2asm(name, variable.type, value as? ArrayLiteral, variable.arraysize)
|
||||
else -> {
|
||||
throw AssemblyError("weird dt")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun arrayVariable2asm(varname: String, dt: DataType, value: ArrayLiteral?, orNumberOfZeros: Int?) {
|
||||
when(dt) {
|
||||
DataType.ARRAY_UB -> {
|
||||
val data = makeArrayFillDataUnsigned(dt, value, orNumberOfZeros)
|
||||
if (data.size <= 16)
|
||||
asmgen.out("$varname\t.byte ${data.joinToString()}")
|
||||
else {
|
||||
asmgen.out(varname)
|
||||
for (chunk in data.chunked(16))
|
||||
asmgen.out(" .byte " + chunk.joinToString())
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_B -> {
|
||||
val data = makeArrayFillDataSigned(dt, value, orNumberOfZeros)
|
||||
if (data.size <= 16)
|
||||
asmgen.out("$varname\t.char ${data.joinToString()}")
|
||||
else {
|
||||
asmgen.out(varname)
|
||||
for (chunk in data.chunked(16))
|
||||
asmgen.out(" .char " + chunk.joinToString())
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_UW -> {
|
||||
val data = makeArrayFillDataUnsigned(dt, value, orNumberOfZeros)
|
||||
if (data.size <= 16)
|
||||
asmgen.out("$varname\t.word ${data.joinToString()}")
|
||||
else {
|
||||
asmgen.out(varname)
|
||||
for (chunk in data.chunked(16))
|
||||
asmgen.out(" .word " + chunk.joinToString())
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_W -> {
|
||||
val data = makeArrayFillDataSigned(dt, value, orNumberOfZeros)
|
||||
if (data.size <= 16)
|
||||
asmgen.out("$varname\t.sint ${data.joinToString()}")
|
||||
else {
|
||||
asmgen.out(varname)
|
||||
for (chunk in data.chunked(16))
|
||||
asmgen.out(" .sint " + chunk.joinToString())
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
val array = value?.value ?:
|
||||
Array(orNumberOfZeros!!) { defaultZero(ArrayToElementTypes.getValue(dt), Position.DUMMY) }
|
||||
val floatFills = array.map {
|
||||
val number = (it as NumericLiteral).number
|
||||
compTarget.machine.getFloat(number).makeFloatFillAsm()
|
||||
}
|
||||
asmgen.out(varname)
|
||||
for (f in array.zip(floatFills))
|
||||
asmgen.out(" .byte ${f.second} ; float ${f.first}")
|
||||
}
|
||||
else -> throw AssemblyError("require array dt")
|
||||
}
|
||||
}
|
||||
|
||||
private fun memdefsAndConsts2asm(block: Block) {
|
||||
val mvs = variables.blockMemvars[block] ?: emptySet()
|
||||
val consts = variables.blockConsts[block] ?: emptySet()
|
||||
memdefsAndConsts2asm(mvs, consts)
|
||||
}
|
||||
|
||||
private fun memdefsAndConsts2asm(sub: Subroutine) {
|
||||
val mvs = variables.subroutineMemvars[sub] ?: emptySet()
|
||||
val consts = variables.subroutineConsts[sub] ?: emptySet()
|
||||
memdefsAndConsts2asm(mvs, consts)
|
||||
}
|
||||
|
||||
private fun memdefsAndConsts2asm(
|
||||
memvars: Set<IVariablesAndConsts.MemoryMappedVariable>,
|
||||
consts: Set<IVariablesAndConsts.ConstantNumberSymbol>
|
||||
) {
|
||||
memvars.forEach {
|
||||
asmgen.out(" ${it.scopedname.last()} = ${it.address.toHex()}")
|
||||
}
|
||||
consts.forEach {
|
||||
if(it.type==DataType.FLOAT)
|
||||
asmgen.out(" ${it.scopedname.last()} = ${it.value}")
|
||||
else
|
||||
asmgen.out(" ${it.scopedname.last()} = ${it.value.toHex()}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun asmsubs2asm(statements: List<Statement>) {
|
||||
statements
|
||||
.filter { it is Subroutine && it.isAsmSubroutine && it.asmAddress!=null }
|
||||
.forEach { asmsub ->
|
||||
asmsub as Subroutine
|
||||
asmgen.out(" ${asmsub.name} = ${asmsub.asmAddress!!.toHex()}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun outputStringvar(varname: String, dt: DataType, encoding: Encoding, value: String) {
|
||||
asmgen.out("$varname\t; $dt $encoding:\"${escape(value).replace("\u0000", "<NULL>")}\"")
|
||||
val bytes = compTarget.encodeString(value, encoding).plus(0.toUByte())
|
||||
val outputBytes = bytes.map { "$" + it.toString(16).padStart(2, '0') }
|
||||
for (chunk in outputBytes.chunked(16))
|
||||
asmgen.out(" .byte " + chunk.joinToString())
|
||||
}
|
||||
|
||||
private fun makeArrayFillDataUnsigned(dt: DataType, value: ArrayLiteral?, orNumberOfZeros: Int?): List<String> {
|
||||
val array = value?.value ?:
|
||||
Array(orNumberOfZeros!!) { defaultZero(ArrayToElementTypes.getValue(dt), Position.DUMMY) }
|
||||
return when (dt) {
|
||||
DataType.ARRAY_UB ->
|
||||
// byte array can never contain pointer-to types, so treat values as all integers
|
||||
array.map {
|
||||
val number = (it as NumericLiteral).number.toInt()
|
||||
"$"+number.toString(16).padStart(2, '0')
|
||||
}
|
||||
DataType.ARRAY_UW -> array.map {
|
||||
when (it) {
|
||||
is NumericLiteral -> {
|
||||
"$" + it.number.toInt().toString(16).padStart(4, '0')
|
||||
}
|
||||
is AddressOf -> {
|
||||
asmgen.asmSymbolName(it.identifier)
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
asmgen.asmSymbolName(it)
|
||||
}
|
||||
else -> throw AssemblyError("weird array elt dt")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeArrayFillDataSigned(dt: DataType, value: ArrayLiteral?, orNumberOfZeros: Int?): List<String> {
|
||||
val array = value?.value ?:
|
||||
Array(orNumberOfZeros!!) { defaultZero(ArrayToElementTypes.getValue(dt), Position.DUMMY) }
|
||||
return when (dt) {
|
||||
DataType.ARRAY_UB ->
|
||||
// byte array can never contain pointer-to types, so treat values as all integers
|
||||
array.map {
|
||||
val number = (it as NumericLiteral).number.toInt()
|
||||
"$"+number.toString(16).padStart(2, '0')
|
||||
}
|
||||
DataType.ARRAY_B ->
|
||||
// byte array can never contain pointer-to types, so treat values as all integers
|
||||
array.map {
|
||||
val number = (it as NumericLiteral).number.toInt()
|
||||
val hexnum = number.absoluteValue.toString(16).padStart(2, '0')
|
||||
if(number>=0)
|
||||
"$$hexnum"
|
||||
else
|
||||
"-$$hexnum"
|
||||
}
|
||||
DataType.ARRAY_UW -> array.map {
|
||||
val number = (it as NumericLiteral).number.toInt()
|
||||
"$" + number.toString(16).padStart(4, '0')
|
||||
}
|
||||
DataType.ARRAY_W -> array.map {
|
||||
val number = (it as NumericLiteral).number.toInt()
|
||||
val hexnum = number.absoluteValue.toString(16).padStart(4, '0')
|
||||
if(number>=0)
|
||||
"$$hexnum"
|
||||
else
|
||||
"-$$hexnum"
|
||||
}
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
166
codeGenCpu6502/src/prog8/codegen/cpu6502/VariableAllocator.kt
Normal file
166
codeGenCpu6502/src/prog8/codegen/cpu6502/VariableAllocator.kt
Normal file
@ -0,0 +1,166 @@
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import com.github.michaelbull.result.fold
|
||||
import com.github.michaelbull.result.onSuccess
|
||||
import prog8.ast.base.ArrayDatatypes
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.IntegerDatatypes
|
||||
import prog8.ast.expressions.StringLiteral
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.statements.ZeropageWish
|
||||
import prog8.compilerinterface.*
|
||||
|
||||
|
||||
internal class VariableAllocator(private val vars: IVariablesAndConsts,
|
||||
private val options: CompilationOptions,
|
||||
private val errors: IErrorReporter) {
|
||||
|
||||
private val zeropage = options.compTarget.machine.zeropage
|
||||
private val subroutineExtras = mutableMapOf<Subroutine, SubroutineExtraAsmInfo>()
|
||||
private val memorySlabsInternal = mutableMapOf<String, Pair<UInt, UInt>>()
|
||||
internal val memorySlabs: Map<String, Pair<UInt, UInt>> = memorySlabsInternal
|
||||
internal val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
|
||||
internal val zeropageVars: Map<List<String>, Zeropage.ZpAllocation> = zeropage.allocatedVariables
|
||||
|
||||
internal fun getMemorySlab(name: String) = memorySlabsInternal[name]
|
||||
internal fun allocateMemorySlab(name: String, size: UInt, align: UInt) {
|
||||
memorySlabsInternal[name] = Pair(size, align)
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate variables into the Zeropage.
|
||||
* The result should be retrieved from the current machine's zeropage object!
|
||||
*/
|
||||
internal fun allocateZeropageVariables() {
|
||||
if(options.zeropage== ZeropageType.DONTUSE)
|
||||
return
|
||||
|
||||
val allVariables = (
|
||||
vars.blockVars.asSequence().flatMap { it.value } +
|
||||
vars.subroutineVars.asSequence().flatMap { it.value }
|
||||
).toList()
|
||||
|
||||
val numberOfAllocatableVariables = allVariables.size
|
||||
val varsRequiringZp = allVariables.filter { it.zp == ZeropageWish.REQUIRE_ZEROPAGE }
|
||||
val varsPreferringZp = allVariables.filter { it.zp == ZeropageWish.PREFER_ZEROPAGE }
|
||||
val varsDontCare = allVariables.filter { it.zp == ZeropageWish.DONTCARE }
|
||||
val numberOfExplicitNonZpVariables = allVariables.count { it.zp == ZeropageWish.NOT_IN_ZEROPAGE }
|
||||
require(varsDontCare.size + varsRequiringZp.size + varsPreferringZp.size + numberOfExplicitNonZpVariables == numberOfAllocatableVariables)
|
||||
|
||||
var numVariablesAllocatedInZP: Int = 0
|
||||
var numberOfNonIntegerVariables: Int = 0
|
||||
|
||||
varsRequiringZp.forEach { variable ->
|
||||
val numElements = numArrayElements(variable)
|
||||
val result = zeropage.allocate(
|
||||
variable.scopedname,
|
||||
variable.type,
|
||||
variable.scope,
|
||||
numElements,
|
||||
variable.initialValue,
|
||||
variable.position,
|
||||
errors
|
||||
)
|
||||
result.fold(
|
||||
success = {
|
||||
numVariablesAllocatedInZP++
|
||||
},
|
||||
failure = {
|
||||
errors.err(it.message!!, variable.position)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if(errors.noErrors()) {
|
||||
varsPreferringZp.forEach { variable ->
|
||||
val numElements = numArrayElements(variable)
|
||||
val result = zeropage.allocate(
|
||||
variable.scopedname,
|
||||
variable.type,
|
||||
variable.scope,
|
||||
numElements,
|
||||
variable.initialValue,
|
||||
variable.position,
|
||||
errors
|
||||
)
|
||||
result.onSuccess { numVariablesAllocatedInZP++ }
|
||||
// no need to check for allocation error, if there is one, just allocate in normal system ram.
|
||||
}
|
||||
|
||||
// try to allocate any other interger variables into the zeropage until it is full.
|
||||
// TODO some form of intelligent priorization? most often used variables first? loopcounter vars first? ...?
|
||||
if(errors.noErrors()) {
|
||||
for (variable in varsDontCare) {
|
||||
if(variable.type in IntegerDatatypes) {
|
||||
if(zeropage.free.isEmpty()) {
|
||||
break
|
||||
} else {
|
||||
val numElements = numArrayElements(variable)
|
||||
val result = zeropage.allocate(
|
||||
variable.scopedname,
|
||||
variable.type,
|
||||
variable.scope,
|
||||
numElements,
|
||||
variable.initialValue,
|
||||
variable.position,
|
||||
errors
|
||||
)
|
||||
result.onSuccess { numVariablesAllocatedInZP++ }
|
||||
}
|
||||
} else
|
||||
numberOfNonIntegerVariables++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println(" number of allocated vars: $numberOfAllocatableVariables")
|
||||
println(" put into zeropage: $numVariablesAllocatedInZP, non-zp allocatable: ${numberOfNonIntegerVariables+numberOfExplicitNonZpVariables}")
|
||||
println(" zeropage free space: ${zeropage.free.size} bytes")
|
||||
}
|
||||
|
||||
internal fun isZpVar(scopedName: List<String>) = scopedName in zeropage.allocatedVariables
|
||||
|
||||
private fun numArrayElements(variable: IVariablesAndConsts.StaticVariable) =
|
||||
when(variable.type) {
|
||||
DataType.STR -> (variable.initialValue as StringLiteral).value.length
|
||||
in ArrayDatatypes -> variable.arraysize!!
|
||||
else -> null
|
||||
}
|
||||
|
||||
internal fun subroutineExtra(sub: Subroutine): SubroutineExtraAsmInfo {
|
||||
var extra = subroutineExtras[sub]
|
||||
return if(extra==null) {
|
||||
extra = SubroutineExtraAsmInfo()
|
||||
subroutineExtras[sub] = extra
|
||||
extra
|
||||
}
|
||||
else
|
||||
extra
|
||||
}
|
||||
|
||||
internal fun getFloatAsmConst(number: Double): String {
|
||||
val asmName = globalFloatConsts[number]
|
||||
if(asmName!=null)
|
||||
return asmName
|
||||
|
||||
val newName = "prog8_float_const_${globalFloatConsts.size}"
|
||||
globalFloatConsts[number] = newName
|
||||
return newName
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class contains various attributes that influence the assembly code generator.
|
||||
* Conceptually it should be part of any INameScope.
|
||||
* But because the resulting code only creates "real" scopes on a subroutine level,
|
||||
* it's more consistent to only define these attributes on a Subroutine node.
|
||||
*/
|
||||
internal class SubroutineExtraAsmInfo {
|
||||
var usedRegsaveA = false
|
||||
var usedRegsaveX = false
|
||||
var usedRegsaveY = false
|
||||
var usedFloatEvalResultVar1 = false
|
||||
var usedFloatEvalResultVar2 = false
|
||||
|
||||
val extraVars = mutableListOf<Triple<DataType, String, UInt?>>()
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
package prog8.codegen.target.cpu6502.codegen.assignment
|
||||
package prog8.codegen.cpu6502.assignment
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.codegen.cpu6502.AsmGen
|
||||
import prog8.compilerinterface.AssemblyError
|
||||
import prog8.compilerinterface.IMemSizer
|
||||
import prog8.codegen.target.AssemblyError
|
||||
import prog8.codegen.target.cpu6502.codegen.AsmGen
|
||||
|
||||
|
||||
internal enum class TargetStorageKind {
|
||||
@ -120,7 +120,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
val array: ArrayIndexedExpression? = null,
|
||||
val memory: DirectMemoryRead? = null,
|
||||
val register: RegisterOrPair? = null,
|
||||
val number: NumericLiteralValue? = null,
|
||||
val number: NumericLiteral? = null,
|
||||
val expression: Expression? = null
|
||||
)
|
||||
{
|
||||
@ -142,9 +142,9 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, cv.type, number = cv)
|
||||
|
||||
return when(value) {
|
||||
is NumericLiteralValue -> throw AssemblyError("should have been constant value")
|
||||
is StringLiteralValue -> throw AssemblyError("string literal value should not occur anymore for asm generation")
|
||||
is ArrayLiteralValue -> throw AssemblyError("array literal value should not occur anymore for asm generation")
|
||||
is NumericLiteral -> throw AssemblyError("should have been constant value")
|
||||
is StringLiteral -> throw AssemblyError("string literal value should not occur anymore for asm generation")
|
||||
is ArrayLiteral -> throw AssemblyError("array literal value should not occur anymore for asm generation")
|
||||
is IdentifierReference -> {
|
||||
val parameter = value.targetVarDecl(program)?.subroutineParameter
|
||||
if(parameter!=null && parameter.definingSubroutine!!.isAsmSubroutine)
|
||||
@ -167,7 +167,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
val dt = value.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
|
||||
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value)
|
||||
}
|
||||
is FunctionCallExpr -> {
|
||||
is FunctionCallExpression -> {
|
||||
when (val sub = value.target.targetStatement(program)) {
|
||||
is Subroutine -> {
|
||||
val returnType = sub.returntypes.zip(sub.asmReturnvaluesRegisters).firstOrNull { rr -> rr.second.registerOrPair != null || rr.second.statusflag!=null }?.first
|
@ -1,20 +1,22 @@
|
||||
package prog8.codegen.target.cpu6502.codegen.assignment
|
||||
package prog8.codegen.cpu6502.assignment
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.toHex
|
||||
import prog8.codegen.target.AssemblyError
|
||||
import prog8.codegen.target.cpu6502.codegen.AsmGen
|
||||
import prog8.codegen.cpu6502.AsmGen
|
||||
import prog8.codegen.cpu6502.VariableAllocator
|
||||
import prog8.compilerinterface.AssemblyError
|
||||
import prog8.compilerinterface.BuiltinFunctions
|
||||
import prog8.compilerinterface.CpuType
|
||||
import prog8.compilerinterface.builtinFunctionReturnType
|
||||
|
||||
|
||||
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||
|
||||
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, asmgen)
|
||||
internal class AssignmentAsmGen(private val program: Program,
|
||||
private val asmgen: AsmGen,
|
||||
private val allocator: VariableAllocator) {
|
||||
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, asmgen, allocator)
|
||||
|
||||
fun translate(assignment: Assignment) {
|
||||
val target = AsmAssignTarget.fromAstAssignment(assignment, program, asmgen)
|
||||
@ -88,7 +90,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue")
|
||||
asmgen.out(" lda #<($arrayVarName+$indexValue) | ldy #>($arrayVarName+$indexValue)")
|
||||
assignFloatFromAY(assign.target)
|
||||
}
|
||||
else ->
|
||||
@ -131,8 +133,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
|
||||
val value = assign.source.memory!!
|
||||
when (value.addressExpression) {
|
||||
is NumericLiteralValue -> {
|
||||
val address = (value.addressExpression as NumericLiteralValue).number.toUInt()
|
||||
is NumericLiteral -> {
|
||||
val address = (value.addressExpression as NumericLiteral).number.toUInt()
|
||||
assignMemoryByte(assign.target, address, null)
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
@ -154,12 +156,12 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
val sourceName = asmgen.asmSymbolName(value.identifier)
|
||||
assignAddressOf(assign.target, sourceName)
|
||||
}
|
||||
is NumericLiteralValue -> throw AssemblyError("source kind should have been literalnumber")
|
||||
is NumericLiteral -> throw AssemblyError("source kind should have been literalnumber")
|
||||
is IdentifierReference -> throw AssemblyError("source kind should have been variable")
|
||||
is ArrayIndexedExpression -> throw AssemblyError("source kind should have been array")
|
||||
is DirectMemoryRead -> throw AssemblyError("source kind should have been memory")
|
||||
is TypecastExpression -> assignTypeCastedValue(assign.target, value.type, value.expression, value)
|
||||
is FunctionCallExpr -> {
|
||||
is FunctionCallExpression -> {
|
||||
when (val sub = value.target.targetStatement(program)) {
|
||||
is Subroutine -> {
|
||||
asmgen.saveXbeforeCall(value)
|
||||
@ -275,16 +277,52 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
containmentCheckIntoA(value)
|
||||
assignRegisterByte(assign.target, CpuRegister.A)
|
||||
}
|
||||
is PipeExpression -> {
|
||||
asmgen.translatePipeExpression(value.expressions, value, false, false)
|
||||
val resultDt = value.inferType(program)
|
||||
val register =
|
||||
if(resultDt.isBytes) RegisterOrPair.A
|
||||
else if(resultDt.isWords) RegisterOrPair.AY
|
||||
else if(resultDt istype DataType.FLOAT) RegisterOrPair.FAC1
|
||||
else throw AssemblyError("invalid dt")
|
||||
asmgen.assignRegister(register, assign.target)
|
||||
}
|
||||
is BinaryExpression -> {
|
||||
if(value.operator in ComparisonOperators) {
|
||||
// TODO real optimized code for comparison expressions that yield a boolean result value
|
||||
// for now generate code for this: assign-false; if expr { assign-true }
|
||||
translateNormalAssignment(
|
||||
AsmAssignment(
|
||||
AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, DataType.UBYTE, number=NumericLiteral.fromBoolean(false, assign.position)),
|
||||
assign.target, false, program.memsizer, assign.position
|
||||
)
|
||||
)
|
||||
val origTarget = assign.target.origAstTarget
|
||||
if(origTarget!=null) {
|
||||
val assignTrue = AnonymousScope(mutableListOf(
|
||||
Assignment(origTarget, NumericLiteral.fromBoolean(true, assign.position), AssignmentOrigin.ASMGEN, assign.position)
|
||||
), assign.position)
|
||||
val assignFalse = AnonymousScope(mutableListOf(), assign.position)
|
||||
val ifelse = IfElse(value.copy(), assignTrue, assignFalse, assign.position)
|
||||
ifelse.linkParents(value)
|
||||
asmgen.translate(ifelse)
|
||||
}
|
||||
else {
|
||||
// no orig ast assign target, can't use the workaround, so fallback to stack eval
|
||||
fallbackToStackEval(value, assign)
|
||||
}
|
||||
} else {
|
||||
// Everything else just evaluate via the stack.
|
||||
// (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...)
|
||||
fallbackToStackEval(value, assign)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// Everything else just evaluate via the stack.
|
||||
// (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...)
|
||||
// TODO DON'T STACK-EVAL THIS... by using a temp var? so that it becomes augmentable assignment expression?
|
||||
asmgen.translateExpression(value)
|
||||
if (assign.target.datatype in WordDatatypes && assign.source.datatype in ByteDatatypes)
|
||||
asmgen.signExtendStackLsb(assign.source.datatype)
|
||||
if(assign.target.kind!=TargetStorageKind.STACK || assign.target.datatype != assign.source.datatype)
|
||||
assignStackValue(assign.target)
|
||||
fallbackToStackEval(value, assign)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -298,9 +336,18 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
}
|
||||
|
||||
private fun fallbackToStackEval(value: Expression, assign: AsmAssignment) {
|
||||
// TODO DON'T STACK-EVAL THIS... by using a temp var? so that it becomes augmentable assignment expression?
|
||||
asmgen.translateExpression(value)
|
||||
if (assign.target.datatype in WordDatatypes && assign.source.datatype in ByteDatatypes)
|
||||
asmgen.signExtendStackLsb(assign.source.datatype)
|
||||
if (assign.target.kind != TargetStorageKind.STACK || assign.target.datatype != assign.source.datatype)
|
||||
assignStackValue(assign.target)
|
||||
}
|
||||
|
||||
private fun containmentCheckIntoA(containment: ContainmentCheck) {
|
||||
val elementDt = containment.element.inferType(program)
|
||||
val range = containment.iterable as? RangeExpr
|
||||
val range = containment.iterable as? RangeExpression
|
||||
if(range!=null) {
|
||||
val constRange = range.toConstantIntegerRange()
|
||||
if(constRange!=null)
|
||||
@ -311,13 +358,24 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
if(variable!=null) {
|
||||
if(elementDt istype DataType.FLOAT)
|
||||
throw AssemblyError("containment check of floats not supported")
|
||||
if(variable.autogeneratedDontRemove) {
|
||||
if(variable.origin!=VarDeclOrigin.USERCODE) {
|
||||
when(variable.datatype) {
|
||||
DataType.STR -> {
|
||||
require(elementDt.isBytes)
|
||||
val stringVal = variable.value as StringLiteralValue
|
||||
val encoded = program.encoding.encodeString(stringVal.value, stringVal.altEncoding)
|
||||
return containmentCheckIntoA(containment.element, elementDt.getOr(DataType.UNDEFINED), encoded.map { it.toInt() })
|
||||
val stringVal = variable.value as StringLiteral
|
||||
if(stringVal.value.length > ContainmentCheck.max_inlined_string_length) {
|
||||
// use subroutine
|
||||
val varname = asmgen.asmVariableName(containment.iterable as IdentifierReference)
|
||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
|
||||
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
|
||||
asmgen.out(" ldy #${stringVal.value.length}")
|
||||
asmgen.out(" jsr prog8_lib.containment_bytearray")
|
||||
return
|
||||
} else {
|
||||
// inline cmp table
|
||||
val encoded = program.encoding.encodeString(stringVal.value, stringVal.encoding)
|
||||
return containmentCheckIntoA(containment.element, elementDt.getOr(DataType.UNDEFINED), encoded.map { it.toInt() })
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
// require(elementDt istype DataType.FLOAT)
|
||||
@ -325,9 +383,32 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
in ArrayDatatypes -> {
|
||||
require(elementDt.isInteger)
|
||||
val arrayVal = variable.value as ArrayLiteralValue
|
||||
val values = arrayVal.value.map { it.constValue(program)!!.number.toInt() }
|
||||
return containmentCheckIntoA(containment.element, elementDt.getOr(DataType.UNDEFINED), values)
|
||||
val arrayVal = variable.value as ArrayLiteral
|
||||
val dt = elementDt.getOr(DataType.UNDEFINED)
|
||||
if(arrayVal.value.size > ContainmentCheck.max_inlined_string_length) {
|
||||
// use subroutine
|
||||
val varname = asmgen.asmVariableName(containment.iterable as IdentifierReference)
|
||||
when(dt) {
|
||||
in ByteDatatypes -> {
|
||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
|
||||
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
|
||||
asmgen.out(" ldy #${arrayVal.value.size}")
|
||||
asmgen.out(" jsr prog8_lib.containment_bytearray")
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
assignExpressionToVariable(containment.element, "P8ZP_SCRATCH_W1", elementDt.getOr(DataType.UNDEFINED), containment.definingSubroutine)
|
||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W2"), varname)
|
||||
asmgen.out(" ldy #${arrayVal.value.size}")
|
||||
asmgen.out(" jsr prog8_lib.containment_wordarray")
|
||||
}
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
return
|
||||
} else {
|
||||
// inline cmp table
|
||||
val values = arrayVal.value.map { it.constValue(program)!!.number.toInt() }
|
||||
return containmentCheckIntoA(containment.element, dt, values)
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
@ -335,16 +416,17 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
val varname = asmgen.asmVariableName(containment.iterable as IdentifierReference)
|
||||
when(variable.datatype) {
|
||||
DataType.STR -> {
|
||||
// use subroutine
|
||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
|
||||
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
|
||||
val stringVal = variable.value as StringLiteralValue
|
||||
val stringVal = variable.value as StringLiteral
|
||||
asmgen.out(" ldy #${stringVal.value.length}")
|
||||
asmgen.out(" jsr prog8_lib.containment_bytearray")
|
||||
return
|
||||
}
|
||||
DataType.ARRAY_F -> throw AssemblyError("containment check of floats not supported")
|
||||
DataType.ARRAY_B, DataType.ARRAY_UB -> {
|
||||
val arrayVal = variable.value as ArrayLiteralValue
|
||||
val arrayVal = variable.value as ArrayLiteral
|
||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
|
||||
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
|
||||
asmgen.out(" ldy #${arrayVal.value.size}")
|
||||
@ -352,7 +434,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
return
|
||||
}
|
||||
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
||||
val arrayVal = variable.value as ArrayLiteralValue
|
||||
val arrayVal = variable.value as ArrayLiteral
|
||||
assignExpressionToVariable(containment.element, "P8ZP_SCRATCH_W1", elementDt.getOr(DataType.UNDEFINED), containment.definingSubroutine)
|
||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W2"), varname)
|
||||
asmgen.out(" ldy #${arrayVal.value.size}")
|
||||
@ -362,15 +444,19 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
}
|
||||
val stringVal = containment.iterable as? StringLiteralValue
|
||||
val stringVal = containment.iterable as? StringLiteral
|
||||
if(stringVal!=null) {
|
||||
require(elementDt.isBytes)
|
||||
val encoded = program.encoding.encodeString(stringVal.value, stringVal.altEncoding)
|
||||
if(stringVal.value.length > ContainmentCheck.max_inlined_string_length)
|
||||
throw AssemblyError("string should have been inlined in if it was this long")
|
||||
val encoded = program.encoding.encodeString(stringVal.value, stringVal.encoding)
|
||||
return containmentCheckIntoA(containment.element, elementDt.getOr(DataType.UNDEFINED), encoded.map { it.toInt() })
|
||||
}
|
||||
val arrayVal = containment.iterable as? ArrayLiteralValue
|
||||
val arrayVal = containment.iterable as? ArrayLiteral
|
||||
if(arrayVal!=null) {
|
||||
require(elementDt.isInteger)
|
||||
if(arrayVal.value.size > ContainmentCheck.max_inlined_string_length)
|
||||
throw AssemblyError("array should have been inlined in if it was this long")
|
||||
val values = arrayVal.value.map { it.constValue(program)!!.number.toInt() }
|
||||
return containmentCheckIntoA(containment.element, elementDt.getOr(DataType.UNDEFINED), values)
|
||||
}
|
||||
@ -381,8 +467,6 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
private fun containmentCheckIntoA(element: Expression, dt: DataType, values: List<Number>) {
|
||||
if(values.size<2)
|
||||
throw AssemblyError("containment check against 0 or 1 values should have been optimized away")
|
||||
|
||||
// TODO don't generate a huge cmp-list when we go over a certain number of values
|
||||
val containsLabel = asmgen.makeLabel("contains")
|
||||
when(dt) {
|
||||
in ByteDatatypes -> {
|
||||
@ -463,8 +547,8 @@ $containsLabel lda #1
|
||||
}
|
||||
|
||||
when (value.addressExpression) {
|
||||
is NumericLiteralValue -> {
|
||||
val address = (value.addressExpression as NumericLiteralValue).number.toUInt()
|
||||
is NumericLiteral -> {
|
||||
val address = (value.addressExpression as NumericLiteral).number.toUInt()
|
||||
assignMemoryByteIntoWord(target, address, null)
|
||||
return
|
||||
}
|
||||
@ -486,7 +570,7 @@ $containsLabel lda #1
|
||||
}
|
||||
}
|
||||
}
|
||||
is NumericLiteralValue -> throw AssemblyError("a cast of a literal value should have been const-folded away")
|
||||
is NumericLiteral -> throw AssemblyError("a cast of a literal value should have been const-folded away")
|
||||
else -> {}
|
||||
}
|
||||
|
||||
@ -564,6 +648,11 @@ $containsLabel lda #1
|
||||
}
|
||||
}
|
||||
|
||||
if(target.kind==TargetStorageKind.REGISTER) {
|
||||
assignExpressionToRegister(value, target.register!!, targetDt==DataType.BYTE || targetDt==DataType.WORD)
|
||||
return
|
||||
}
|
||||
|
||||
if(targetDt==DataType.FLOAT && (target.register==RegisterOrPair.FAC1 || target.register==RegisterOrPair.FAC2)) {
|
||||
when(valueDt) {
|
||||
DataType.UBYTE -> {
|
||||
@ -596,7 +685,7 @@ $containsLabel lda #1
|
||||
}
|
||||
|
||||
private fun assignCastViaLsbFunc(value: Expression, target: AsmAssignTarget) {
|
||||
val lsb = FunctionCallExpr(IdentifierReference(listOf("lsb"), value.position), mutableListOf(value), value.position)
|
||||
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), value.position), mutableListOf(value), value.position)
|
||||
lsb.linkParents(value.parent)
|
||||
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UBYTE, expression = lsb)
|
||||
val assign = AsmAssignment(src, target, false, program.memsizer, value.position)
|
||||
@ -940,8 +1029,8 @@ $containsLabel lda #1
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out("""
|
||||
lda #<${target.asmVarname}+$scaledIdx
|
||||
ldy #>${target.asmVarname}+$scaledIdx
|
||||
lda #<(${target.asmVarname}+$scaledIdx)
|
||||
ldy #>(${target.asmVarname}+$scaledIdx)
|
||||
jsr floats.pop_float
|
||||
""")
|
||||
}
|
||||
@ -1153,8 +1242,8 @@ $containsLabel lda #1
|
||||
ldy #>$sourceName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<${target.asmVarname}+$scaledIdx
|
||||
ldy #>${target.asmVarname}+$scaledIdx
|
||||
lda #<(${target.asmVarname}+$scaledIdx)
|
||||
ldy #>(${target.asmVarname}+$scaledIdx)
|
||||
jsr floats.copy_float
|
||||
""")
|
||||
}
|
||||
@ -1306,17 +1395,13 @@ $containsLabel lda #1
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.out("""
|
||||
lda $sourceName
|
||||
sta ${target.asmVarname}
|
||||
lda $sourceName+1
|
||||
sta ${target.asmVarname}+1
|
||||
lda $sourceName+2
|
||||
sta ${target.asmVarname}+2
|
||||
lda $sourceName+3
|
||||
sta ${target.asmVarname}+3
|
||||
lda $sourceName+4
|
||||
sta ${target.asmVarname}+4
|
||||
""")
|
||||
lda #<$sourceName
|
||||
ldy #>$sourceName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<${target.asmVarname}
|
||||
ldy #>${target.asmVarname}
|
||||
jsr floats.copy_float""")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
asmgen.out("""
|
||||
@ -1437,7 +1522,7 @@ $containsLabel lda #1
|
||||
pha
|
||||
ora #$7f
|
||||
bmi +
|
||||
ldx #0
|
||||
lda #0
|
||||
+ tax
|
||||
pla""")
|
||||
RegisterOrPair.AY -> asmgen.out("""
|
||||
@ -1445,7 +1530,7 @@ $containsLabel lda #1
|
||||
pha
|
||||
ora #$7f
|
||||
bmi +
|
||||
ldy #0
|
||||
lda #0
|
||||
+ tay
|
||||
pla""")
|
||||
RegisterOrPair.XY -> asmgen.out("""
|
||||
@ -1453,9 +1538,19 @@ $containsLabel lda #1
|
||||
tax
|
||||
ora #$7f
|
||||
bmi +
|
||||
ldy #0
|
||||
lda #0
|
||||
+ tay""")
|
||||
else -> throw AssemblyError("only reg pairs are words")
|
||||
in Cx16VirtualRegisters -> {
|
||||
val regname = wordtarget.register.name.lowercase()
|
||||
asmgen.out("""
|
||||
lda $sourceName
|
||||
sta cx16.$regname
|
||||
ora #$7f
|
||||
bmi +
|
||||
lda #0
|
||||
+ sta cx16.$regname+1""")
|
||||
}
|
||||
else -> throw AssemblyError("only reg pairs allowed as word target ${wordtarget.register}")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.STACK -> {
|
||||
@ -1506,7 +1601,14 @@ $containsLabel lda #1
|
||||
RegisterOrPair.AX -> asmgen.out(" ldx #0 | lda $sourceName")
|
||||
RegisterOrPair.AY -> asmgen.out(" ldy #0 | lda $sourceName")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldy #0 | ldx $sourceName")
|
||||
else -> throw AssemblyError("only reg pairs are words")
|
||||
in Cx16VirtualRegisters -> {
|
||||
val regname = wordtarget.register.name.lowercase()
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" lda $sourceName | sta cx16.$regname | stz cx16.$regname+1")
|
||||
else
|
||||
asmgen.out(" lda $sourceName | sta cx16.$regname | lda #0 | sta cx16.$regname+1")
|
||||
}
|
||||
else -> throw AssemblyError("only reg pairs allowed as word target")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.STACK -> {
|
||||
@ -2019,7 +2121,7 @@ $containsLabel lda #1
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("can't assign float to memory byte")
|
||||
TargetStorageKind.REGISTER -> {
|
||||
val floatConst = asmgen.getFloatAsmConst(float)
|
||||
val floatConst = allocator.getFloatAsmConst(float)
|
||||
when(target.register!!) {
|
||||
RegisterOrPair.FAC1 -> asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.MOVFM")
|
||||
RegisterOrPair.FAC2 -> asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.CONUPK")
|
||||
@ -2027,27 +2129,23 @@ $containsLabel lda #1
|
||||
}
|
||||
}
|
||||
TargetStorageKind.STACK -> {
|
||||
val floatConst = asmgen.getFloatAsmConst(float)
|
||||
val floatConst = allocator.getFloatAsmConst(float)
|
||||
asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.push_float")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// non-zero value
|
||||
val constFloat = asmgen.getFloatAsmConst(float)
|
||||
val constFloat = allocator.getFloatAsmConst(float)
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.out("""
|
||||
lda $constFloat
|
||||
sta ${target.asmVarname}
|
||||
lda $constFloat+1
|
||||
sta ${target.asmVarname}+1
|
||||
lda $constFloat+2
|
||||
sta ${target.asmVarname}+2
|
||||
lda $constFloat+3
|
||||
sta ${target.asmVarname}+3
|
||||
lda $constFloat+4
|
||||
sta ${target.asmVarname}+4
|
||||
""")
|
||||
lda #<$constFloat
|
||||
ldy #>$constFloat
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<${target.asmVarname}
|
||||
ldy #>${target.asmVarname}
|
||||
jsr floats.copy_float""")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
val arrayVarName = target.asmVarname
|
||||
@ -2055,17 +2153,13 @@ $containsLabel lda #1
|
||||
if (constIndex!=null) {
|
||||
val indexValue = constIndex * program.memsizer.memorySize(DataType.FLOAT)
|
||||
asmgen.out("""
|
||||
lda $constFloat
|
||||
sta $arrayVarName+$indexValue
|
||||
lda $constFloat+1
|
||||
sta $arrayVarName+$indexValue+1
|
||||
lda $constFloat+2
|
||||
sta $arrayVarName+$indexValue+2
|
||||
lda $constFloat+3
|
||||
sta $arrayVarName+$indexValue+3
|
||||
lda $constFloat+4
|
||||
sta $arrayVarName+$indexValue+4
|
||||
""")
|
||||
lda #<$constFloat
|
||||
ldy #>$constFloat
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<($arrayVarName+$indexValue)
|
||||
ldy #>($arrayVarName+$indexValue)
|
||||
jsr floats.copy_float""")
|
||||
} else {
|
||||
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexExpr as IdentifierReference)
|
||||
asmgen.out("""
|
||||
@ -2084,7 +2178,7 @@ $containsLabel lda #1
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("can't assign float to memory byte")
|
||||
TargetStorageKind.REGISTER -> {
|
||||
val floatConst = asmgen.getFloatAsmConst(float)
|
||||
val floatConst = allocator.getFloatAsmConst(float)
|
||||
when(target.register!!) {
|
||||
RegisterOrPair.FAC1 -> asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.MOVFM")
|
||||
RegisterOrPair.FAC2 -> asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.CONUPK")
|
||||
@ -2092,7 +2186,7 @@ $containsLabel lda #1
|
||||
}
|
||||
}
|
||||
TargetStorageKind.STACK -> {
|
||||
val floatConst = asmgen.getFloatAsmConst(float)
|
||||
val floatConst = allocator.getFloatAsmConst(float)
|
||||
asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.push_float")
|
||||
}
|
||||
}
|
||||
@ -2248,11 +2342,11 @@ $containsLabel lda #1
|
||||
|
||||
private fun storeRegisterAInMemoryAddress(memoryAddress: DirectMemoryWrite) {
|
||||
val addressExpr = memoryAddress.addressExpression
|
||||
val addressLv = addressExpr as? NumericLiteralValue
|
||||
val addressLv = addressExpr as? NumericLiteral
|
||||
|
||||
fun storeViaExprEval() {
|
||||
when(addressExpr) {
|
||||
is NumericLiteralValue, is IdentifierReference -> {
|
||||
is NumericLiteral, is IdentifierReference -> {
|
||||
assignExpressionToVariable(addressExpr, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2")
|
||||
}
|
@ -1,18 +1,20 @@
|
||||
package prog8.codegen.target.cpu6502.codegen.assignment
|
||||
package prog8.codegen.cpu6502.assignment
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.toHex
|
||||
import prog8.codegen.target.AssemblyError
|
||||
import prog8.codegen.target.cpu6502.codegen.AsmGen
|
||||
import prog8.codegen.cpu6502.AsmGen
|
||||
import prog8.codegen.cpu6502.VariableAllocator
|
||||
import prog8.compilerinterface.AssemblyError
|
||||
import prog8.compilerinterface.CpuType
|
||||
|
||||
|
||||
internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
private val assignmentAsmGen: AssignmentAsmGen,
|
||||
private val asmgen: AsmGen
|
||||
private val asmgen: AsmGen,
|
||||
private val allocator: VariableAllocator
|
||||
) {
|
||||
fun translate(assign: AsmAssignment) {
|
||||
require(assign.isAugmentable)
|
||||
@ -160,7 +162,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
origValue
|
||||
}
|
||||
|
||||
val valueLv = (value as? NumericLiteralValue)?.number
|
||||
val valueLv = (value as? NumericLiteral)?.number
|
||||
val ident = value as? IdentifierReference
|
||||
val memread = value as? DirectMemoryRead
|
||||
|
||||
@ -173,7 +175,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
ident != null -> inplaceModification_byte_variable_to_variable(target.asmVarname, target.datatype, operator, ident)
|
||||
memread != null -> inplaceModification_byte_memread_to_variable(target.asmVarname, target.datatype, operator, memread)
|
||||
value is TypecastExpression -> {
|
||||
if (tryRemoveRedundantCast(value, target, operator)) return
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
|
||||
inplaceModification_byte_value_to_variable(target.asmVarname, target.datatype, operator, value)
|
||||
}
|
||||
else -> inplaceModification_byte_value_to_variable(target.asmVarname, target.datatype, operator, value)
|
||||
@ -185,7 +187,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
ident != null -> inplaceModification_word_variable_to_variable(target.asmVarname, target.datatype, operator, ident)
|
||||
memread != null -> inplaceModification_word_memread_to_variable(target.asmVarname, target.datatype, operator, memread)
|
||||
value is TypecastExpression -> {
|
||||
if (tryRemoveRedundantCast(value, target, operator)) return
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator))
|
||||
return
|
||||
inplaceModification_word_value_to_variable(target.asmVarname, target.datatype, operator, value)
|
||||
}
|
||||
else -> inplaceModification_word_value_to_variable(target.asmVarname, target.datatype, operator, value)
|
||||
@ -196,7 +199,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
valueLv != null -> inplaceModification_float_litval_to_variable(target.asmVarname, operator, valueLv.toDouble(), target.scope!!)
|
||||
ident != null -> inplaceModification_float_variable_to_variable(target.asmVarname, operator, ident, target.scope!!)
|
||||
value is TypecastExpression -> {
|
||||
if (tryRemoveRedundantCast(value, target, operator)) return
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
|
||||
inplaceModification_float_value_to_variable(target.asmVarname, operator, value, target.scope!!)
|
||||
}
|
||||
else -> inplaceModification_float_value_to_variable(target.asmVarname, operator, value, target.scope!!)
|
||||
@ -208,15 +211,15 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
TargetStorageKind.MEMORY -> {
|
||||
val memory = target.memory!!
|
||||
when (memory.addressExpression) {
|
||||
is NumericLiteralValue -> {
|
||||
val addr = (memory.addressExpression as NumericLiteralValue).number.toInt()
|
||||
is NumericLiteral -> {
|
||||
val addr = (memory.addressExpression as NumericLiteral).number.toInt()
|
||||
// re-use code to assign a variable, instead this time, use a direct memory address
|
||||
when {
|
||||
valueLv != null -> inplaceModification_byte_litval_to_variable(addr.toHex(), DataType.UBYTE, operator, valueLv.toInt())
|
||||
ident != null -> inplaceModification_byte_variable_to_variable(addr.toHex(), DataType.UBYTE, operator, ident)
|
||||
memread != null -> inplaceModification_byte_memread_to_variable(addr.toHex(), DataType.UBYTE, operator, value)
|
||||
value is TypecastExpression -> {
|
||||
if (tryRemoveRedundantCast(value, target, operator)) return
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
|
||||
inplaceModification_byte_value_to_variable(addr.toHex(), DataType.UBYTE, operator, value)
|
||||
}
|
||||
else -> inplaceModification_byte_value_to_variable(addr.toHex(), DataType.UBYTE, operator, value)
|
||||
@ -228,7 +231,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
valueLv != null -> inplaceModification_byte_litval_to_pointer(pointer, operator, valueLv.toInt())
|
||||
ident != null -> inplaceModification_byte_variable_to_pointer(pointer, operator, ident)
|
||||
value is TypecastExpression -> {
|
||||
if (tryRemoveRedundantCast(value, target, operator)) return
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
|
||||
inplaceModification_byte_value_to_pointer(pointer, operator, value)
|
||||
}
|
||||
else -> inplaceModification_byte_value_to_pointer(pointer, operator, value)
|
||||
@ -243,7 +246,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
ident != null -> inplaceModification_byte_variable_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, ident)
|
||||
memread != null -> inplaceModification_byte_memread_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, memread)
|
||||
value is TypecastExpression -> {
|
||||
if (tryRemoveRedundantCast(value, target, operator)) return
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
|
||||
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)
|
||||
@ -254,7 +257,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
with(target.array!!.indexer) {
|
||||
val indexNum = indexExpr as? NumericLiteralValue
|
||||
val indexNum = indexExpr as? NumericLiteral
|
||||
val indexVar = indexExpr as? IdentifierReference
|
||||
when {
|
||||
indexNum!=null -> {
|
||||
@ -266,7 +269,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
ident != null -> inplaceModification_byte_variable_to_variable(targetVarName, target.datatype, operator, ident)
|
||||
memread != null -> inplaceModification_byte_memread_to_variable(targetVarName, target.datatype, operator, memread)
|
||||
value is TypecastExpression -> {
|
||||
if (tryRemoveRedundantCast(value, target, operator)) return
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
|
||||
inplaceModification_byte_value_to_variable(targetVarName, target.datatype, operator, value)
|
||||
}
|
||||
else -> inplaceModification_byte_value_to_variable(targetVarName, target.datatype, operator, value)
|
||||
@ -278,7 +281,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
ident != null -> inplaceModification_word_variable_to_variable(targetVarName, target.datatype, operator, ident)
|
||||
memread != null -> inplaceModification_word_memread_to_variable(targetVarName, target.datatype, operator, memread)
|
||||
value is TypecastExpression -> {
|
||||
if (tryRemoveRedundantCast(value, target, operator)) return
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
|
||||
inplaceModification_word_value_to_variable(targetVarName, target.datatype, operator, value)
|
||||
}
|
||||
else -> inplaceModification_word_value_to_variable(targetVarName, target.datatype, operator, value)
|
||||
@ -289,7 +292,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
valueLv != null -> inplaceModification_float_litval_to_variable(targetVarName, operator, valueLv.toDouble(), target.scope!!)
|
||||
ident != null -> inplaceModification_float_variable_to_variable(targetVarName, operator, ident, target.scope!!)
|
||||
value is TypecastExpression -> {
|
||||
if (tryRemoveRedundantCast(value, target, operator)) return
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
|
||||
inplaceModification_float_value_to_variable(targetVarName, operator, value, target.scope!!)
|
||||
}
|
||||
else -> inplaceModification_float_value_to_variable(targetVarName, operator, value, target.scope!!)
|
||||
@ -348,7 +351,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun tryRemoveRedundantCast(value: TypecastExpression, target: AsmAssignTarget, operator: String): Boolean {
|
||||
private fun tryInplaceModifyWithRemovedRedundantCast(value: TypecastExpression, target: AsmAssignTarget, operator: String): Boolean {
|
||||
if (target.datatype == value.type) {
|
||||
val childIDt = value.expression.inferType(program)
|
||||
val childDt = childIDt.getOrElse { throw AssemblyError("unknown dt") }
|
||||
@ -744,18 +747,18 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
"^", "xor" -> asmgen.out(" lda $name | eor #$value | sta $name")
|
||||
"==" -> {
|
||||
asmgen.out("""
|
||||
lda #$value
|
||||
cmp $name
|
||||
lda $name
|
||||
cmp #$value
|
||||
beq +
|
||||
lda #0
|
||||
bne ++
|
||||
beq ++
|
||||
+ lda #1
|
||||
+ sta $name""")
|
||||
}
|
||||
"!=" -> {
|
||||
asmgen.out("""
|
||||
lda #$value
|
||||
cmp $name
|
||||
lda $name
|
||||
cmp #$value
|
||||
beq +
|
||||
lda #1
|
||||
bne ++
|
||||
@ -1066,6 +1069,18 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
else
|
||||
asmgen.out(" lda #0 | sta $name | sta $name+1")
|
||||
}
|
||||
value == 0x00ff -> {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz $name+1")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta $name+1")
|
||||
}
|
||||
value == 0xff00 -> {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz $name")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta $name")
|
||||
}
|
||||
value and 255 == 0 -> {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz $name")
|
||||
@ -1145,33 +1160,38 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
+""")
|
||||
else
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
ldy #255
|
||||
lda $otherName
|
||||
bpl +
|
||||
dey ; sign extend
|
||||
+ sty P8ZP_SCRATCH_B1
|
||||
lda $name
|
||||
iny ; sign extend
|
||||
+ eor #255
|
||||
sec
|
||||
sbc $otherName
|
||||
adc $name
|
||||
sta $name
|
||||
lda $name+1
|
||||
sbc P8ZP_SCRATCH_B1
|
||||
tya
|
||||
adc $name+1
|
||||
sta $name+1""")
|
||||
}
|
||||
"*" -> {
|
||||
asmgen.out(" lda $otherName | sta P8ZP_SCRATCH_W1")
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz P8ZP_SCRATCH_W1+1")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta P8ZP_SCRATCH_W1+1")
|
||||
if(valueDt==DataType.UBYTE) {
|
||||
asmgen.out(" lda $otherName | sta P8ZP_SCRATCH_W1")
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz P8ZP_SCRATCH_W1+1")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta P8ZP_SCRATCH_W1+1")
|
||||
} else {
|
||||
asmgen.out(" lda $otherName")
|
||||
asmgen.signExtendAYlsb(valueDt)
|
||||
asmgen.out(" sta P8ZP_SCRATCH_W1 | sty P8ZP_SCRATCH_W1+1")
|
||||
}
|
||||
asmgen.out("""
|
||||
lda $name
|
||||
ldy $name+1
|
||||
jsr math.multiply_words
|
||||
lda math.multiply_words.result
|
||||
sta $name
|
||||
lda math.multiply_words.result+1
|
||||
sta $name+1""")
|
||||
lda $name
|
||||
ldy $name+1
|
||||
jsr math.multiply_words
|
||||
lda math.multiply_words.result
|
||||
sta $name
|
||||
lda math.multiply_words.result+1
|
||||
sta $name+1""")
|
||||
}
|
||||
"/" -> {
|
||||
if(dt==DataType.UWORD) {
|
||||
@ -1427,29 +1447,28 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
sta $name+1""")
|
||||
}
|
||||
"-" -> {
|
||||
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_REG", valueDt, null)
|
||||
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_B1", valueDt, null)
|
||||
if(valueDt==DataType.UBYTE)
|
||||
asmgen.out("""
|
||||
lda $name
|
||||
sec
|
||||
sbc P8ZP_SCRATCH_REG
|
||||
sbc P8ZP_SCRATCH_B1
|
||||
sta $name
|
||||
bcs +
|
||||
dec $name+1
|
||||
+""")
|
||||
else
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda P8ZP_SCRATCH_REG
|
||||
ldy #255
|
||||
lda P8ZP_SCRATCH_B1
|
||||
bpl +
|
||||
dey ; sign extend
|
||||
+ sty P8ZP_SCRATCH_B1
|
||||
lda $name
|
||||
iny ; sign extend
|
||||
+ eor #255
|
||||
sec
|
||||
sbc P8ZP_SCRATCH_REG
|
||||
adc $name
|
||||
sta $name
|
||||
lda $name+1
|
||||
sbc P8ZP_SCRATCH_B1
|
||||
tya
|
||||
adc $name+1
|
||||
sta $name+1""")
|
||||
}
|
||||
"*" -> {
|
||||
@ -1628,7 +1647,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, scope)
|
||||
when (operator) {
|
||||
"**" -> {
|
||||
if(asmgen.haveFPWR()) {
|
||||
if(asmgen.haveFPWRcall()) {
|
||||
asmgen.out("""
|
||||
lda #<$name
|
||||
ldy #>$name
|
||||
@ -1701,11 +1720,11 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
|
||||
private fun inplaceModification_float_litval_to_variable(name: String, operator: String, value: Double, scope: Subroutine) {
|
||||
val constValueName = asmgen.getFloatAsmConst(value)
|
||||
val constValueName = allocator.getFloatAsmConst(value)
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, scope)
|
||||
when (operator) {
|
||||
"**" -> {
|
||||
if(asmgen.haveFPWR()) {
|
||||
if(asmgen.haveFPWRcall()) {
|
||||
asmgen.out("""
|
||||
lda #<$name
|
||||
ldy #>$name
|
||||
@ -1850,8 +1869,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
TargetStorageKind.MEMORY -> {
|
||||
val mem = target.memory!!
|
||||
when (mem.addressExpression) {
|
||||
is NumericLiteralValue -> {
|
||||
val addr = (mem.addressExpression as NumericLiteralValue).number.toHex()
|
||||
is NumericLiteral -> {
|
||||
val addr = (mem.addressExpression as NumericLiteral).number.toHex()
|
||||
asmgen.out("""
|
||||
lda $addr
|
||||
beq +
|
||||
@ -1978,8 +1997,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
TargetStorageKind.MEMORY -> {
|
||||
val memory = target.memory!!
|
||||
when (memory.addressExpression) {
|
||||
is NumericLiteralValue -> {
|
||||
val addr = (memory.addressExpression as NumericLiteralValue).number.toHex()
|
||||
is NumericLiteral -> {
|
||||
val addr = (memory.addressExpression as NumericLiteral).number.toHex()
|
||||
asmgen.out("""
|
||||
lda $addr
|
||||
eor #255
|
||||
@ -2086,40 +2105,38 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
when(target.register!!) { //P8ZP_SCRATCH_REG
|
||||
RegisterOrPair.AX -> {
|
||||
asmgen.out("""
|
||||
sta P8ZP_SCRATCH_REG
|
||||
stx P8ZP_SCRATCH_REG+1
|
||||
lda #0
|
||||
sec
|
||||
sbc P8ZP_SCRATCH_REG
|
||||
eor #255
|
||||
adc #0
|
||||
pha
|
||||
lda #0
|
||||
sbc P8ZP_SCRATCH_REG+1
|
||||
txa
|
||||
eor #255
|
||||
adc #0
|
||||
tax
|
||||
pla""")
|
||||
}
|
||||
RegisterOrPair.AY -> {
|
||||
asmgen.out("""
|
||||
sta P8ZP_SCRATCH_REG
|
||||
sty P8ZP_SCRATCH_REG+1
|
||||
lda #0
|
||||
sec
|
||||
sbc P8ZP_SCRATCH_REG
|
||||
eor #255
|
||||
adc #0
|
||||
pha
|
||||
lda #0
|
||||
sbc P8ZP_SCRATCH_REG+1
|
||||
tya
|
||||
eor #255
|
||||
adc #0
|
||||
tay
|
||||
pla""")
|
||||
}
|
||||
RegisterOrPair.XY -> {
|
||||
asmgen.out("""
|
||||
stx P8ZP_SCRATCH_REG
|
||||
sty P8ZP_SCRATCH_REG+1
|
||||
lda #0
|
||||
sec
|
||||
sbc P8ZP_SCRATCH_REG
|
||||
txa
|
||||
eor #255
|
||||
adc #0
|
||||
tax
|
||||
lda #0
|
||||
sbc P8ZP_SCRATCH_REG+1
|
||||
tya
|
||||
eor #255
|
||||
adc #0
|
||||
tay""")
|
||||
}
|
||||
in Cx16VirtualRegisters -> throw AssemblyError("cx16 virtual regs should be variables, not real registers")
|
46
codeGenExperimental6502/build.gradle
Normal file
46
codeGenExperimental6502/build.gradle
Normal file
@ -0,0 +1,46 @@
|
||||
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'application'
|
||||
id "org.jetbrains.kotlin.jvm"
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(javaVersion)
|
||||
}
|
||||
}
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = javaVersion
|
||||
}
|
||||
}
|
||||
|
||||
compileTestKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = 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"
|
||||
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/src"]
|
||||
}
|
||||
resources {
|
||||
srcDirs = ["${project.projectDir}/res"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// note: there are no unit tests in this module!
|
16
codeGenExperimental6502/codeGenExperimental6502.iml
Normal file
16
codeGenExperimental6502/codeGenExperimental6502.iml
Normal file
@ -0,0 +1,16 @@
|
||||
<?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" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="module" module-name="compilerAst" />
|
||||
<orderEntry type="module" module-name="compilerInterfaces" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
</component>
|
||||
</module>
|
@ -0,0 +1,18 @@
|
||||
package prog8.codegen.experimental6502
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.compilerinterface.*
|
||||
|
||||
class AsmGen(internal val program: Program,
|
||||
internal val errors: IErrorReporter,
|
||||
internal val variables: IVariablesAndConsts,
|
||||
internal val options: CompilationOptions): IAssemblyGenerator {
|
||||
|
||||
override fun compileToAssembly(): IAssemblyProgram? {
|
||||
|
||||
println("\n** experimental 65(c)02 code generator **\n")
|
||||
|
||||
println("..todo: create assembly code into ${options.outputDir.toAbsolutePath()}..")
|
||||
return AssemblyProgram("dummy")
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package prog8.codegen.experimental6502
|
||||
|
||||
import prog8.compilerinterface.CompilationOptions
|
||||
import prog8.compilerinterface.IAssemblyProgram
|
||||
|
||||
|
||||
internal class AssemblyProgram(override val name: String) : IAssemblyProgram
|
||||
{
|
||||
override fun assemble(options: CompilationOptions): Boolean {
|
||||
println("..todo: assemble code into binary..")
|
||||
return false
|
||||
}
|
||||
}
|
46
codeGenTargets/build.gradle
Normal file
46
codeGenTargets/build.gradle
Normal file
@ -0,0 +1,46 @@
|
||||
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'application'
|
||||
id "org.jetbrains.kotlin.jvm"
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(javaVersion)
|
||||
}
|
||||
}
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = javaVersion
|
||||
}
|
||||
}
|
||||
|
||||
compileTestKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = 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"
|
||||
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/src"]
|
||||
}
|
||||
resources {
|
||||
srcDirs = ["${project.projectDir}/res"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// note: there are no unit tests in this module!
|
27
codeGenTargets/src/prog8/codegen/target/C128Target.kt
Normal file
27
codeGenTargets/src/prog8/codegen/target/C128Target.kt
Normal file
@ -0,0 +1,27 @@
|
||||
package prog8.codegen.target
|
||||
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.statements.RegisterOrStatusflag
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.codegen.target.c128.C128MachineDefinition
|
||||
import prog8.codegen.target.cbm.CbmMemorySizer
|
||||
import prog8.codegen.target.cbm.asmsub6502ArgsEvalOrder
|
||||
import prog8.codegen.target.cbm.asmsub6502ArgsHaveRegisterClobberRisk
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
import prog8.compilerinterface.IMemSizer
|
||||
import prog8.compilerinterface.IStringEncoding
|
||||
|
||||
|
||||
class C128Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
|
||||
override val name = NAME
|
||||
override val machine = C128MachineDefinition()
|
||||
|
||||
companion object {
|
||||
const val NAME = "c128"
|
||||
}
|
||||
|
||||
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> =
|
||||
asmsub6502ArgsEvalOrder(sub)
|
||||
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>, paramRegisters: List<RegisterOrStatusflag>) =
|
||||
asmsub6502ArgsHaveRegisterClobberRisk(args, paramRegisters)
|
||||
}
|
27
codeGenTargets/src/prog8/codegen/target/C64Target.kt
Normal file
27
codeGenTargets/src/prog8/codegen/target/C64Target.kt
Normal file
@ -0,0 +1,27 @@
|
||||
package prog8.codegen.target
|
||||
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.statements.RegisterOrStatusflag
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.codegen.target.c64.C64MachineDefinition
|
||||
import prog8.codegen.target.cbm.CbmMemorySizer
|
||||
import prog8.codegen.target.cbm.asmsub6502ArgsEvalOrder
|
||||
import prog8.codegen.target.cbm.asmsub6502ArgsHaveRegisterClobberRisk
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
import prog8.compilerinterface.IMemSizer
|
||||
import prog8.compilerinterface.IStringEncoding
|
||||
|
||||
|
||||
class C64Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
|
||||
override val name = NAME
|
||||
override val machine = C64MachineDefinition()
|
||||
|
||||
companion object {
|
||||
const val NAME = "c64"
|
||||
}
|
||||
|
||||
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> =
|
||||
asmsub6502ArgsEvalOrder(sub)
|
||||
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>, paramRegisters: List<RegisterOrStatusflag>) =
|
||||
asmsub6502ArgsHaveRegisterClobberRisk(args, paramRegisters)
|
||||
}
|
27
codeGenTargets/src/prog8/codegen/target/Cx16Target.kt
Normal file
27
codeGenTargets/src/prog8/codegen/target/Cx16Target.kt
Normal file
@ -0,0 +1,27 @@
|
||||
package prog8.codegen.target
|
||||
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.statements.RegisterOrStatusflag
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.codegen.target.cbm.CbmMemorySizer
|
||||
import prog8.codegen.target.cbm.asmsub6502ArgsEvalOrder
|
||||
import prog8.codegen.target.cbm.asmsub6502ArgsHaveRegisterClobberRisk
|
||||
import prog8.codegen.target.cx16.CX16MachineDefinition
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
import prog8.compilerinterface.IMemSizer
|
||||
import prog8.compilerinterface.IStringEncoding
|
||||
|
||||
|
||||
class Cx16Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
|
||||
override val name = NAME
|
||||
override val machine = CX16MachineDefinition()
|
||||
|
||||
companion object {
|
||||
const val NAME = "cx16"
|
||||
}
|
||||
|
||||
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> =
|
||||
asmsub6502ArgsEvalOrder(sub)
|
||||
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>, paramRegisters: List<RegisterOrStatusflag>) =
|
||||
asmsub6502ArgsHaveRegisterClobberRisk(args, paramRegisters)
|
||||
}
|
35
codeGenTargets/src/prog8/codegen/target/Encoder.kt
Normal file
35
codeGenTargets/src/prog8/codegen/target/Encoder.kt
Normal file
@ -0,0 +1,35 @@
|
||||
package prog8.codegen.target
|
||||
|
||||
import com.github.michaelbull.result.fold
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.codegen.target.cbm.IsoEncoding
|
||||
import prog8.codegen.target.cbm.PetsciiEncoding
|
||||
import prog8.compilerinterface.Encoding
|
||||
import prog8.compilerinterface.IStringEncoding
|
||||
|
||||
internal object Encoder: IStringEncoding {
|
||||
override fun encodeString(str: String, encoding: Encoding): List<UByte> { // TODO use Result
|
||||
val coded = when(encoding) {
|
||||
Encoding.PETSCII -> PetsciiEncoding.encodePetscii(str, true)
|
||||
Encoding.SCREENCODES -> PetsciiEncoding.encodeScreencode(str, true)
|
||||
Encoding.ISO -> IsoEncoding.encode(str)
|
||||
else -> throw FatalAstException("unsupported encoding $encoding")
|
||||
}
|
||||
return coded.fold(
|
||||
failure = { throw it },
|
||||
success = { it }
|
||||
)
|
||||
}
|
||||
override fun decodeString(bytes: List<UByte>, encoding: Encoding): String { // TODO use Result
|
||||
val decoded = when(encoding) {
|
||||
Encoding.PETSCII -> PetsciiEncoding.decodePetscii(bytes, true)
|
||||
Encoding.SCREENCODES -> PetsciiEncoding.decodeScreencode(bytes, true)
|
||||
Encoding.ISO -> IsoEncoding.decode(bytes)
|
||||
else -> throw FatalAstException("unsupported encoding $encoding")
|
||||
}
|
||||
return decoded.fold(
|
||||
failure = { throw it },
|
||||
success = { it }
|
||||
)
|
||||
}
|
||||
}
|
@ -1,9 +1,7 @@
|
||||
package prog8.codegen.target.c128
|
||||
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.codegen.target.c64.normal6502instructions
|
||||
import prog8.codegen.target.cbm.Mflpt5
|
||||
import prog8.codegen.target.cbm.viceMonListName
|
||||
import prog8.compilerinterface.*
|
||||
import java.io.IOException
|
||||
import java.nio.file.Path
|
||||
@ -58,7 +56,6 @@ class C128MachineDefinition: IMachineDefinition {
|
||||
}
|
||||
|
||||
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
|
||||
override fun getPreallocatedZeropageVars(): Map<String, Pair<UInt, DataType>> = emptyMap()
|
||||
|
||||
override fun initializeZeropage(compilerOptions: CompilationOptions) {
|
||||
zeropage = C128Zeropage(compilerOptions)
|
@ -1,8 +1,6 @@
|
||||
package prog8.codegen.target.c64
|
||||
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.codegen.target.cbm.Mflpt5
|
||||
import prog8.codegen.target.cbm.viceMonListName
|
||||
import prog8.compilerinterface.*
|
||||
import java.io.IOException
|
||||
import java.nio.file.Path
|
||||
@ -57,7 +55,6 @@ class C64MachineDefinition: IMachineDefinition {
|
||||
}
|
||||
|
||||
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
|
||||
override fun getPreallocatedZeropageVars(): Map<String, Pair<UInt, DataType>> = emptyMap()
|
||||
|
||||
override fun initializeZeropage(compilerOptions: CompilationOptions) {
|
||||
zeropage = C64Zeropage(compilerOptions)
|
@ -1,8 +1,10 @@
|
||||
package prog8.codegen.target.cpu6502.codegen
|
||||
package prog8.codegen.target.cbm
|
||||
|
||||
import prog8.ast.base.Cx16VirtualRegisters
|
||||
import prog8.ast.base.RegisterOrPair
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.expressions.ArrayIndexedExpression
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.FunctionCallExpression
|
||||
import prog8.ast.statements.RegisterOrStatusflag
|
||||
import prog8.ast.statements.Subroutine
|
||||
|
||||
@ -44,7 +46,7 @@ internal fun asmsub6502ArgsHaveRegisterClobberRisk(args: List<Expression>,
|
||||
it.registerOrPair in listOf(RegisterOrPair.Y, RegisterOrPair.AY, RegisterOrPair.XY)
|
||||
}
|
||||
}
|
||||
is FunctionCallExpr -> {
|
||||
is FunctionCallExpression -> {
|
||||
if (expr.target.nameInSource == listOf("lsb") || expr.target.nameInSource == listOf("msb"))
|
||||
return isClobberRisk(expr.args[0])
|
||||
if (expr.target.nameInSource == listOf("mkword"))
|
@ -0,0 +1,31 @@
|
||||
package prog8.codegen.target.cbm
|
||||
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.StringLiteral
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.compilerinterface.IMemSizer
|
||||
|
||||
internal object CbmMemorySizer: IMemSizer {
|
||||
override fun memorySize(dt: DataType): Int {
|
||||
return when(dt) {
|
||||
in ByteDatatypes -> 1
|
||||
in WordDatatypes, in PassByReferenceDatatypes -> 2
|
||||
DataType.FLOAT -> Mflpt5.FLOAT_MEM_SIZE
|
||||
else -> Int.MIN_VALUE
|
||||
}
|
||||
}
|
||||
|
||||
override fun memorySize(decl: VarDecl): Int {
|
||||
return when(decl.type) {
|
||||
VarDeclType.CONST -> 0
|
||||
VarDeclType.VAR, VarDeclType.MEMORY -> {
|
||||
when(val dt = decl.datatype) {
|
||||
in NumericDatatypes -> return memorySize(dt)
|
||||
in ArrayDatatypes -> decl.arraysize!!.constIndex()!! * memorySize(ArrayToElementTypes.getValue(dt))
|
||||
DataType.STR -> (decl.value as StringLiteral).value.length + 1
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
27
codeGenTargets/src/prog8/codegen/target/cbm/IsoEncoding.kt
Normal file
27
codeGenTargets/src/prog8/codegen/target/cbm/IsoEncoding.kt
Normal file
@ -0,0 +1,27 @@
|
||||
package prog8.codegen.target.cbm
|
||||
|
||||
import com.github.michaelbull.result.Err
|
||||
import com.github.michaelbull.result.Ok
|
||||
import com.github.michaelbull.result.Result
|
||||
import java.io.CharConversionException
|
||||
import java.nio.charset.Charset
|
||||
|
||||
object IsoEncoding {
|
||||
val charset: Charset = Charset.forName("ISO-8859-15")
|
||||
|
||||
fun encode(str: String): Result<List<UByte>, CharConversionException> {
|
||||
return try {
|
||||
Ok(str.toByteArray(charset).map { it.toUByte() })
|
||||
} catch (ce: CharConversionException) {
|
||||
Err(ce)
|
||||
}
|
||||
}
|
||||
|
||||
fun decode(bytes: List<UByte>): Result<String, CharConversionException> {
|
||||
return try {
|
||||
Ok(String(bytes.map { it.toByte() }.toByteArray(), charset))
|
||||
} catch (ce: CharConversionException) {
|
||||
Err(ce)
|
||||
}
|
||||
}
|
||||
}
|
@ -6,10 +6,10 @@ import com.github.michaelbull.result.Result
|
||||
import prog8.ast.antlr.escape
|
||||
import java.io.CharConversionException
|
||||
|
||||
object Petscii {
|
||||
object PetsciiEncoding {
|
||||
|
||||
// decoding: from Petscii/Screencodes (0-255) to unicode
|
||||
// character tables used from https://github.com/dj51d/cbmcodecs
|
||||
// character tables used from https://github.com/irmen/cbmcodecs2
|
||||
|
||||
private val decodingPetsciiLowercase = charArrayOf(
|
||||
'\u0000', // 0x00 -> \u0000
|
||||
@ -1095,13 +1095,17 @@ object Petscii {
|
||||
}
|
||||
}
|
||||
|
||||
fun decodePetscii(petscii: Iterable<UByte>, lowercase: Boolean = false): String {
|
||||
return petscii.map {
|
||||
val code = it.toInt()
|
||||
if(code<0 || code>= decodingPetsciiLowercase.size)
|
||||
throw CharConversionException("petscii $code out of range 0..${decodingPetsciiLowercase.size-1}")
|
||||
if(lowercase) decodingPetsciiLowercase[code] else decodingPetsciiUppercase[code]
|
||||
}.joinToString("")
|
||||
fun decodePetscii(petscii: Iterable<UByte>, lowercase: Boolean = false): Result<String, CharConversionException> {
|
||||
return try {
|
||||
Ok(petscii.map {
|
||||
val code = it.toInt()
|
||||
if(code<0 || code>= decodingPetsciiLowercase.size)
|
||||
throw CharConversionException("petscii $code out of range 0..${decodingPetsciiLowercase.size-1}")
|
||||
if(lowercase) decodingPetsciiLowercase[code] else decodingPetsciiUppercase[code]
|
||||
}.joinToString(""))
|
||||
} catch(ce: CharConversionException) {
|
||||
return Err(ce)
|
||||
}
|
||||
}
|
||||
|
||||
fun encodeScreencode(text: String, lowercase: Boolean = false): Result<List<UByte>, CharConversionException> {
|
||||
@ -1134,13 +1138,17 @@ object Petscii {
|
||||
}
|
||||
}
|
||||
|
||||
fun decodeScreencode(screencode: Iterable<UByte>, lowercase: Boolean = false): String {
|
||||
return screencode.map {
|
||||
val code = it.toInt()
|
||||
if(code<0 || code>= decodingScreencodeLowercase.size)
|
||||
throw CharConversionException("screencode $code out of range 0..${decodingScreencodeLowercase.size-1}")
|
||||
if (lowercase) decodingScreencodeLowercase[code] else decodingScreencodeUppercase[code]
|
||||
}.joinToString("")
|
||||
fun decodeScreencode(screencode: Iterable<UByte>, lowercase: Boolean = false): Result<String, CharConversionException> {
|
||||
return try {
|
||||
Ok(screencode.map {
|
||||
val code = it.toInt()
|
||||
if(code<0 || code>= decodingScreencodeLowercase.size)
|
||||
throw CharConversionException("screencode $code out of range 0..${decodingScreencodeLowercase.size-1}")
|
||||
if (lowercase) decodingScreencodeLowercase[code] else decodingScreencodeUppercase[code]
|
||||
}.joinToString(""))
|
||||
} catch(ce: CharConversionException) {
|
||||
Err(ce)
|
||||
}
|
||||
}
|
||||
|
||||
fun petscii2scr(petscii_code: UByte, inverseVideo: Boolean): Result<UByte, CharConversionException> {
|
@ -1,8 +1,6 @@
|
||||
package prog8.codegen.target.cx16
|
||||
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.codegen.target.cbm.Mflpt5
|
||||
import prog8.codegen.target.cbm.viceMonListName
|
||||
import prog8.compilerinterface.*
|
||||
import java.io.IOException
|
||||
import java.nio.file.Path
|
||||
@ -67,18 +65,6 @@ class CX16MachineDefinition: IMachineDefinition {
|
||||
}
|
||||
|
||||
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0x9f00u..0x9fffu
|
||||
override fun getPreallocatedZeropageVars(): Map<String, Pair<UInt, DataType>> {
|
||||
val vars = mutableMapOf<String, Pair<UInt, DataType>>()
|
||||
for(reg in 0..15) {
|
||||
vars["cx16.r${reg}"] = (2+reg*2).toUInt() to DataType.UWORD // cx16.r0 .. cx16.r15
|
||||
vars["cx16.r${reg}s"] = (2+reg*2).toUInt() to DataType.WORD // cx16.r0s .. cx16.r15s
|
||||
vars["cx16.r${reg}L"] = (2+reg*2).toUInt() to DataType.UBYTE // cx16.r0L .. cx16.r15L
|
||||
vars["cx16.r${reg}H"] = (3+reg*2).toUInt() to DataType.UBYTE // cx16.r0H .. cx16.r15H
|
||||
vars["cx16.r${reg}sL"] = (2+reg*2).toUInt() to DataType.BYTE // cx16.r0sL .. cx16.r15sL
|
||||
vars["cx16.r${reg}sH"] = (3+reg*2).toUInt() to DataType.BYTE // cx16.r0sH .. cx16.r15sH
|
||||
}
|
||||
return vars
|
||||
}
|
||||
|
||||
override fun initializeZeropage(compilerOptions: CompilationOptions) {
|
||||
zeropage = CX16Zeropage(compilerOptions)
|
57
codeGenTargets/src/prog8/codegen/target/cx16/CX16Zeropage.kt
Normal file
57
codeGenTargets/src/prog8/codegen/target/cx16/CX16Zeropage.kt
Normal file
@ -0,0 +1,57 @@
|
||||
package prog8.codegen.target.cx16
|
||||
|
||||
import prog8.ast.GlobalNamespace
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.compilerinterface.CompilationOptions
|
||||
import prog8.compilerinterface.InternalCompilerException
|
||||
import prog8.compilerinterface.Zeropage
|
||||
import prog8.compilerinterface.ZeropageType
|
||||
|
||||
class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
|
||||
override val SCRATCH_B1 = 0x7au // temp storage for a single byte
|
||||
override val SCRATCH_REG = 0x7bu // temp storage for a register, must be B1+1
|
||||
override val SCRATCH_W1 = 0x7cu // temp storage 1 for a word $7c+$7d
|
||||
override val SCRATCH_W2 = 0x7eu // temp storage 2 for a word $7e+$7f
|
||||
|
||||
|
||||
init {
|
||||
if (options.floats && options.zeropage !in arrayOf(ZeropageType.BASICSAFE, ZeropageType.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.
|
||||
|
||||
synchronized(this) {
|
||||
when (options.zeropage) {
|
||||
ZeropageType.FULL -> {
|
||||
free.addAll(0x22u..0xffu)
|
||||
}
|
||||
ZeropageType.KERNALSAFE -> {
|
||||
free.addAll(0x22u..0x7fu)
|
||||
free.addAll(0xa9u..0xffu)
|
||||
}
|
||||
ZeropageType.BASICSAFE -> {
|
||||
free.addAll(0x22u..0x7fu)
|
||||
}
|
||||
ZeropageType.DONTUSE -> {
|
||||
free.clear() // don't use zeropage at all
|
||||
}
|
||||
else -> throw InternalCompilerException("for this machine target, zero page type 'floatsafe' is not available. ${options.zeropage}")
|
||||
}
|
||||
|
||||
removeReservedFromFreePool()
|
||||
|
||||
// note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses.
|
||||
// however, to be able for the compiler to "see" them as zero page variables, we have to register them here as well.
|
||||
val dummyscope = GlobalNamespace(emptyList())
|
||||
for(reg in 0..15) {
|
||||
allocatedVariables[listOf("cx16", "r${reg}")] = ZpAllocation((2+reg*2).toUInt(), DataType.UWORD, 2, dummyscope, null, null) // cx16.r0 .. cx16.r15
|
||||
allocatedVariables[listOf("cx16", "r${reg}s")] = ZpAllocation((2+reg*2).toUInt(), DataType.WORD, 2, dummyscope, null, null) // cx16.r0s .. cx16.r15s
|
||||
allocatedVariables[listOf("cx16", "r${reg}L")] = ZpAllocation((2+reg*2).toUInt(), DataType.UBYTE, 1, dummyscope, null, null) // cx16.r0L .. cx16.r15L
|
||||
allocatedVariables[listOf("cx16", "r${reg}H")] = ZpAllocation((3+reg*2).toUInt(), DataType.UBYTE, 1, dummyscope, null, null) // cx16.r0H .. cx16.r15H
|
||||
allocatedVariables[listOf("cx16", "r${reg}sL")] = ZpAllocation((2+reg*2).toUInt(), DataType.BYTE, 1, dummyscope, null, null) // cx16.r0sL .. cx16.r15sL
|
||||
allocatedVariables[listOf("cx16", "r${reg}sH")] = ZpAllocation((3+reg*2).toUInt(), DataType.BYTE, 1, dummyscope, null, null) // cx16.r0sH .. cx16.r15sH
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
package prog8.codegen.target
|
||||
|
||||
class AssemblyError(msg: String) : RuntimeException(msg)
|
@ -1,41 +0,0 @@
|
||||
package prog8.codegen.target
|
||||
|
||||
import com.github.michaelbull.result.fold
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.statements.RegisterOrStatusflag
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.codegen.target.c128.C128MachineDefinition
|
||||
import prog8.codegen.target.cbm.Petscii
|
||||
import prog8.codegen.target.cpu6502.codegen.asmsub6502ArgsEvalOrder
|
||||
import prog8.codegen.target.cpu6502.codegen.asmsub6502ArgsHaveRegisterClobberRisk
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
|
||||
|
||||
object C128Target: ICompilationTarget {
|
||||
override val name = "c128"
|
||||
override val machine = C128MachineDefinition()
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
|
||||
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<UByte>, altEncoding: Boolean) =
|
||||
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||
|
||||
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> =
|
||||
asmsub6502ArgsEvalOrder(sub)
|
||||
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>, paramRegisters: List<RegisterOrStatusflag>) =
|
||||
asmsub6502ArgsHaveRegisterClobberRisk(args, paramRegisters)
|
||||
|
||||
override fun memorySize(dt: DataType): Int {
|
||||
return when(dt) {
|
||||
in ByteDatatypes -> 1
|
||||
in WordDatatypes, in PassByReferenceDatatypes -> 2
|
||||
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
|
||||
else -> Int.MIN_VALUE
|
||||
}
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
package prog8.codegen.target
|
||||
|
||||
import com.github.michaelbull.result.fold
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.statements.RegisterOrStatusflag
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.codegen.target.c64.C64MachineDefinition
|
||||
import prog8.codegen.target.cbm.Petscii
|
||||
import prog8.codegen.target.cpu6502.codegen.asmsub6502ArgsEvalOrder
|
||||
import prog8.codegen.target.cpu6502.codegen.asmsub6502ArgsHaveRegisterClobberRisk
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
|
||||
|
||||
object C64Target: ICompilationTarget {
|
||||
override val name = "c64"
|
||||
override val machine = C64MachineDefinition()
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
|
||||
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<UByte>, altEncoding: Boolean) =
|
||||
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||
|
||||
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> =
|
||||
asmsub6502ArgsEvalOrder(sub)
|
||||
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>, paramRegisters: List<RegisterOrStatusflag>) =
|
||||
asmsub6502ArgsHaveRegisterClobberRisk(args, paramRegisters)
|
||||
|
||||
override fun memorySize(dt: DataType): Int {
|
||||
return when(dt) {
|
||||
in ByteDatatypes -> 1
|
||||
in WordDatatypes, in PassByReferenceDatatypes -> 2
|
||||
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
|
||||
else -> Int.MIN_VALUE
|
||||
}
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
package prog8.codegen.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.ast.expressions.Expression
|
||||
import prog8.ast.statements.RegisterOrStatusflag
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.codegen.target.cbm.Petscii
|
||||
import prog8.codegen.target.cpu6502.codegen.asmsub6502ArgsEvalOrder
|
||||
import prog8.codegen.target.cpu6502.codegen.asmsub6502ArgsHaveRegisterClobberRisk
|
||||
import prog8.codegen.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<UByte> {
|
||||
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<UByte>, altEncoding: Boolean) =
|
||||
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||
|
||||
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> =
|
||||
asmsub6502ArgsEvalOrder(sub)
|
||||
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>, paramRegisters: List<RegisterOrStatusflag>) =
|
||||
asmsub6502ArgsHaveRegisterClobberRisk(args, paramRegisters)
|
||||
|
||||
override fun memorySize(dt: DataType): Int {
|
||||
return when(dt) {
|
||||
in ByteDatatypes -> 1
|
||||
in WordDatatypes, in PassByReferenceDatatypes -> 2
|
||||
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
|
||||
else -> Int.MIN_VALUE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,41 +0,0 @@
|
||||
package prog8.codegen.target.cx16
|
||||
|
||||
import prog8.compilerinterface.CompilationOptions
|
||||
import prog8.compilerinterface.InternalCompilerException
|
||||
import prog8.compilerinterface.Zeropage
|
||||
import prog8.compilerinterface.ZeropageType
|
||||
|
||||
class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
|
||||
override val SCRATCH_B1 = 0x7au // temp storage for a single byte
|
||||
override val SCRATCH_REG = 0x7bu // temp storage for a register, must be B1+1
|
||||
override val SCRATCH_W1 = 0x7cu // temp storage 1 for a word $7c+$7d
|
||||
override val SCRATCH_W2 = 0x7eu // temp storage 2 for a word $7e+$7f
|
||||
|
||||
|
||||
init {
|
||||
if (options.floats && options.zeropage !in arrayOf(ZeropageType.BASICSAFE, ZeropageType.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.
|
||||
|
||||
when (options.zeropage) {
|
||||
ZeropageType.FULL -> {
|
||||
free.addAll(0x22u..0xffu)
|
||||
}
|
||||
ZeropageType.KERNALSAFE -> {
|
||||
free.addAll(0x22u..0x7fu)
|
||||
free.addAll(0xa9u..0xffu)
|
||||
}
|
||||
ZeropageType.BASICSAFE -> {
|
||||
free.addAll(0x22u..0x7fu)
|
||||
}
|
||||
ZeropageType.DONTUSE -> {
|
||||
free.clear() // don't use zeropage at all
|
||||
}
|
||||
else -> throw InternalCompilerException("for this machine target, zero page type 'floatsafe' is not available. ${options.zeropage}")
|
||||
}
|
||||
|
||||
removeReservedFromFreePool()
|
||||
}
|
||||
}
|
@ -4,13 +4,14 @@ import prog8.ast.IStatementContainer
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.AugmentAssignmentOperators
|
||||
import prog8.ast.expressions.BinaryExpression
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.TypecastExpression
|
||||
import prog8.ast.expressions.AugmentAssignmentOperators
|
||||
import prog8.ast.getTempVar
|
||||
import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.AssignmentOrigin
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compilerinterface.CompilationOptions
|
||||
@ -45,7 +46,7 @@ X = BinExpr X = LeftExpr
|
||||
|
||||
|
||||
*/
|
||||
if(binExpr.operator in AugmentAssignmentOperators + listOf("==", "!=") && isSimpleTarget(assignment.target)) {
|
||||
if(binExpr.operator in AugmentAssignmentOperators && isSimpleTarget(assignment.target)) {
|
||||
if(assignment.target isSameAs binExpr.right)
|
||||
return noModifications
|
||||
if(assignment.target isSameAs binExpr.left) {
|
||||
@ -73,17 +74,8 @@ X = BinExpr X = LeftExpr
|
||||
// )
|
||||
}
|
||||
|
||||
if(binExpr.operator == "==" || binExpr.operator == "!=") {
|
||||
// don't split if the operand(s) don't fit the type of the resulting variable
|
||||
val targetDt = assignment.target.inferType(program)
|
||||
val leftDt = binExpr.left.inferType(program)
|
||||
val rightDt = binExpr.right.inferType(program)
|
||||
if(leftDt isNotAssignableTo targetDt || rightDt isNotAssignableTo targetDt)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
if(binExpr.right.isSimple) {
|
||||
val firstAssign = Assignment(assignment.target.copy(), binExpr.left, binExpr.left.position)
|
||||
val firstAssign = Assignment(assignment.target.copy(), binExpr.left, AssignmentOrigin.OPTIMIZER, binExpr.left.position)
|
||||
val targetExpr = assignment.target.toExpression()
|
||||
val augExpr = BinaryExpression(targetExpr, binExpr.operator, binExpr.right, binExpr.right.position)
|
||||
return listOf(
|
||||
@ -106,17 +98,10 @@ X = BinExpr X = LeftExpr
|
||||
// we can see if we can unwrap the binary expression by working on a new temporary variable
|
||||
// (that has the type of the expression), and then finally doing the typecast.
|
||||
// Once it's outside the typecast, the regular splitting can commence.
|
||||
val tempVar = when(val tempDt = origExpr.inferType(program).getOr(DataType.UNDEFINED)) {
|
||||
DataType.UBYTE -> listOf("prog8_lib", "retval_interm_ub")
|
||||
DataType.BYTE -> listOf("prog8_lib", "retval_interm_b")
|
||||
DataType.UWORD -> listOf("prog8_lib", "retval_interm_uw")
|
||||
DataType.WORD -> listOf("prog8_lib", "retval_interm_w")
|
||||
DataType.FLOAT -> listOf("floats", "tempvar_swap_float")
|
||||
else -> throw FatalAstException("invalid dt $tempDt")
|
||||
}
|
||||
val tempVar = program.getTempVar(origExpr.inferType(program).getOr(DataType.UNDEFINED))
|
||||
val assignTempVar = Assignment(
|
||||
AssignTarget(IdentifierReference(tempVar, typecast.position), null, null, typecast.position),
|
||||
typecast.expression, typecast.position
|
||||
typecast.expression, AssignmentOrigin.OPTIMIZER, typecast.position
|
||||
)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, assignTempVar, parent as IStatementContainer),
|
||||
|
@ -2,13 +2,13 @@ package prog8.optimizer
|
||||
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
import kotlin.math.pow
|
||||
|
||||
|
||||
class ConstExprEvaluator {
|
||||
|
||||
fun evaluate(left: NumericLiteralValue, operator: String, right: NumericLiteralValue): Expression {
|
||||
fun evaluate(left: NumericLiteral, operator: String, right: NumericLiteral): Expression {
|
||||
try {
|
||||
return when(operator) {
|
||||
"+" -> plus(left, right)
|
||||
@ -23,12 +23,12 @@ class ConstExprEvaluator {
|
||||
"and" -> logicaland(left, right)
|
||||
"or" -> logicalor(left, right)
|
||||
"xor" -> logicalxor(left, right)
|
||||
"<" -> NumericLiteralValue.fromBoolean(left < right, left.position)
|
||||
">" -> NumericLiteralValue.fromBoolean(left > right, left.position)
|
||||
"<=" -> NumericLiteralValue.fromBoolean(left <= right, left.position)
|
||||
">=" -> NumericLiteralValue.fromBoolean(left >= right, left.position)
|
||||
"==" -> NumericLiteralValue.fromBoolean(left == right, left.position)
|
||||
"!=" -> NumericLiteralValue.fromBoolean(left != right, left.position)
|
||||
"<" -> NumericLiteral.fromBoolean(left < right, left.position)
|
||||
">" -> NumericLiteral.fromBoolean(left > right, left.position)
|
||||
"<=" -> NumericLiteral.fromBoolean(left <= right, left.position)
|
||||
">=" -> NumericLiteral.fromBoolean(left >= right, left.position)
|
||||
"==" -> NumericLiteral.fromBoolean(left == right, left.position)
|
||||
"!=" -> NumericLiteral.fromBoolean(left != right, left.position)
|
||||
"<<" -> shiftedleft(left, right)
|
||||
">>" -> shiftedright(left, right)
|
||||
else -> throw FatalAstException("const evaluation for invalid operator $operator")
|
||||
@ -38,7 +38,7 @@ class ConstExprEvaluator {
|
||||
}
|
||||
}
|
||||
|
||||
private fun shiftedright(left: NumericLiteralValue, amount: NumericLiteralValue): Expression {
|
||||
private fun shiftedright(left: NumericLiteral, amount: NumericLiteral): Expression {
|
||||
if(left.type !in IntegerDatatypes || amount.type !in IntegerDatatypes)
|
||||
throw ExpressionError("cannot compute $left >> $amount", left.position)
|
||||
val result =
|
||||
@ -46,168 +46,168 @@ class ConstExprEvaluator {
|
||||
left.number.toInt().ushr(amount.number.toInt())
|
||||
else
|
||||
left.number.toInt().shr(amount.number.toInt())
|
||||
return NumericLiteralValue(left.type, result.toDouble(), left.position)
|
||||
return NumericLiteral(left.type, result.toDouble(), left.position)
|
||||
}
|
||||
|
||||
private fun shiftedleft(left: NumericLiteralValue, amount: NumericLiteralValue): Expression {
|
||||
private fun shiftedleft(left: NumericLiteral, amount: NumericLiteral): Expression {
|
||||
if(left.type !in IntegerDatatypes || amount.type !in IntegerDatatypes)
|
||||
throw ExpressionError("cannot compute $left << $amount", left.position)
|
||||
val result = left.number.toInt().shl(amount.number.toInt())
|
||||
return NumericLiteralValue(left.type, result.toDouble(), left.position)
|
||||
return NumericLiteral(left.type, result.toDouble(), left.position)
|
||||
}
|
||||
|
||||
private fun logicalxor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||
private fun logicalxor(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
|
||||
val error = "cannot compute $left locical-bitxor $right"
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number.toInt() != 0), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number != 0.0), left.position)
|
||||
in IntegerDatatypes -> NumericLiteral.fromBoolean((left.number.toInt() != 0) xor (right.number.toInt() != 0), left.position)
|
||||
DataType.FLOAT -> NumericLiteral.fromBoolean((left.number.toInt() != 0) xor (right.number != 0.0), left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
DataType.FLOAT -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number != 0.0) xor (right.number.toInt() != 0), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number != 0.0) xor (right.number != 0.0), left.position)
|
||||
in IntegerDatatypes -> NumericLiteral.fromBoolean((left.number != 0.0) xor (right.number.toInt() != 0), left.position)
|
||||
DataType.FLOAT -> NumericLiteral.fromBoolean((left.number != 0.0) xor (right.number != 0.0), left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
}
|
||||
|
||||
private fun logicalor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||
private fun logicalor(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
|
||||
val error = "cannot compute $left locical-or $right"
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number.toInt() != 0, left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number != 0.0, left.position)
|
||||
in IntegerDatatypes -> NumericLiteral.fromBoolean(left.number.toInt() != 0 || right.number.toInt() != 0, left.position)
|
||||
DataType.FLOAT -> NumericLiteral.fromBoolean(left.number.toInt() != 0 || right.number != 0.0, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
DataType.FLOAT -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number != 0.0 || right.number.toInt() != 0, left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number != 0.0 || right.number != 0.0, left.position)
|
||||
in IntegerDatatypes -> NumericLiteral.fromBoolean(left.number != 0.0 || right.number.toInt() != 0, left.position)
|
||||
DataType.FLOAT -> NumericLiteral.fromBoolean(left.number != 0.0 || right.number != 0.0, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
}
|
||||
|
||||
private fun logicaland(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||
private fun logicaland(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
|
||||
val error = "cannot compute $left locical-and $right"
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number.toInt() != 0, left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number != 0.0, left.position)
|
||||
in IntegerDatatypes -> NumericLiteral.fromBoolean(left.number.toInt() != 0 && right.number.toInt() != 0, left.position)
|
||||
DataType.FLOAT -> NumericLiteral.fromBoolean(left.number.toInt() != 0 && right.number != 0.0, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
DataType.FLOAT -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number != 0.0 && right.number.toInt() != 0, left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number != 0.0 && right.number != 0.0, left.position)
|
||||
in IntegerDatatypes -> NumericLiteral.fromBoolean(left.number != 0.0 && right.number.toInt() != 0, left.position)
|
||||
DataType.FLOAT -> NumericLiteral.fromBoolean(left.number != 0.0 && right.number != 0.0, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
}
|
||||
|
||||
private fun bitwisexor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||
private fun bitwisexor(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
|
||||
if(left.type== DataType.UBYTE) {
|
||||
if(right.type in IntegerDatatypes) {
|
||||
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() xor (right.number.toInt() and 255)).toDouble(), left.position)
|
||||
return NumericLiteral(DataType.UBYTE, (left.number.toInt() xor (right.number.toInt() and 255)).toDouble(), left.position)
|
||||
}
|
||||
} else if(left.type== DataType.UWORD) {
|
||||
if(right.type in IntegerDatatypes) {
|
||||
return NumericLiteralValue(DataType.UWORD, (left.number.toInt() xor right.number.toInt()).toDouble(), left.position)
|
||||
return NumericLiteral(DataType.UWORD, (left.number.toInt() xor right.number.toInt()).toDouble(), left.position)
|
||||
}
|
||||
}
|
||||
throw ExpressionError("cannot calculate $left ^ $right", left.position)
|
||||
}
|
||||
|
||||
private fun bitwiseor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||
private fun bitwiseor(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
|
||||
if(left.type== DataType.UBYTE) {
|
||||
if(right.type in IntegerDatatypes) {
|
||||
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() or (right.number.toInt() and 255)).toDouble(), left.position)
|
||||
return NumericLiteral(DataType.UBYTE, (left.number.toInt() or (right.number.toInt() and 255)).toDouble(), left.position)
|
||||
}
|
||||
} else if(left.type== DataType.UWORD) {
|
||||
if(right.type in IntegerDatatypes) {
|
||||
return NumericLiteralValue(DataType.UWORD, (left.number.toInt() or right.number.toInt()).toDouble(), left.position)
|
||||
return NumericLiteral(DataType.UWORD, (left.number.toInt() or right.number.toInt()).toDouble(), left.position)
|
||||
}
|
||||
}
|
||||
throw ExpressionError("cannot calculate $left | $right", left.position)
|
||||
}
|
||||
|
||||
private fun bitwiseand(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||
private fun bitwiseand(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
|
||||
if(left.type== DataType.UBYTE) {
|
||||
if(right.type in IntegerDatatypes) {
|
||||
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() and (right.number.toInt() and 255)).toDouble(), left.position)
|
||||
return NumericLiteral(DataType.UBYTE, (left.number.toInt() and (right.number.toInt() and 255)).toDouble(), left.position)
|
||||
}
|
||||
} else if(left.type== DataType.UWORD) {
|
||||
if(right.type in IntegerDatatypes) {
|
||||
return NumericLiteralValue(DataType.UWORD, (left.number.toInt() and right.number.toInt()).toDouble(), left.position)
|
||||
return NumericLiteral(DataType.UWORD, (left.number.toInt() and right.number.toInt()).toDouble(), left.position)
|
||||
}
|
||||
}
|
||||
throw ExpressionError("cannot calculate $left & $right", left.position)
|
||||
}
|
||||
|
||||
private fun power(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||
private fun power(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
|
||||
val error = "cannot calculate $left ** $right"
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble().pow(right.number.toInt()), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt().toDouble().pow(right.number), left.position)
|
||||
in IntegerDatatypes -> NumericLiteral.optimalNumeric(left.number.toInt().toDouble().pow(right.number.toInt()), left.position)
|
||||
DataType.FLOAT -> NumericLiteral(DataType.FLOAT, left.number.toInt().toDouble().pow(right.number), left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
DataType.FLOAT -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.pow(right.number.toInt()), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.pow(right.number), left.position)
|
||||
in IntegerDatatypes -> NumericLiteral(DataType.FLOAT, left.number.pow(right.number.toInt()), left.position)
|
||||
DataType.FLOAT -> NumericLiteral(DataType.FLOAT, left.number.pow(right.number), left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
}
|
||||
|
||||
private fun plus(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||
private fun plus(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
|
||||
val error = "cannot add $left and $right"
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() + right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() + right.number, left.position)
|
||||
in IntegerDatatypes -> NumericLiteral.optimalInteger(left.number.toInt() + right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteral(DataType.FLOAT, left.number.toInt() + right.number, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
DataType.FLOAT -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number + right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number + right.number, left.position)
|
||||
in IntegerDatatypes -> NumericLiteral(DataType.FLOAT, left.number + right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteral(DataType.FLOAT, left.number + right.number, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
}
|
||||
|
||||
private fun minus(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||
private fun minus(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
|
||||
val error = "cannot subtract $left and $right"
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() - right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() - right.number, left.position)
|
||||
in IntegerDatatypes -> NumericLiteral.optimalInteger(left.number.toInt() - right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteral(DataType.FLOAT, left.number.toInt() - right.number, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
DataType.FLOAT -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number - right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number - right.number, left.position)
|
||||
in IntegerDatatypes -> NumericLiteral(DataType.FLOAT, left.number - right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteral(DataType.FLOAT, left.number - right.number, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
}
|
||||
|
||||
private fun multiply(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||
private fun multiply(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
|
||||
val error = "cannot multiply ${left.type} and ${right.type}"
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() * right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() * right.number, left.position)
|
||||
in IntegerDatatypes -> NumericLiteral.optimalInteger(left.number.toInt() * right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteral(DataType.FLOAT, left.number.toInt() * right.number, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
DataType.FLOAT -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number * right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number * right.number, left.position)
|
||||
in IntegerDatatypes -> NumericLiteral(DataType.FLOAT, left.number * right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteral(DataType.FLOAT, left.number * right.number, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
@ -217,29 +217,29 @@ class ConstExprEvaluator {
|
||||
private fun divideByZeroError(pos: Position): Unit =
|
||||
throw ExpressionError("division by zero", pos)
|
||||
|
||||
private fun divide(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||
private fun divide(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
|
||||
val error = "cannot divide $left by $right"
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> {
|
||||
if(right.number.toInt()==0) divideByZeroError(right.position)
|
||||
val result: Int = left.number.toInt() / right.number.toInt()
|
||||
NumericLiteralValue.optimalInteger(result, left.position)
|
||||
NumericLiteral.optimalInteger(result, left.position)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
if(right.number==0.0) divideByZeroError(right.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number.toInt() / right.number, left.position)
|
||||
NumericLiteral(DataType.FLOAT, left.number.toInt() / right.number, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
DataType.FLOAT -> when (right.type) {
|
||||
in IntegerDatatypes -> {
|
||||
if(right.number.toInt()==0) divideByZeroError(right.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number / right.number.toInt(), left.position)
|
||||
NumericLiteral(DataType.FLOAT, left.number / right.number.toInt(), left.position)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
if(right.number ==0.0) divideByZeroError(right.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number / right.number, left.position)
|
||||
NumericLiteral(DataType.FLOAT, left.number / right.number, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
@ -247,28 +247,28 @@ class ConstExprEvaluator {
|
||||
}
|
||||
}
|
||||
|
||||
private fun remainder(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||
private fun remainder(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
|
||||
val error = "cannot compute remainder of $left by $right"
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> {
|
||||
if(right.number.toInt()==0) divideByZeroError(right.position)
|
||||
NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble() % right.number.toInt().toDouble(), left.position)
|
||||
NumericLiteral.optimalNumeric(left.number.toInt().toDouble() % right.number.toInt().toDouble(), left.position)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
if(right.number ==0.0) divideByZeroError(right.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number.toInt() % right.number, left.position)
|
||||
NumericLiteral(DataType.FLOAT, left.number.toInt() % right.number, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
DataType.FLOAT -> when (right.type) {
|
||||
in IntegerDatatypes -> {
|
||||
if(right.number.toInt()==0) divideByZeroError(right.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number % right.number.toInt(), left.position)
|
||||
NumericLiteral(DataType.FLOAT, left.number % right.number.toInt(), left.position)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
if(right.number ==0.0) divideByZeroError(right.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number % right.number, left.position)
|
||||
NumericLiteral(DataType.FLOAT, left.number % right.number, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
|
@ -35,19 +35,19 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
// Compile-time constant sub expressions will be evaluated on the spot.
|
||||
// For instance, the expression for "- 4.5" will be optimized into the float literal -4.5
|
||||
val subexpr = expr.expression
|
||||
if (subexpr is NumericLiteralValue) {
|
||||
if (subexpr is NumericLiteral) {
|
||||
// accept prefixed literal values (such as -3, not true)
|
||||
return when (expr.operator) {
|
||||
"+" -> listOf(IAstModification.ReplaceNode(expr, subexpr, parent))
|
||||
"-" -> when (subexpr.type) {
|
||||
in IntegerDatatypes -> {
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue.optimalInteger(-subexpr.number.toInt(), subexpr.position),
|
||||
NumericLiteral.optimalInteger(-subexpr.number.toInt(), subexpr.position),
|
||||
parent))
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue(DataType.FLOAT, -subexpr.number, subexpr.position),
|
||||
NumericLiteral(DataType.FLOAT, -subexpr.number, subexpr.position),
|
||||
parent))
|
||||
}
|
||||
else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
|
||||
@ -55,29 +55,29 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
"~" -> when (subexpr.type) {
|
||||
DataType.BYTE -> {
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue(DataType.BYTE, subexpr.number.toInt().inv().toDouble(), subexpr.position),
|
||||
NumericLiteral(DataType.BYTE, subexpr.number.toInt().inv().toDouble(), subexpr.position),
|
||||
parent))
|
||||
}
|
||||
DataType.UBYTE -> {
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue(DataType.UBYTE, (subexpr.number.toInt().inv() and 255).toDouble(), subexpr.position),
|
||||
NumericLiteral(DataType.UBYTE, (subexpr.number.toInt().inv() and 255).toDouble(), subexpr.position),
|
||||
parent))
|
||||
}
|
||||
DataType.WORD -> {
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue(DataType.WORD, subexpr.number.toInt().inv().toDouble(), subexpr.position),
|
||||
NumericLiteral(DataType.WORD, subexpr.number.toInt().inv().toDouble(), subexpr.position),
|
||||
parent))
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue(DataType.UWORD, (subexpr.number.toInt().inv() and 65535).toDouble(), subexpr.position),
|
||||
NumericLiteral(DataType.UWORD, (subexpr.number.toInt().inv() and 65535).toDouble(), subexpr.position),
|
||||
parent))
|
||||
}
|
||||
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
|
||||
}
|
||||
"not" -> {
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue.fromBoolean(subexpr.number == 0.0, subexpr.position),
|
||||
NumericLiteral.fromBoolean(subexpr.number == 0.0, subexpr.position),
|
||||
parent))
|
||||
}
|
||||
else -> throw ExpressionError(expr.operator, subexpr.position)
|
||||
@ -116,7 +116,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
when (leftExpr.operator) {
|
||||
"+" -> {
|
||||
// X + С1 == C2 --> X == C2 - C1
|
||||
val newRightConst = NumericLiteralValue(rightconst.type, rightconst.number - leftRightConst.number, rightconst.position)
|
||||
val newRightConst = NumericLiteral(rightconst.type, rightconst.number - leftRightConst.number, rightconst.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(leftExpr, leftExpr.left, expr),
|
||||
IAstModification.ReplaceNode(expr.right, newRightConst, expr)
|
||||
@ -124,7 +124,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
}
|
||||
"-" -> {
|
||||
// X - С1 == C2 --> X == C2 + C1
|
||||
val newRightConst = NumericLiteralValue(rightconst.type, rightconst.number + leftRightConst.number, rightconst.position)
|
||||
val newRightConst = NumericLiteral(rightconst.type, rightconst.number + leftRightConst.number, rightconst.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(leftExpr, leftExpr.left, expr),
|
||||
IAstModification.ReplaceNode(expr.right, newRightConst, expr)
|
||||
@ -142,16 +142,16 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
val leftDt = leftconst.inferType(program).getOr(DataType.UNDEFINED)
|
||||
when (leftconst.number) {
|
||||
0.0 -> {
|
||||
val value = NumericLiteralValue(leftDt, 0.0, expr.position)
|
||||
val value = NumericLiteral(leftDt, 0.0, expr.position)
|
||||
modifications += IAstModification.ReplaceNode(expr, value, parent)
|
||||
}
|
||||
1.0 -> {
|
||||
val value = NumericLiteralValue(leftDt, 1.0, expr.position)
|
||||
val value = NumericLiteral(leftDt, 1.0, expr.position)
|
||||
modifications += IAstModification.ReplaceNode(expr, value, parent)
|
||||
}
|
||||
2.0 -> {
|
||||
if(rightconst!=null) {
|
||||
val value = NumericLiteralValue(leftDt, 2.0.pow(rightconst.number), expr.position)
|
||||
val value = NumericLiteral(leftDt, 2.0.pow(rightconst.number), expr.position)
|
||||
modifications += IAstModification.ReplaceNode(expr, value, parent)
|
||||
} else {
|
||||
val rightDt = expr.right.inferType(program).getOr(DataType.UNDEFINED)
|
||||
@ -162,7 +162,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
is VarDecl -> parent.datatype
|
||||
else -> leftDt
|
||||
}
|
||||
val one = NumericLiteralValue(targetDt, 1.0, expr.position)
|
||||
val one = NumericLiteral(targetDt, 1.0, expr.position)
|
||||
val shift = BinaryExpression(one, "<<", expr.right, expr.position)
|
||||
modifications += IAstModification.ReplaceNode(expr, shift, parent)
|
||||
}
|
||||
@ -284,7 +284,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
return modifications
|
||||
}
|
||||
|
||||
override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(array: ArrayLiteral, parent: Node): Iterable<IAstModification> {
|
||||
// because constant folding can result in arrays that are now suddenly capable
|
||||
// of telling the type of all their elements (for instance, when they contained -2 which
|
||||
// was a prefix expression earlier), we recalculate the array's datatype.
|
||||
@ -310,7 +310,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(functionCallExpr: FunctionCallExpr, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
|
||||
// the args of a fuction are constfolded via recursion already.
|
||||
val constvalue = functionCallExpr.constValue(program)
|
||||
return if(constvalue!=null)
|
||||
@ -320,7 +320,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
}
|
||||
|
||||
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
|
||||
fun adjustRangeDt(rangeFrom: NumericLiteralValue, targetDt: DataType, rangeTo: NumericLiteralValue, stepLiteral: NumericLiteralValue?, range: RangeExpr): RangeExpr? {
|
||||
fun adjustRangeDt(rangeFrom: NumericLiteral, targetDt: DataType, rangeTo: NumericLiteral, stepLiteral: NumericLiteral?, range: RangeExpression): RangeExpression? {
|
||||
val fromCast = rangeFrom.cast(targetDt)
|
||||
val toCast = rangeTo.cast(targetDt)
|
||||
if(!fromCast.isValid || !toCast.isValid)
|
||||
@ -337,18 +337,18 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
range.step
|
||||
}
|
||||
|
||||
return RangeExpr(fromCast.valueOrZero(), toCast.valueOrZero(), newStep, range.position)
|
||||
return RangeExpression(fromCast.valueOrZero(), toCast.valueOrZero(), newStep, range.position)
|
||||
}
|
||||
|
||||
// adjust the datatype of a range expression in for loops to the loop variable.
|
||||
val iterableRange = forLoop.iterable as? RangeExpr ?: return noModifications
|
||||
val rangeFrom = iterableRange.from as? NumericLiteralValue
|
||||
val rangeTo = iterableRange.to as? NumericLiteralValue
|
||||
val iterableRange = forLoop.iterable as? RangeExpression ?: return noModifications
|
||||
val rangeFrom = iterableRange.from as? NumericLiteral
|
||||
val rangeTo = iterableRange.to as? NumericLiteral
|
||||
if(rangeFrom==null || rangeTo==null) return noModifications
|
||||
|
||||
val loopvar = forLoop.loopVar.targetVarDecl(program) ?: throw UndefinedSymbolError(forLoop.loopVar)
|
||||
|
||||
val stepLiteral = iterableRange.step as? NumericLiteralValue
|
||||
val stepLiteral = iterableRange.step as? NumericLiteral
|
||||
when(loopvar.datatype) {
|
||||
DataType.UBYTE -> {
|
||||
if(rangeFrom.type!= DataType.UBYTE) {
|
||||
@ -389,7 +389,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
}
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
val numval = decl.value as? NumericLiteralValue
|
||||
val numval = decl.value as? NumericLiteral
|
||||
if(decl.type== VarDeclType.CONST && numval!=null) {
|
||||
val valueDt = numval.inferType(program)
|
||||
if(valueDt isnot decl.datatype) {
|
||||
|
@ -25,7 +25,7 @@ class VarConstantValueTypeAdjuster(private val program: Program, private val err
|
||||
&& declConstValue.type != decl.datatype) {
|
||||
// avoid silent float roundings
|
||||
if(decl.datatype in IntegerDatatypes && declConstValue.type==DataType.FLOAT) {
|
||||
errors.err("refused silent rounding of float to avoid loss of precision", decl.value!!.position)
|
||||
errors.err("refused rounding of float to avoid loss of precision", decl.value!!.position)
|
||||
} else {
|
||||
// cast the numeric literal to the appropriate datatype of the variable
|
||||
val cast = declConstValue.cast(decl.datatype)
|
||||
@ -40,7 +40,7 @@ class VarConstantValueTypeAdjuster(private val program: Program, private val err
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(range: RangeExpr, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(range: RangeExpression, parent: Node): Iterable<IAstModification> {
|
||||
val from = range.from.constValue(program)?.number
|
||||
val to = range.to.constValue(program)?.number
|
||||
val step = range.step.constValue(program)?.number
|
||||
@ -93,7 +93,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
in NumericDatatypes -> listOf(
|
||||
IAstModification.ReplaceNode(
|
||||
identifier,
|
||||
NumericLiteralValue(cval.type, cval.number, identifier.position),
|
||||
NumericLiteral(cval.type, cval.number, identifier.position),
|
||||
identifier.parent
|
||||
)
|
||||
)
|
||||
@ -118,11 +118,11 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
val arraysize = decl.arraysize
|
||||
if(arraysize==null) {
|
||||
// for arrays that have no size specifier attempt to deduce the size
|
||||
val arrayval = decl.value as? ArrayLiteralValue
|
||||
val arrayval = decl.value as? ArrayLiteral
|
||||
if(arrayval!=null) {
|
||||
return listOf(IAstModification.SetExpression(
|
||||
{ decl.arraysize = ArrayIndex(it, decl.position) },
|
||||
NumericLiteralValue.optimalInteger(arrayval.value.size, decl.position),
|
||||
NumericLiteral.optimalInteger(arrayval.value.size, decl.position),
|
||||
decl
|
||||
))
|
||||
}
|
||||
@ -132,14 +132,14 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
when(decl.datatype) {
|
||||
DataType.FLOAT -> {
|
||||
// vardecl: for scalar float vars, promote constant integer initialization values to floats
|
||||
val litval = decl.value as? NumericLiteralValue
|
||||
val litval = decl.value as? NumericLiteral
|
||||
if (litval!=null && litval.type in IntegerDatatypes) {
|
||||
val newValue = NumericLiteralValue(DataType.FLOAT, litval.number, litval.position)
|
||||
val newValue = NumericLiteral(DataType.FLOAT, litval.number, litval.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
val rangeExpr = decl.value as? RangeExpr
|
||||
val rangeExpr = decl.value as? RangeExpression
|
||||
if(rangeExpr!=null) {
|
||||
// convert the initializer range expression to an actual array
|
||||
val declArraySize = decl.arraysize?.constIndex()
|
||||
@ -149,18 +149,18 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
if(constRange!=null) {
|
||||
val eltType = rangeExpr.inferType(program).getOr(DataType.UBYTE)
|
||||
val newValue = if(eltType in ByteDatatypes) {
|
||||
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
|
||||
constRange.map { NumericLiteralValue(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
|
||||
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
} else {
|
||||
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
|
||||
constRange.map { NumericLiteralValue(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
|
||||
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||
}
|
||||
}
|
||||
val numericLv = decl.value as? NumericLiteralValue
|
||||
val numericLv = decl.value as? NumericLiteral
|
||||
if(numericLv!=null && numericLv.type== DataType.FLOAT)
|
||||
errors.err("arraysize requires only integers here", numericLv.position)
|
||||
val size = decl.arraysize?.constIndex() ?: return noModifications
|
||||
@ -187,13 +187,13 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
else -> {}
|
||||
}
|
||||
// create the array itself, filled with the fillvalue.
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteralValue(ArrayToElementTypes.getValue(decl.datatype), it.toDouble(), numericLv.position) }.toTypedArray<Expression>()
|
||||
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteral(ArrayToElementTypes.getValue(decl.datatype), it.toDouble(), numericLv.position) }.toTypedArray<Expression>()
|
||||
val refValue = ArrayLiteral(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
val rangeExpr = decl.value as? RangeExpr
|
||||
val rangeExpr = decl.value as? RangeExpression
|
||||
if(rangeExpr!=null) {
|
||||
// convert the initializer range expression to an actual array of floats
|
||||
val declArraySize = decl.arraysize?.constIndex()
|
||||
@ -201,14 +201,14 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
|
||||
val constRange = rangeExpr.toConstantIntegerRange()
|
||||
if(constRange!=null) {
|
||||
val newValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F),
|
||||
constRange.map { NumericLiteralValue(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||
val newValue = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F),
|
||||
constRange.map { NumericLiteral(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||
}
|
||||
}
|
||||
|
||||
val numericLv = decl.value as? NumericLiteralValue
|
||||
val numericLv = decl.value as? NumericLiteral
|
||||
val size = decl.arraysize?.constIndex() ?: return noModifications
|
||||
if(rangeExpr==null && numericLv!=null) {
|
||||
// arraysize initializer is a single int, and we know the size.
|
||||
@ -217,8 +217,8 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
errors.err("float value overflow", numericLv.position)
|
||||
else {
|
||||
// create the array itself, filled with the fillvalue.
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteralValue(DataType.FLOAT, it, numericLv.position) }.toTypedArray<Expression>()
|
||||
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = numericLv.position)
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.FLOAT, it, numericLv.position) }.toTypedArray<Expression>()
|
||||
val refValue = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = numericLv.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
|
||||
}
|
||||
}
|
||||
|
@ -8,12 +8,10 @@ import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.base.IntegerDatatypes
|
||||
import prog8.ast.base.NumericDatatypes
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.AnonymousScope
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.IfElse
|
||||
import prog8.ast.statements.Jump
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.log2
|
||||
import kotlin.math.pow
|
||||
@ -26,7 +24,7 @@ import kotlin.math.pow
|
||||
*/
|
||||
|
||||
|
||||
class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
class ExpressionSimplifier(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
|
||||
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
|
||||
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
|
||||
|
||||
@ -34,7 +32,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
val mods = mutableListOf<IAstModification>()
|
||||
|
||||
// try to statically convert a literal value into one of the desired type
|
||||
val literal = typecast.expression as? NumericLiteralValue
|
||||
val literal = typecast.expression as? NumericLiteral
|
||||
if (literal != null) {
|
||||
val newLiteral = literal.cast(typecast.type)
|
||||
if (newLiteral.isValid && newLiteral.valueOrZero() !== literal)
|
||||
@ -144,7 +142,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
val x = expr.right
|
||||
val y = determineY(x, leftBinExpr)
|
||||
if (y != null) {
|
||||
val yPlus1 = BinaryExpression(y, "+", NumericLiteralValue(leftDt, 1.0, y.position), y.position)
|
||||
val yPlus1 = BinaryExpression(y, "+", NumericLiteral(leftDt, 1.0, y.position), y.position)
|
||||
val newExpr = BinaryExpression(x, "*", yPlus1, x.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||
}
|
||||
@ -154,7 +152,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
val x = expr.right
|
||||
val y = determineY(x, leftBinExpr)
|
||||
if (y != null) {
|
||||
val yMinus1 = BinaryExpression(y, "-", NumericLiteralValue(leftDt, 1.0, y.position), y.position)
|
||||
val yMinus1 = BinaryExpression(y, "-", NumericLiteral(leftDt, 1.0, y.position), y.position)
|
||||
val newExpr = BinaryExpression(x, "*", yMinus1, x.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||
}
|
||||
@ -166,7 +164,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
val x = expr.left
|
||||
val y = determineY(x, rightBinExpr)
|
||||
if (y != null) {
|
||||
val yPlus1 = BinaryExpression(y, "+", NumericLiteralValue.optimalInteger(1, y.position), y.position)
|
||||
val yPlus1 = BinaryExpression(y, "+", NumericLiteral.optimalInteger(1, y.position), y.position)
|
||||
val newExpr = BinaryExpression(x, "*", yPlus1, x.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||
}
|
||||
@ -177,54 +175,32 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
if(leftDt!=DataType.FLOAT && expr.operator == ">=" && rightVal?.number == 1.0) {
|
||||
// for integers: x >= 1 --> x > 0
|
||||
expr.operator = ">"
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteralValue.optimalInteger(0, expr.right.position), expr))
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral.optimalInteger(0, expr.right.position), expr))
|
||||
}
|
||||
|
||||
if(expr.operator == ">=" && rightVal?.number == 0.0) {
|
||||
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
|
||||
// unsigned >= 0 --> true
|
||||
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(true, expr.position), parent))
|
||||
return listOf(IAstModification.ReplaceNode(expr, NumericLiteral.fromBoolean(true, expr.position), parent))
|
||||
}
|
||||
}
|
||||
|
||||
if(leftDt!=DataType.FLOAT && expr.operator == "<" && rightVal?.number == 1.0) {
|
||||
// for integers: x < 1 --> x <= 0
|
||||
expr.operator = "<="
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteralValue.optimalInteger(0, expr.right.position), expr))
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral.optimalInteger(0, expr.right.position), expr))
|
||||
}
|
||||
|
||||
if(expr.operator == "<" && rightVal?.number == 0.0) {
|
||||
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
|
||||
// unsigned < 0 --> false
|
||||
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(false, expr.position), parent))
|
||||
}
|
||||
when(leftDt) {
|
||||
DataType.BYTE -> {
|
||||
// signed < 0 --> signed & $80
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr,
|
||||
BinaryExpression(expr.left, "&", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
|
||||
parent
|
||||
))
|
||||
}
|
||||
DataType.WORD -> {
|
||||
// signedw < 0 --> msb(signedw) & $80
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr,
|
||||
BinaryExpression(FunctionCallExpr(IdentifierReference(listOf("msb"), expr.position),
|
||||
mutableListOf(expr.left),
|
||||
expr.position
|
||||
), "&", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
|
||||
parent
|
||||
))
|
||||
}
|
||||
else -> {}
|
||||
return listOf(IAstModification.ReplaceNode(expr, NumericLiteral.fromBoolean(false, expr.position), parent))
|
||||
}
|
||||
}
|
||||
|
||||
// simplify when a term is constant and directly determines the outcome
|
||||
val constTrue = NumericLiteralValue.fromBoolean(true, expr.position)
|
||||
val constFalse = NumericLiteralValue.fromBoolean(false, expr.position)
|
||||
val constTrue = NumericLiteral.fromBoolean(true, expr.position)
|
||||
val constFalse = NumericLiteral.fromBoolean(false, expr.position)
|
||||
val newExpr: Expression? = when (expr.operator) {
|
||||
"or" -> {
|
||||
when {
|
||||
@ -255,10 +231,10 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
when {
|
||||
leftVal?.number==0.0 -> expr.right
|
||||
rightVal?.number==0.0 -> expr.left
|
||||
rightIDt.isBytes && rightVal?.number==255.0 -> NumericLiteralValue(DataType.UBYTE, 255.0, rightVal.position)
|
||||
rightIDt.isWords && rightVal?.number==65535.0 -> NumericLiteralValue(DataType.UWORD, 65535.0, rightVal.position)
|
||||
leftIDt.isBytes && leftVal?.number==255.0 -> NumericLiteralValue(DataType.UBYTE, 255.0, leftVal.position)
|
||||
leftIDt.isWords && leftVal?.number==65535.0 -> NumericLiteralValue(DataType.UWORD, 65535.0, leftVal.position)
|
||||
rightIDt.isBytes && rightVal?.number==255.0 -> NumericLiteral(DataType.UBYTE, 255.0, rightVal.position)
|
||||
rightIDt.isWords && rightVal?.number==65535.0 -> NumericLiteral(DataType.UWORD, 65535.0, rightVal.position)
|
||||
leftIDt.isBytes && leftVal?.number==255.0 -> NumericLiteral(DataType.UBYTE, 255.0, leftVal.position)
|
||||
leftIDt.isWords && leftVal?.number==65535.0 -> NumericLiteral(DataType.UWORD, 65535.0, leftVal.position)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
@ -301,7 +277,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(functionCallExpr: FunctionCallExpr, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(functionCallExpr.target.nameInSource == listOf("lsb")) {
|
||||
val arg = functionCallExpr.args[0]
|
||||
if(arg is TypecastExpression) {
|
||||
@ -326,7 +302,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
// useless msb() of byte value that was typecasted to word, replace with 0
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
functionCallExpr,
|
||||
NumericLiteralValue(valueDt.getOr(DataType.UBYTE), 0.0, arg.expression.position),
|
||||
NumericLiteral(valueDt.getOr(DataType.UBYTE), 0.0, arg.expression.position),
|
||||
parent))
|
||||
}
|
||||
} else {
|
||||
@ -335,7 +311,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
// useless msb() of byte value, replace with 0
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
functionCallExpr,
|
||||
NumericLiteralValue(argDt.getOr(DataType.UBYTE), 0.0, arg.position),
|
||||
NumericLiteral(argDt.getOr(DataType.UBYTE), 0.0, arg.position),
|
||||
parent))
|
||||
}
|
||||
}
|
||||
@ -345,7 +321,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
}
|
||||
|
||||
override fun after(containment: ContainmentCheck, parent: Node): Iterable<IAstModification> {
|
||||
val range = containment.iterable as? RangeExpr
|
||||
val range = containment.iterable as? RangeExpression
|
||||
if(range!=null && range.step.constValue(program)?.number==1.0) {
|
||||
val from = range.from.constValue(program)
|
||||
val to = range.to.constValue(program)
|
||||
@ -363,6 +339,44 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(pipeExpr: PipeExpression, parent: Node): Iterable<IAstModification> {
|
||||
val expressions = pipeExpr.expressions
|
||||
val firstValue = expressions.first()
|
||||
if(firstValue.isSimple) {
|
||||
val funcname = expressions[1] as IdentifierReference
|
||||
val first = FunctionCallExpression(funcname.copy(), mutableListOf(firstValue), firstValue.position)
|
||||
val newExprs = mutableListOf<Expression>(first)
|
||||
newExprs.addAll(expressions.drop(2))
|
||||
return listOf(IAstModification.ReplaceNode(pipeExpr, PipeExpression(newExprs, pipeExpr.position), parent))
|
||||
}
|
||||
val singleExpr = expressions.singleOrNull()
|
||||
if(singleExpr!=null) {
|
||||
val callExpr = singleExpr as FunctionCallExpression
|
||||
val call = FunctionCallExpression(callExpr.target, callExpr.args, callExpr.position)
|
||||
return listOf(IAstModification.ReplaceNode(pipeExpr, call, parent))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(pipe: Pipe, parent: Node): Iterable<IAstModification> {
|
||||
val expressions = pipe.expressions
|
||||
val firstValue = expressions.first()
|
||||
if(firstValue.isSimple) {
|
||||
val funcname = expressions[1] as IdentifierReference
|
||||
val first = FunctionCallExpression(funcname.copy(), mutableListOf(firstValue), firstValue.position)
|
||||
val newExprs = mutableListOf<Expression>(first)
|
||||
newExprs.addAll(expressions.drop(2))
|
||||
return listOf(IAstModification.ReplaceNode(pipe, Pipe(newExprs, pipe.position), parent))
|
||||
}
|
||||
val singleExpr = expressions.singleOrNull()
|
||||
if(singleExpr!=null) {
|
||||
val callExpr = singleExpr as FunctionCallExpression
|
||||
val call = FunctionCallStatement(callExpr.target, callExpr.args, true, callExpr.position)
|
||||
return listOf(IAstModification.ReplaceNode(pipe, call, parent))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun determineY(x: Expression, subBinExpr: BinaryExpression): Expression? {
|
||||
return when {
|
||||
subBinExpr.left isSameAs x -> subBinExpr.right
|
||||
@ -371,11 +385,11 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun optimizeAdd(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? {
|
||||
private fun optimizeAdd(expr: BinaryExpression, leftVal: NumericLiteral?, rightVal: NumericLiteral?): Expression? {
|
||||
if(expr.left.isSameAs(expr.right)) {
|
||||
// optimize X+X into X *2
|
||||
expr.operator = "*"
|
||||
expr.right = NumericLiteralValue.optimalInteger(2, expr.right.position)
|
||||
expr.right = NumericLiteral.optimalInteger(2, expr.right.position)
|
||||
expr.right.linkParents(expr)
|
||||
return expr
|
||||
}
|
||||
@ -386,7 +400,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
val (expr2, _, rightVal2) = reorderAssociativeWithConstant(expr, leftVal)
|
||||
if (rightVal2 != null) {
|
||||
// right value is a constant, see if we can optimize
|
||||
val rightConst: NumericLiteralValue = rightVal2
|
||||
val rightConst: NumericLiteral = rightVal2
|
||||
when (rightConst.number) {
|
||||
0.0 -> {
|
||||
// left
|
||||
@ -399,17 +413,17 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
val rnum = rightVal?.number
|
||||
if(rnum!=null && rnum<0.0) {
|
||||
expr.operator = "-"
|
||||
expr.right = NumericLiteralValue(rightVal.type, -rnum, rightVal.position)
|
||||
expr.right = NumericLiteral(rightVal.type, -rnum, rightVal.position)
|
||||
return expr
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun optimizeSub(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? {
|
||||
private fun optimizeSub(expr: BinaryExpression, leftVal: NumericLiteral?, rightVal: NumericLiteral?): Expression? {
|
||||
if(expr.left.isSameAs(expr.right)) {
|
||||
// optimize X-X into 0
|
||||
return NumericLiteralValue.optimalInteger(0, expr.position)
|
||||
return NumericLiteral.optimalInteger(0, expr.position)
|
||||
}
|
||||
|
||||
if (leftVal == null && rightVal == null)
|
||||
@ -425,7 +439,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
|
||||
if(rnum<0.0) {
|
||||
expr.operator = "+"
|
||||
expr.right = NumericLiteralValue(rightVal.type, -rnum, rightVal.position)
|
||||
expr.right = NumericLiteral(rightVal.type, -rnum, rightVal.position)
|
||||
return expr
|
||||
}
|
||||
}
|
||||
@ -443,38 +457,38 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
return null
|
||||
}
|
||||
|
||||
private fun optimizePower(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? {
|
||||
private fun optimizePower(expr: BinaryExpression, leftVal: NumericLiteral?, rightVal: NumericLiteral?): Expression? {
|
||||
if (leftVal == null && rightVal == null)
|
||||
return null
|
||||
|
||||
if (rightVal != null) {
|
||||
// right value is a constant, see if we can optimize
|
||||
val rightConst: NumericLiteralValue = rightVal
|
||||
val rightConst: NumericLiteral = rightVal
|
||||
when (rightConst.number) {
|
||||
-3.0 -> {
|
||||
// -1/(left*left*left)
|
||||
return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/",
|
||||
return BinaryExpression(NumericLiteral(DataType.FLOAT, -1.0, expr.position), "/",
|
||||
BinaryExpression(expr.left, "*", BinaryExpression(expr.left, "*", expr.left, expr.position), expr.position),
|
||||
expr.position)
|
||||
}
|
||||
-2.0 -> {
|
||||
// -1/(left*left)
|
||||
return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/",
|
||||
return BinaryExpression(NumericLiteral(DataType.FLOAT, -1.0, expr.position), "/",
|
||||
BinaryExpression(expr.left, "*", expr.left, expr.position),
|
||||
expr.position)
|
||||
}
|
||||
-1.0 -> {
|
||||
// -1/left
|
||||
return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/",
|
||||
return BinaryExpression(NumericLiteral(DataType.FLOAT, -1.0, expr.position), "/",
|
||||
expr.left, expr.position)
|
||||
}
|
||||
0.0 -> {
|
||||
// 1
|
||||
return NumericLiteralValue(rightConst.type, 1.0, expr.position)
|
||||
return NumericLiteral(rightConst.type, 1.0, expr.position)
|
||||
}
|
||||
0.5 -> {
|
||||
// sqrt(left)
|
||||
return FunctionCallExpr(IdentifierReference(listOf("sqrt"), expr.position), mutableListOf(expr.left), expr.position)
|
||||
return FunctionCallExpression(IdentifierReference(listOf("sqrt"), expr.position), mutableListOf(expr.left), expr.position)
|
||||
}
|
||||
1.0 -> {
|
||||
// left
|
||||
@ -495,15 +509,15 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
when (leftVal.number) {
|
||||
-1.0 -> {
|
||||
// -1
|
||||
return NumericLiteralValue(DataType.FLOAT, -1.0, expr.position)
|
||||
return NumericLiteral(DataType.FLOAT, -1.0, expr.position)
|
||||
}
|
||||
0.0 -> {
|
||||
// 0
|
||||
return NumericLiteralValue(leftVal.type, 0.0, expr.position)
|
||||
return NumericLiteral(leftVal.type, 0.0, expr.position)
|
||||
}
|
||||
1.0 -> {
|
||||
//1
|
||||
return NumericLiteralValue(leftVal.type, 1.0, expr.position)
|
||||
return NumericLiteral(leftVal.type, 1.0, expr.position)
|
||||
}
|
||||
|
||||
}
|
||||
@ -512,7 +526,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
return null
|
||||
}
|
||||
|
||||
private fun optimizeRemainder(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? {
|
||||
private fun optimizeRemainder(expr: BinaryExpression, leftVal: NumericLiteral?, rightVal: NumericLiteral?): Expression? {
|
||||
if (leftVal == null && rightVal == null)
|
||||
return null
|
||||
|
||||
@ -525,10 +539,10 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
val idt = expr.inferType(program)
|
||||
if(!idt.isKnown)
|
||||
throw FatalAstException("unknown dt")
|
||||
return NumericLiteralValue(idt.getOr(DataType.UNDEFINED), 0.0, expr.position)
|
||||
return NumericLiteral(idt.getOr(DataType.UNDEFINED), 0.0, expr.position)
|
||||
} else if (cv in powersOfTwo) {
|
||||
expr.operator = "&"
|
||||
expr.right = NumericLiteralValue.optimalInteger(cv!!.toInt()-1, expr.position)
|
||||
expr.right = NumericLiteral.optimalInteger(cv!!.toInt()-1, expr.position)
|
||||
return null
|
||||
}
|
||||
}
|
||||
@ -537,14 +551,14 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
|
||||
}
|
||||
|
||||
private fun optimizeDivision(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? {
|
||||
private fun optimizeDivision(expr: BinaryExpression, leftVal: NumericLiteral?, rightVal: NumericLiteral?): Expression? {
|
||||
if (leftVal == null && rightVal == null)
|
||||
return null
|
||||
|
||||
// cannot shuffle assiciativity with division!
|
||||
if (rightVal != null) {
|
||||
// right value is a constant, see if we can optimize
|
||||
val rightConst: NumericLiteralValue = rightVal
|
||||
val rightConst: NumericLiteral = rightVal
|
||||
val cv = rightConst.number
|
||||
val leftIDt = expr.left.inferType(program)
|
||||
if (!leftIDt.isKnown)
|
||||
@ -567,25 +581,25 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
if (leftDt in IntegerDatatypes) {
|
||||
// divided by a power of two => shift right
|
||||
val numshifts = log2(cv).toInt()
|
||||
return BinaryExpression(expr.left, ">>", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
|
||||
return BinaryExpression(expr.left, ">>", NumericLiteral.optimalInteger(numshifts, expr.position), expr.position)
|
||||
}
|
||||
}
|
||||
in negativePowersOfTwo -> {
|
||||
if (leftDt in IntegerDatatypes) {
|
||||
// divided by a negative power of two => negate, then shift right
|
||||
val numshifts = log2(-cv).toInt()
|
||||
return BinaryExpression(PrefixExpression("-", expr.left, expr.position), ">>", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
|
||||
return BinaryExpression(PrefixExpression("-", expr.left, expr.position), ">>", NumericLiteral.optimalInteger(numshifts, expr.position), expr.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (leftDt == DataType.UBYTE) {
|
||||
if (abs(rightConst.number) >= 256.0) {
|
||||
return NumericLiteralValue(DataType.UBYTE, 0.0, expr.position)
|
||||
return NumericLiteral(DataType.UBYTE, 0.0, expr.position)
|
||||
}
|
||||
} else if (leftDt == DataType.UWORD) {
|
||||
if (abs(rightConst.number) >= 65536.0) {
|
||||
return NumericLiteralValue(DataType.UBYTE, 0.0, expr.position)
|
||||
return NumericLiteral(DataType.UBYTE, 0.0, expr.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -595,7 +609,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
when (leftVal.number) {
|
||||
0.0 -> {
|
||||
// 0
|
||||
return NumericLiteralValue(leftVal.type, 0.0, expr.position)
|
||||
return NumericLiteral(leftVal.type, 0.0, expr.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -603,7 +617,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
return null
|
||||
}
|
||||
|
||||
private fun optimizeMultiplication(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? {
|
||||
private fun optimizeMultiplication(expr: BinaryExpression, leftVal: NumericLiteral?, rightVal: NumericLiteral?): Expression? {
|
||||
if (leftVal == null && rightVal == null)
|
||||
return null
|
||||
|
||||
@ -611,7 +625,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
if (rightVal2 != null) {
|
||||
// right value is a constant, see if we can optimize
|
||||
val leftValue: Expression = expr2.left
|
||||
val rightConst: NumericLiteralValue = rightVal2
|
||||
val rightConst: NumericLiteral = rightVal2
|
||||
when (val cv = rightConst.number) {
|
||||
-1.0 -> {
|
||||
// -left
|
||||
@ -619,7 +633,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
}
|
||||
0.0 -> {
|
||||
// 0
|
||||
return NumericLiteralValue(rightConst.type, 0.0, expr.position)
|
||||
return NumericLiteral(rightConst.type, 0.0, expr.position)
|
||||
}
|
||||
1.0 -> {
|
||||
// left
|
||||
@ -629,14 +643,14 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
if (leftValue.inferType(program).isInteger) {
|
||||
// times a power of two => shift left
|
||||
val numshifts = log2(cv).toInt()
|
||||
return BinaryExpression(expr2.left, "<<", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
|
||||
return BinaryExpression(expr2.left, "<<", NumericLiteral.optimalInteger(numshifts, expr.position), expr.position)
|
||||
}
|
||||
}
|
||||
in negativePowersOfTwo -> {
|
||||
if (leftValue.inferType(program).isInteger) {
|
||||
// times a negative power of two => negate, then shift left
|
||||
val numshifts = log2(-cv).toInt()
|
||||
return BinaryExpression(PrefixExpression("-", expr2.left, expr.position), "<<", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
|
||||
return BinaryExpression(PrefixExpression("-", expr2.left, expr.position), "<<", NumericLiteral.optimalInteger(numshifts, expr.position), expr.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -646,7 +660,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
return null
|
||||
}
|
||||
|
||||
private fun optimizeShiftLeft(expr: BinaryExpression, amountLv: NumericLiteralValue?): Expression? {
|
||||
private fun optimizeShiftLeft(expr: BinaryExpression, amountLv: NumericLiteral?): Expression? {
|
||||
if (amountLv == null)
|
||||
return null
|
||||
|
||||
@ -660,19 +674,16 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
when (val targetDt = targetIDt.getOr(DataType.UNDEFINED)) {
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
if (amount >= 8) {
|
||||
return NumericLiteralValue(targetDt, 0.0, expr.position)
|
||||
return NumericLiteral(targetDt, 0.0, expr.position)
|
||||
}
|
||||
}
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
if (amount >= 16) {
|
||||
return NumericLiteralValue(targetDt, 0.0, expr.position)
|
||||
} else if (amount >= 8) {
|
||||
val lsb = FunctionCallExpr(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
|
||||
if (amount == 8) {
|
||||
return FunctionCallExpr(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(lsb, NumericLiteralValue.optimalInteger(0, expr.position)), expr.position)
|
||||
}
|
||||
val shifted = BinaryExpression(lsb, "<<", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position)
|
||||
return FunctionCallExpr(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(shifted, NumericLiteralValue.optimalInteger(0, expr.position)), expr.position)
|
||||
return NumericLiteral(targetDt, 0.0, expr.position)
|
||||
} else if (amount > 8) {
|
||||
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
|
||||
val shifted = BinaryExpression(lsb, "<<", NumericLiteral.optimalInteger(amount - 8, expr.position), expr.position)
|
||||
return FunctionCallExpression(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(shifted, NumericLiteral.optimalInteger(0, expr.position)), expr.position)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
@ -681,7 +692,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
return null
|
||||
}
|
||||
|
||||
private fun optimizeShiftRight(expr: BinaryExpression, amountLv: NumericLiteralValue?): Expression? {
|
||||
private fun optimizeShiftRight(expr: BinaryExpression, amountLv: NumericLiteral?): Expression? {
|
||||
if (amountLv == null)
|
||||
return null
|
||||
|
||||
@ -695,32 +706,27 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
when (idt.getOr(DataType.UNDEFINED)) {
|
||||
DataType.UBYTE -> {
|
||||
if (amount >= 8) {
|
||||
return NumericLiteralValue.optimalInteger(0, expr.position)
|
||||
return NumericLiteral.optimalInteger(0, expr.position)
|
||||
}
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
if (amount > 8) {
|
||||
expr.right = NumericLiteralValue.optimalInteger(8, expr.right.position)
|
||||
expr.right = NumericLiteral.optimalInteger(8, expr.right.position)
|
||||
return null
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
if (amount >= 16) {
|
||||
return NumericLiteralValue.optimalInteger(0, expr.position)
|
||||
return NumericLiteral.optimalInteger(0, expr.position)
|
||||
}
|
||||
else if (amount >= 8) {
|
||||
val msb = FunctionCallExpr(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
|
||||
if (amount == 8) {
|
||||
// mkword(0, msb(v))
|
||||
val zero = NumericLiteralValue(DataType.UBYTE, 0.0, expr.position)
|
||||
return FunctionCallExpr(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(zero, msb), expr.position)
|
||||
}
|
||||
return TypecastExpression(BinaryExpression(msb, ">>", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position), DataType.UWORD, true, expr.position)
|
||||
else if (amount > 8) {
|
||||
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
|
||||
return TypecastExpression(BinaryExpression(msb, ">>", NumericLiteral.optimalInteger(amount - 8, expr.position), expr.position), DataType.UWORD, true, expr.position)
|
||||
}
|
||||
}
|
||||
DataType.WORD -> {
|
||||
if (amount > 16) {
|
||||
expr.right = NumericLiteralValue.optimalInteger(16, expr.right.position)
|
||||
expr.right = NumericLiteral.optimalInteger(16, expr.right.position)
|
||||
return null
|
||||
}
|
||||
}
|
||||
@ -730,7 +736,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
return null
|
||||
}
|
||||
|
||||
private fun reorderAssociativeWithConstant(expr: BinaryExpression, leftVal: NumericLiteralValue?): BinExprWithConstants {
|
||||
private fun reorderAssociativeWithConstant(expr: BinaryExpression, leftVal: NumericLiteral?): BinExprWithConstants {
|
||||
if (expr.operator in AssociativeOperators && leftVal != null) {
|
||||
// swap left and right so that right is always the constant
|
||||
val tmp = expr.left
|
||||
@ -741,6 +747,6 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
return BinExprWithConstants(expr, leftVal, expr.right.constValue(program))
|
||||
}
|
||||
|
||||
private data class BinExprWithConstants(val expr: BinaryExpression, val leftVal: NumericLiteralValue?, val rightVal: NumericLiteralValue?)
|
||||
private data class BinExprWithConstants(val expr: BinaryExpression, val leftVal: NumericLiteral?, val rightVal: NumericLiteral?)
|
||||
|
||||
}
|
@ -57,8 +57,8 @@ fun Program.optimizeStatements(errors: IErrorReporter,
|
||||
return optimizationCount
|
||||
}
|
||||
|
||||
fun Program.simplifyExpressions() : Int {
|
||||
val opti = ExpressionSimplifier(this)
|
||||
fun Program.simplifyExpressions(errors: IErrorReporter) : Int {
|
||||
val opti = ExpressionSimplifier(this, errors)
|
||||
opti.visit(this)
|
||||
return opti.applyModifications()
|
||||
}
|
||||
@ -69,7 +69,7 @@ fun Program.splitBinaryExpressions(options: CompilationOptions, compTarget: ICom
|
||||
return opti.applyModifications()
|
||||
}
|
||||
|
||||
fun getTempVarName(dt: InferredTypes.InferredType): List<String> {
|
||||
fun getTempRegisterName(dt: InferredTypes.InferredType): List<String> {
|
||||
return when {
|
||||
// TODO assume (hope) cx16.r9 isn't used for anything else during the use of this temporary variable...
|
||||
dt istype DataType.UBYTE -> listOf("cx16", "r9L")
|
||||
|
@ -1,7 +1,10 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.base.ArrayDatatypes
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.IntegerDatatypes
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
@ -17,8 +20,8 @@ class StatementOptimizer(private val program: Program,
|
||||
private val compTarget: ICompilationTarget
|
||||
) : AstWalker() {
|
||||
|
||||
override fun before(functionCallExpr: FunctionCallExpr, parent: Node): Iterable<IAstModification> {
|
||||
// if the first instruction in the called subroutine is a return statement with a simple value,
|
||||
override fun before(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
|
||||
// if the first instruction in the called subroutine is a return statement with a simple value (NOT being a parameter),
|
||||
// remove the jump altogeter and inline the returnvalue directly.
|
||||
|
||||
fun scopePrefix(variable: IdentifierReference): IdentifierReference {
|
||||
@ -37,13 +40,18 @@ class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
when(val expr = orig.addressExpression) {
|
||||
is NumericLiteralValue -> DirectMemoryRead(expr.copy(), orig.position)
|
||||
is NumericLiteral -> DirectMemoryRead(expr.copy(), orig.position)
|
||||
else -> return noModifications
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> scopePrefix(orig)
|
||||
is NumericLiteralValue -> orig.copy()
|
||||
is StringLiteralValue -> orig.copy()
|
||||
is IdentifierReference -> {
|
||||
if(orig.targetVarDecl(program)?.origin == VarDeclOrigin.SUBROUTINEPARAM)
|
||||
return noModifications
|
||||
else
|
||||
scopePrefix(orig)
|
||||
}
|
||||
is NumericLiteral -> orig.copy()
|
||||
is StringLiteral -> orig.copy()
|
||||
else -> return noModifications
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(functionCallExpr, copy, parent))
|
||||
@ -52,8 +60,6 @@ class StatementOptimizer(private val program: Program,
|
||||
return noModifications
|
||||
}
|
||||
|
||||
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
if(functionCallStatement.target.targetStatement(program) is BuiltinFunctionPlaceholder) {
|
||||
val functionName = functionCallStatement.target.nameInSource[0]
|
||||
@ -73,32 +79,38 @@ class StatementOptimizer(private val program: Program,
|
||||
arg as? IdentifierReference
|
||||
}
|
||||
if(stringVar!=null && stringVar.wasStringLiteral(program)) {
|
||||
val string = stringVar.targetVarDecl(program)?.value as? StringLiteralValue
|
||||
val string = stringVar.targetVarDecl(program)?.value as? StringLiteral
|
||||
if(string!=null) {
|
||||
val pos = functionCallStatement.position
|
||||
if (string.value.length == 1) {
|
||||
val firstCharEncoded = compTarget.encodeString(string.value, string.altEncoding)[0]
|
||||
val firstCharEncoded = compTarget.encodeString(string.value, string.encoding)[0]
|
||||
val chrout = FunctionCallStatement(
|
||||
IdentifierReference(listOf("txt", "chrout"), pos),
|
||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toDouble(), pos)),
|
||||
mutableListOf(NumericLiteral(DataType.UBYTE, firstCharEncoded.toDouble(), pos)),
|
||||
functionCallStatement.void, pos
|
||||
)
|
||||
return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent))
|
||||
val stringDecl = string.parent as VarDecl
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(functionCallStatement, chrout, parent),
|
||||
IAstModification.Remove(stringDecl, stringDecl.parent as IStatementContainer)
|
||||
)
|
||||
} else if (string.value.length == 2) {
|
||||
val firstTwoCharsEncoded = compTarget.encodeString(string.value.take(2), string.altEncoding)
|
||||
val firstTwoCharsEncoded = compTarget.encodeString(string.value.take(2), string.encoding)
|
||||
val chrout1 = FunctionCallStatement(
|
||||
IdentifierReference(listOf("txt", "chrout"), pos),
|
||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toDouble(), pos)),
|
||||
mutableListOf(NumericLiteral(DataType.UBYTE, firstTwoCharsEncoded[0].toDouble(), pos)),
|
||||
functionCallStatement.void, pos
|
||||
)
|
||||
val chrout2 = FunctionCallStatement(
|
||||
IdentifierReference(listOf("txt", "chrout"), pos),
|
||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toDouble(), pos)),
|
||||
mutableListOf(NumericLiteral(DataType.UBYTE, firstTwoCharsEncoded[1].toDouble(), pos)),
|
||||
functionCallStatement.void, pos
|
||||
)
|
||||
val stringDecl = string.parent as VarDecl
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(functionCallStatement, chrout1, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(functionCallStatement, chrout2, parent)
|
||||
IAstModification.ReplaceNode(functionCallStatement, chrout2, parent),
|
||||
IAstModification.Remove(stringDecl, stringDecl.parent as IStatementContainer)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -118,9 +130,9 @@ class StatementOptimizer(private val program: Program,
|
||||
if(functionCallStatement.target.nameInSource !in listOf(listOf("pop"), listOf("popw")) && functionCallStatement.args.size==1) {
|
||||
val arg = functionCallStatement.args[0]
|
||||
if(!arg.isSimple && arg !is TypecastExpression && arg !is IFunctionCall) {
|
||||
val name = getTempVarName(arg.inferType(program))
|
||||
val name = getTempRegisterName(arg.inferType(program))
|
||||
val tempvar = IdentifierReference(name, functionCallStatement.position)
|
||||
val assignTempvar = Assignment(AssignTarget(tempvar.copy(), null, null, functionCallStatement.position), arg, functionCallStatement.position)
|
||||
val assignTempvar = Assignment(AssignTarget(tempvar.copy(), null, null, functionCallStatement.position), arg, AssignmentOrigin.OPTIMIZER, functionCallStatement.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(functionCallStatement, assignTempvar, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(arg, tempvar, functionCallStatement)
|
||||
@ -177,13 +189,13 @@ class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
val range = forLoop.iterable as? RangeExpr
|
||||
val range = forLoop.iterable as? RangeExpression
|
||||
if(range!=null) {
|
||||
if (range.size() == 1) {
|
||||
// for loop over a (constant) range of just a single value-- optimize the loop away
|
||||
// loopvar/reg = range value , follow by block
|
||||
val scope = AnonymousScope(mutableListOf(), forLoop.position)
|
||||
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), range.from, forLoop.position))
|
||||
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), range.from, AssignmentOrigin.OPTIMIZER, forLoop.position))
|
||||
scope.statements.addAll(forLoop.body.statements)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
|
||||
}
|
||||
@ -191,14 +203,14 @@ class StatementOptimizer(private val program: Program,
|
||||
val iterable = (forLoop.iterable as? IdentifierReference)?.targetVarDecl(program)
|
||||
if(iterable!=null) {
|
||||
if(iterable.datatype==DataType.STR) {
|
||||
val sv = iterable.value as StringLiteralValue
|
||||
val sv = iterable.value as StringLiteral
|
||||
val size = sv.value.length
|
||||
if(size==1) {
|
||||
// loop over string of length 1 -> just assign the single character
|
||||
val character = compTarget.encodeString(sv.value, sv.altEncoding)[0]
|
||||
val byte = NumericLiteralValue(DataType.UBYTE, character.toDouble(), iterable.position)
|
||||
val character = compTarget.encodeString(sv.value, sv.encoding)[0]
|
||||
val byte = NumericLiteral(DataType.UBYTE, character.toDouble(), iterable.position)
|
||||
val scope = AnonymousScope(mutableListOf(), forLoop.position)
|
||||
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), byte, forLoop.position))
|
||||
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), byte, AssignmentOrigin.OPTIMIZER, forLoop.position))
|
||||
scope.statements.addAll(forLoop.body.statements)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
|
||||
}
|
||||
@ -207,12 +219,12 @@ class StatementOptimizer(private val program: Program,
|
||||
val size = iterable.arraysize!!.constIndex()
|
||||
if(size==1) {
|
||||
// loop over array of length 1 -> just assign the single value
|
||||
val av = (iterable.value as ArrayLiteralValue).value[0].constValue(program)?.number
|
||||
val av = (iterable.value as ArrayLiteral).value[0].constValue(program)?.number
|
||||
if(av!=null) {
|
||||
val scope = AnonymousScope(mutableListOf(), forLoop.position)
|
||||
scope.statements.add(Assignment(
|
||||
AssignTarget(forLoop.loopVar, null, null, forLoop.position), NumericLiteralValue.optimalInteger(av.toInt(), iterable.position),
|
||||
forLoop.position))
|
||||
AssignTarget(forLoop.loopVar, null, null, forLoop.position), NumericLiteral.optimalInteger(av.toInt(), iterable.position),
|
||||
AssignmentOrigin.OPTIMIZER, forLoop.position))
|
||||
scope.statements.addAll(forLoop.body.statements)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
|
||||
}
|
||||
@ -275,14 +287,9 @@ class StatementOptimizer(private val program: Program,
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(jump: Jump, parent: Node): Iterable<IAstModification> {
|
||||
// if the jump is to the next statement, remove the jump
|
||||
val scope = jump.parent as IStatementContainer
|
||||
val label = jump.identifier?.targetStatement(program)
|
||||
if (label != null && scope.statements.indexOf(label) == scope.statements.indexOf(jump) + 1)
|
||||
return listOf(IAstModification.Remove(jump, scope))
|
||||
return noModifications
|
||||
}
|
||||
|
||||
// NOTE: do NOT remove a jump to the next statement, because this will lead to wrong code when this occurs at the end of a subroutine
|
||||
// if we want to optimize this away, it can be done later at code generation time.
|
||||
|
||||
override fun after(gosub: GoSub, parent: Node): Iterable<IAstModification> {
|
||||
// if the next statement is return with no returnvalue, change into a regular jump if there are no parameters as well.
|
||||
@ -309,12 +316,12 @@ class StatementOptimizer(private val program: Program,
|
||||
val op1 = binExpr.operator
|
||||
val op2 = rExpr.operator
|
||||
|
||||
if(rExpr.left is NumericLiteralValue && op2 in AssociativeOperators) {
|
||||
if(rExpr.left is NumericLiteral && op2 in AssociativeOperators) {
|
||||
// associative operator, make sure the constant numeric value is second (right)
|
||||
return listOf(IAstModification.SwapOperands(rExpr))
|
||||
}
|
||||
|
||||
val rNum = (rExpr.right as? NumericLiteralValue)?.number
|
||||
val rNum = (rExpr.right as? NumericLiteral)?.number
|
||||
if(rNum!=null) {
|
||||
if (op1 == "+" || op1 == "-") {
|
||||
if (op2 == "+") {
|
||||
@ -323,7 +330,7 @@ class StatementOptimizer(private val program: Program,
|
||||
val addConstant = Assignment(
|
||||
assignment.target.copy(),
|
||||
BinaryExpression(binExpr.left.copy(), "+", rExpr.right, rExpr.position),
|
||||
assignment.position
|
||||
AssignmentOrigin.OPTIMIZER, assignment.position
|
||||
)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
|
||||
@ -334,7 +341,7 @@ class StatementOptimizer(private val program: Program,
|
||||
val subConstant = Assignment(
|
||||
assignment.target.copy(),
|
||||
BinaryExpression(binExpr.left.copy(), "-", rExpr.right, rExpr.position),
|
||||
assignment.position
|
||||
AssignmentOrigin.OPTIMIZER, assignment.position
|
||||
)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
|
||||
@ -375,7 +382,7 @@ class StatementOptimizer(private val program: Program,
|
||||
if(bexpr.right isSameAs assignment.target) {
|
||||
// X = value - X --> X = -X ; X += value (to avoid need of stack-evaluation)
|
||||
val negation = PrefixExpression("-", bexpr.right.copy(), bexpr.position)
|
||||
val addValue = Assignment(assignment.target.copy(), BinaryExpression(bexpr.right, "+", bexpr.left, bexpr.position), assignment.position)
|
||||
val addValue = Assignment(assignment.target.copy(), BinaryExpression(bexpr.right, "+", bexpr.left, bexpr.position), AssignmentOrigin.OPTIMIZER, assignment.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(bexpr, negation, assignment),
|
||||
IAstModification.InsertAfter(assignment, addValue, parent as IStatementContainer)
|
||||
@ -436,6 +443,28 @@ class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
// word = msb(word) , word=lsb(word)
|
||||
if(assignment.target.inferType(program).isWords) {
|
||||
var fcall = assignment.value as? FunctionCallExpression
|
||||
if (fcall == null)
|
||||
fcall = (assignment.value as? TypecastExpression)?.expression as? FunctionCallExpression
|
||||
if (fcall != null && (fcall.target.nameInSource == listOf("lsb") || fcall.target.nameInSource == listOf("msb"))) {
|
||||
if (fcall.args.single() isSameAs assignment.target) {
|
||||
return if (fcall.target.nameInSource == listOf("lsb")) {
|
||||
// optimize word=lsb(word) ==> word &= $00ff
|
||||
val and255 = BinaryExpression(fcall.args[0], "&", NumericLiteral(DataType.UWORD, 255.0, fcall.position), fcall.position)
|
||||
val newAssign = Assignment(assignment.target, and255, AssignmentOrigin.OPTIMIZER, fcall.position)
|
||||
listOf(IAstModification.ReplaceNode(assignment, newAssign, parent))
|
||||
} else {
|
||||
// optimize word=msb(word) ==> word >>= 8
|
||||
val shift8 = BinaryExpression(fcall.args[0], ">>", NumericLiteral(DataType.UBYTE, 8.0, fcall.position), fcall.position)
|
||||
val newAssign = Assignment(assignment.target, shift8, AssignmentOrigin.OPTIMIZER, fcall.position)
|
||||
listOf(IAstModification.ReplaceNode(assignment, newAssign, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@ -445,16 +474,10 @@ class StatementOptimizer(private val program: Program,
|
||||
val returnDt = subr.returntypes.single()
|
||||
if (returnDt in IntegerDatatypes) {
|
||||
// first assign to intermediary variable, then return that
|
||||
val returnVarName = "retval_interm_" + when(returnDt) {
|
||||
DataType.UBYTE -> "ub"
|
||||
DataType.BYTE -> "b"
|
||||
DataType.UWORD -> "uw"
|
||||
DataType.WORD -> "w"
|
||||
else -> "<undefined>"
|
||||
}
|
||||
val returnValueIntermediary = IdentifierReference(listOf("prog8_lib", returnVarName), returnStmt.position)
|
||||
val returnVarName = program.getTempVar(returnDt)
|
||||
val returnValueIntermediary = IdentifierReference(returnVarName, returnStmt.position)
|
||||
val tgt = AssignTarget(returnValueIntermediary, null, null, returnStmt.position)
|
||||
val assign = Assignment(tgt, value, returnStmt.position)
|
||||
val assign = Assignment(tgt, value, AssignmentOrigin.OPTIMIZER, returnStmt.position)
|
||||
val returnReplacement = Return(returnValueIntermediary.copy(), returnStmt.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(returnStmt, assign, parent as IStatementContainer),
|
||||
@ -464,10 +487,14 @@ class StatementOptimizer(private val program: Program,
|
||||
return null
|
||||
}
|
||||
|
||||
if(returnStmt.value is BinaryExpression) {
|
||||
val mod = returnViaIntermediaryVar(returnStmt.value!!)
|
||||
if(mod!=null)
|
||||
return mod
|
||||
// TODO decision when to use intermediary variable to calculate returnvalue seems a bit arbitrary...
|
||||
val returnvalue = returnStmt.value
|
||||
if (returnvalue!=null) {
|
||||
if (returnvalue is BinaryExpression || (returnvalue is TypecastExpression && !returnvalue.expression.isSimple)) {
|
||||
val mod = returnViaIntermediaryVar(returnvalue)
|
||||
if(mod!=null)
|
||||
return mod
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
|
@ -3,6 +3,7 @@ package prog8.optimizer
|
||||
import prog8.ast.*
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.base.defaultZero
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
@ -69,6 +70,7 @@ class UnusedCodeRemover(private val program: Program,
|
||||
if(callgraph.unused(block)) {
|
||||
if(block.statements.any{ it !is VarDecl || it.type==VarDeclType.VAR})
|
||||
errors.warn("removing unused block '${block.name}'", block.position)
|
||||
program.removeInternedStringsFromRemovedBlock(block)
|
||||
return listOf(IAstModification.Remove(block, parent as IStatementContainer))
|
||||
}
|
||||
}
|
||||
@ -92,6 +94,7 @@ class UnusedCodeRemover(private val program: Program,
|
||||
}
|
||||
if(!subroutine.definingModule.isLibrary)
|
||||
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
|
||||
program.removeInternedStringsFromRemovedSubroutine(subroutine)
|
||||
return listOf(IAstModification.Remove(subroutine, parent as IStatementContainer))
|
||||
}
|
||||
}
|
||||
@ -101,28 +104,29 @@ class UnusedCodeRemover(private val program: Program,
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
if(decl.type==VarDeclType.VAR) {
|
||||
val forceOutput = "force_output" in decl.definingBlock.options()
|
||||
if (!forceOutput && !decl.autogeneratedDontRemove && !decl.sharedWithAsm && !decl.definingBlock.isInLibrary) {
|
||||
val block = decl.definingBlock
|
||||
val forceOutput = "force_output" in block.options()
|
||||
if (!forceOutput && decl.origin==VarDeclOrigin.USERCODE && !decl.sharedWithAsm) {
|
||||
val usages = callgraph.usages(decl)
|
||||
if (usages.isEmpty()) {
|
||||
errors.warn("removing unused variable '${decl.name}'", decl.position)
|
||||
return listOf(IAstModification.Remove(decl, parent as IStatementContainer))
|
||||
} else {
|
||||
// if all usages are just an assignment to this vardecl,
|
||||
// and it is in regular RAM, then remove the var as well including all assignments
|
||||
val assignTargets = usages.mapNotNull {
|
||||
it.parent as? AssignTarget
|
||||
}.filter {
|
||||
!it.isIOAddress(compTarget.machine)
|
||||
}
|
||||
if(assignTargets.size==usages.size) {
|
||||
// if(!decl.definingModule.isLibrary)
|
||||
errors.warn("removing unused variable '${decl.name}'", decl.position)
|
||||
val assignmentsToRemove = assignTargets.map { it.parent to it.parent.parent as IStatementContainer}.toSet()
|
||||
return assignmentsToRemove.map {
|
||||
IAstModification.Remove(it.first, it.second)
|
||||
} + listOf(
|
||||
IAstModification.Remove(decl, parent as IStatementContainer)
|
||||
)
|
||||
return listOf(IAstModification.Remove(decl, parent as IStatementContainer))
|
||||
}
|
||||
else {
|
||||
if(usages.size==1) {
|
||||
val singleUse = usages[0].parent
|
||||
if(singleUse is AssignTarget) {
|
||||
val assignment = singleUse.parent as Assignment
|
||||
if(assignment.origin==AssignmentOrigin.VARINIT) {
|
||||
if(!decl.definingModule.isLibrary)
|
||||
errors.warn("removing unused variable '${decl.name}'", decl.position)
|
||||
return listOf(
|
||||
IAstModification.Remove(decl, parent as IStatementContainer),
|
||||
IAstModification.Remove(assignment, assignment.parent as IStatementContainer)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -132,11 +136,11 @@ class UnusedCodeRemover(private val program: Program,
|
||||
}
|
||||
|
||||
private fun deduplicateAssignments(statements: List<Statement>, scope: IStatementContainer): List<IAstModification> {
|
||||
// removes 'duplicate' assignments that assign the same target directly after another
|
||||
// removes 'duplicate' assignments that assign the same target directly after another, unless it is a function call
|
||||
val linesToRemove = mutableListOf<Assignment>()
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
|
||||
fun substituteZeroInBinexpr(expr: BinaryExpression, zero: NumericLiteralValue, assign1: Assignment, assign2: Assignment) {
|
||||
fun substituteZeroInBinexpr(expr: BinaryExpression, zero: NumericLiteral, assign1: Assignment, assign2: Assignment) {
|
||||
if(expr.left isSameAs assign2.target) {
|
||||
// X = X <oper> Right
|
||||
linesToRemove.add(assign1)
|
||||
@ -187,7 +191,7 @@ class UnusedCodeRemover(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
fun substituteZeroInPrefixexpr(expr: PrefixExpression, zero: NumericLiteralValue, assign1: Assignment, assign2: Assignment) {
|
||||
fun substituteZeroInPrefixexpr(expr: PrefixExpression, zero: NumericLiteral, assign1: Assignment, assign2: Assignment) {
|
||||
if(expr.expression isSameAs assign2.target) {
|
||||
linesToRemove.add(assign1)
|
||||
modifications.add(IAstModification.ReplaceNode(
|
||||
@ -196,7 +200,7 @@ class UnusedCodeRemover(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
fun substituteZeroInTypecast(expr: TypecastExpression, zero: NumericLiteralValue, assign1: Assignment, assign2: Assignment) {
|
||||
fun substituteZeroInTypecast(expr: TypecastExpression, zero: NumericLiteral, assign1: Assignment, assign2: Assignment) {
|
||||
if(expr.expression isSameAs assign2.target) {
|
||||
linesToRemove.add(assign1)
|
||||
modifications.add(IAstModification.ReplaceNode(
|
||||
@ -219,7 +223,7 @@ class UnusedCodeRemover(private val program: Program,
|
||||
val cvalue1 = assign1.value.constValue(program)
|
||||
if(cvalue1!=null && cvalue1.number==0.0 && assign2.target.isSameAs(assign1.target, program) && assign2.isAugmentable) {
|
||||
val value2 = assign2.value
|
||||
val zero = VarDecl.defaultZero(value2.inferType(program).getOr(DataType.UNDEFINED), value2.position)
|
||||
val zero = defaultZero(value2.inferType(program).getOr(DataType.UNDEFINED), value2.position)
|
||||
when(value2) {
|
||||
is BinaryExpression -> substituteZeroInBinexpr(value2, zero, assign1, assign2)
|
||||
is PrefixExpression -> substituteZeroInPrefixexpr(value2, zero, assign1, assign2)
|
||||
@ -234,8 +238,11 @@ class UnusedCodeRemover(private val program: Program,
|
||||
is PrefixExpression,
|
||||
is BinaryExpression,
|
||||
is TypecastExpression,
|
||||
is FunctionCallExpr -> { /* don't remove */ }
|
||||
else -> linesToRemove.add(assign1)
|
||||
is FunctionCallExpression -> { /* don't remove */ }
|
||||
else -> {
|
||||
if(assign1.value !is IFunctionCall)
|
||||
linesToRemove.add(assign1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,9 @@ dependencies {
|
||||
implementation project(':compilerInterfaces')
|
||||
implementation project(':codeOptimizers')
|
||||
implementation project(':compilerAst')
|
||||
implementation project(':codeGeneration')
|
||||
implementation project(':codeGenTargets')
|
||||
implementation project(':codeGenCpu6502')
|
||||
implementation project(':codeGenExperimental6502')
|
||||
implementation 'org.antlr:antlr4-runtime:4.9.2'
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
|
@ -16,9 +16,11 @@
|
||||
<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" />
|
||||
<orderEntry type="module" module-name="codeGenTargets" />
|
||||
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
|
||||
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||
<orderEntry type="library" name="antlr.antlr4" level="project" />
|
||||
<orderEntry type="module" module-name="codeGenCpu6502" />
|
||||
<orderEntry type="module" module-name="codeGenExperimental6502" />
|
||||
</component>
|
||||
</module>
|
@ -10,8 +10,6 @@ floats {
|
||||
const float PI = 3.141592653589793
|
||||
const float TWOPI = 6.283185307179586
|
||||
|
||||
float tempvar_swap_float ; used for some swap() operations
|
||||
|
||||
|
||||
; ---- ROM float functions ----
|
||||
|
||||
|
@ -555,6 +555,7 @@ sys {
|
||||
}
|
||||
|
||||
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
|
||||
; note: can't be inlined because is called from asm as well
|
||||
%asm {{
|
||||
ldx cx16.r0
|
||||
stx P8ZP_SCRATCH_W1 ; source in ZP
|
||||
|
@ -248,15 +248,25 @@ pop_float_fac1 .proc
|
||||
.pend
|
||||
|
||||
copy_float .proc
|
||||
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
|
||||
; -- copies the 5 bytes of the mflt value pointed to by P8ZP_SCRATCH_W1,
|
||||
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
|
||||
sta _target+1
|
||||
sty _target+2
|
||||
ldy #4
|
||||
_loop lda (P8ZP_SCRATCH_W1),y
|
||||
_target sta $ffff,y ; modified
|
||||
dey
|
||||
bpl _loop
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
rts
|
||||
.pend
|
||||
|
||||
@ -678,3 +688,54 @@ set_array_float .proc
|
||||
.pend
|
||||
|
||||
|
||||
equal_zero .proc
|
||||
jsr floats.pop_float_fac1
|
||||
jsr floats.SIGN
|
||||
beq _true
|
||||
bne _false
|
||||
_true lda #1
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
_false lda #0
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
notequal_zero .proc
|
||||
jsr floats.pop_float_fac1
|
||||
jsr floats.SIGN
|
||||
bne equal_zero._true
|
||||
beq equal_zero._false
|
||||
.pend
|
||||
|
||||
greater_zero .proc
|
||||
jsr floats.pop_float_fac1
|
||||
jsr floats.SIGN
|
||||
beq equal_zero._false
|
||||
bpl equal_zero._true
|
||||
jmp equal_zero._false
|
||||
.pend
|
||||
|
||||
less_zero .proc
|
||||
jsr floats.pop_float_fac1
|
||||
jsr floats.SIGN
|
||||
bmi equal_zero._true
|
||||
jmp equal_zero._false
|
||||
.pend
|
||||
|
||||
greaterequal_zero .proc
|
||||
jsr floats.pop_float_fac1
|
||||
jsr floats.SIGN
|
||||
bpl equal_zero._true
|
||||
jmp equal_zero._false
|
||||
.pend
|
||||
|
||||
lessequal_zero .proc
|
||||
jsr floats.pop_float_fac1
|
||||
jsr floats.SIGN
|
||||
beq equal_zero._true
|
||||
bmi equal_zero._true
|
||||
jmp equal_zero._false
|
||||
.pend
|
||||
|
@ -163,7 +163,7 @@ graphics {
|
||||
lda addr+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
ldy separate_pixels
|
||||
lda _filled_right,y
|
||||
lda hline_filled_right,y
|
||||
eor #255
|
||||
ldy #0
|
||||
ora (P8ZP_SCRATCH_W1),y
|
||||
@ -207,18 +207,18 @@ _modified stx $ffff ; modified
|
||||
_zero ldx P8ZP_SCRATCH_REG
|
||||
|
||||
ldy separate_pixels
|
||||
beq _zero2
|
||||
beq hline_zero2
|
||||
lda _modified+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda _modified+2
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda _filled_right,y
|
||||
lda hline_filled_right,y
|
||||
ldy #0
|
||||
ora (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
jmp _zero2
|
||||
_filled_right .byte 0, %10000000, %11000000, %11100000, %11110000, %11111000, %11111100, %11111110
|
||||
_zero2
|
||||
jmp hline_zero2
|
||||
hline_filled_right .byte 0, %10000000, %11000000, %11100000, %11110000, %11111000, %11111100, %11111110
|
||||
hline_zero2
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
@ -520,6 +520,7 @@ sys {
|
||||
}
|
||||
|
||||
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
|
||||
; note: can't be inlined because is called from asm as well
|
||||
%asm {{
|
||||
ldx cx16.r0
|
||||
stx P8ZP_SCRATCH_W1 ; source in ZP
|
||||
|
@ -7,7 +7,7 @@ conv {
|
||||
|
||||
; ----- number conversions to decimal strings ----
|
||||
|
||||
str string_out = "????????????????" ; result buffer for the string conversion routines
|
||||
str @shared string_out = "????????????????" ; result buffer for the string conversion routines
|
||||
|
||||
asmsub str_ub0 (ubyte value @ A) clobbers(A,Y) {
|
||||
; ---- convert the ubyte in A in decimal string form, with left padding 0s (3 positions total)
|
||||
|
@ -34,4 +34,42 @@ cx16diskio {
|
||||
sub load_size(ubyte startbank, uword startaddress, uword endaddress) -> uword {
|
||||
return $2000 * (cx16.getrambank() - startbank) + endaddress - startaddress
|
||||
}
|
||||
|
||||
asmsub vload(str name @R0, ubyte device @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
|
||||
; -- like the basic command VLOAD "filename",device,bank,address
|
||||
; loads a file into Vera's video memory in the given bank:address, returns success in A
|
||||
%asm {{
|
||||
; -- load a file into video ram
|
||||
phx
|
||||
pha
|
||||
tya
|
||||
tax
|
||||
lda #1
|
||||
ldy #0
|
||||
jsr c64.SETLFS
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jsr prog8_lib.strlen
|
||||
tya
|
||||
ldx cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jsr c64.SETNAM
|
||||
pla
|
||||
clc
|
||||
adc #2
|
||||
ldx cx16.r1
|
||||
ldy cx16.r1+1
|
||||
stz P8ZP_SCRATCH_B1
|
||||
jsr c64.LOAD
|
||||
bcs +
|
||||
inc P8ZP_SCRATCH_B1
|
||||
+ jsr c64.CLRCHN
|
||||
lda #1
|
||||
jsr c64.CLOSE
|
||||
plx
|
||||
lda P8ZP_SCRATCH_B1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -13,8 +13,6 @@ floats {
|
||||
const float PI = 3.141592653589793
|
||||
const float TWOPI = 6.283185307179586
|
||||
|
||||
float tempvar_swap_float ; used for some swap() operations
|
||||
|
||||
|
||||
; ---- ROM float functions ----
|
||||
|
||||
|
@ -369,7 +369,7 @@ _done
|
||||
set_both_strides(13) ; 160 increment = 1 line in 640 px 4c mode
|
||||
color &= 3
|
||||
color <<= gfx2.plot.shift4c[lsb(x) & 3]
|
||||
ubyte mask = gfx2.plot.mask4c[lsb(x) & 3]
|
||||
ubyte @shared mask = gfx2.plot.mask4c[lsb(x) & 3]
|
||||
repeat height {
|
||||
%asm {{
|
||||
lda cx16.VERA_DATA0
|
||||
@ -545,9 +545,9 @@ _done
|
||||
}
|
||||
|
||||
sub plot(uword @zp x, uword y, ubyte color) {
|
||||
ubyte[8] bits = [128, 64, 32, 16, 8, 4, 2, 1]
|
||||
ubyte[4] mask4c = [%00111111, %11001111, %11110011, %11111100]
|
||||
ubyte[4] shift4c = [6,4,2,0]
|
||||
ubyte[8] @shared bits = [128, 64, 32, 16, 8, 4, 2, 1]
|
||||
ubyte[4] @shared mask4c = [%00111111, %11001111, %11110011, %11111100]
|
||||
ubyte[4] @shared shift4c = [6,4,2,0]
|
||||
|
||||
when active_mode {
|
||||
1 -> {
|
||||
|
@ -307,13 +307,25 @@ romsub $ff7d = primm()
|
||||
romsub $ff44 = macptr() clobbers(A,X,Y)
|
||||
romsub $ff47 = enter_basic(ubyte cold_or_warm @Pc) clobbers(A,X,Y)
|
||||
romsub $ff68 = mouse_config(ubyte shape @A, ubyte scale @X) clobbers (A, X, Y)
|
||||
romsub $ff6b = mouse_get(ubyte zpdataptr @X) clobbers(A)
|
||||
romsub $ff6b = mouse_get(ubyte zpdataptr @X) -> ubyte @A
|
||||
romsub $ff71 = mouse_scan() clobbers(A, X, Y)
|
||||
romsub $ff53 = joystick_scan() clobbers(A, X, Y)
|
||||
romsub $ff56 = joystick_get(ubyte joynr @A) -> ubyte @A, ubyte @X, ubyte @Y
|
||||
romsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, ubyte jiffies @R3) clobbers(A, X, Y)
|
||||
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, ubyte @R3 ; result registers see clock_set_date_time()
|
||||
|
||||
asmsub mouse_pos() -> ubyte @A {
|
||||
; -- short wrapper around mouse_get() kernal routine:
|
||||
; -- gets the position of the mouse cursor in cx16.r0 and cx16.r1 (x/y coordinate), returns mouse button status.
|
||||
%asm {{
|
||||
phx
|
||||
ldx #cx16.r0
|
||||
jsr cx16.mouse_get
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
; It's not documented what registers are clobbered, so we assume the worst for all following kernal routines...:
|
||||
|
||||
@ -521,45 +533,6 @@ asmsub vpoke_xor(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers (A)
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub vload(str name @R0, ubyte device @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
|
||||
; -- like the basic command VLOAD "filename",device,bank,address
|
||||
; loads a file into video memory in the given bank:address, returns success in A
|
||||
; !! NOTE !! the V38 ROMs contain a bug in the LOAD code that makes the load address not work correctly,
|
||||
; it works fine when loading from local filesystem
|
||||
%asm {{
|
||||
; -- load a file into video ram
|
||||
phx
|
||||
pha
|
||||
tya
|
||||
tax
|
||||
lda #1
|
||||
ldy #0
|
||||
jsr c64.SETLFS
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jsr prog8_lib.strlen
|
||||
tya
|
||||
ldx cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jsr c64.SETNAM
|
||||
pla
|
||||
clc
|
||||
adc #2
|
||||
ldx cx16.r1
|
||||
ldy cx16.r1+1
|
||||
stz P8ZP_SCRATCH_B1
|
||||
jsr c64.LOAD
|
||||
bcs +
|
||||
inc P8ZP_SCRATCH_B1
|
||||
+ jsr c64.CLRCHN
|
||||
lda #1
|
||||
jsr c64.CLOSE
|
||||
plx
|
||||
lda P8ZP_SCRATCH_B1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub joystick_get2(ubyte joynr @A) clobbers(Y) -> uword @AX {
|
||||
; convenience routine to get the joystick state without requiring inline assembly that deals with the multiple return values.
|
||||
; Also disables interrupts to avoid the IRQ race condition mentioned here: https://github.com/commanderx16/x16-rom/issues/203
|
||||
@ -848,11 +821,12 @@ sys {
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
|
||||
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
|
||||
; note: can't be inlined because is called from asm as well
|
||||
%asm {{
|
||||
sta cx16.r2
|
||||
sty cx16.r2+1
|
||||
jsr cx16.memory_copy
|
||||
jmp cx16.memory_copy
|
||||
}}
|
||||
}
|
||||
|
||||
|
@ -169,13 +169,26 @@ sub color2 (ubyte txtcol, ubyte bgcol) {
|
||||
}
|
||||
|
||||
sub lowercase() {
|
||||
cx16.screen_set_charset(3, 0) ; lowercase charset
|
||||
c64.CHROUT($0e)
|
||||
; this is not 100% compatible: cx16.screen_set_charset(3, 0) ; lowercase petscii charset
|
||||
}
|
||||
|
||||
sub uppercase() {
|
||||
cx16.screen_set_charset(2, 0) ; uppercase charset
|
||||
c64.CHROUT($8e)
|
||||
; this is not 100% compatible: cx16.screen_set_charset(2, 0) ; uppercase petscii charset
|
||||
}
|
||||
|
||||
sub iso() {
|
||||
c64.CHROUT($0f)
|
||||
; This doesn't enable it completely: cx16.screen_set_charset(1, 0) ; iso charset
|
||||
}
|
||||
|
||||
sub iso_off() {
|
||||
; -- you have to call this first when switching back from iso charset to regular charset.
|
||||
c64.CHROUT($8f)
|
||||
}
|
||||
|
||||
|
||||
asmsub scroll_left() clobbers(A, Y) {
|
||||
; ---- scroll the whole screen 1 character to the left
|
||||
; contents of the rightmost column are unchanged, you should clear/refill this yourself
|
||||
|
@ -76,7 +76,8 @@ io_error:
|
||||
|
||||
sub list_files(ubyte drivenumber, uword pattern_ptr, uword name_ptrs, ubyte max_names) -> ubyte {
|
||||
; -- fill the array 'name_ptrs' with (pointers to) the names of the files requested.
|
||||
uword names_buffer = memory("filenames", 512)
|
||||
const uword names_buf_size = 800
|
||||
uword names_buffer = memory("filenames", names_buf_size, 0)
|
||||
uword buffer_start = names_buffer
|
||||
ubyte files_found = 0
|
||||
if lf_start_list(drivenumber, pattern_ptr) {
|
||||
@ -87,7 +88,7 @@ io_error:
|
||||
name_ptrs++
|
||||
names_buffer += string.copy(diskio.list_filename, names_buffer) + 1
|
||||
files_found++
|
||||
if names_buffer - buffer_start > 512-18
|
||||
if names_buffer - buffer_start > names_buf_size-18
|
||||
break
|
||||
if files_found == max_names
|
||||
break
|
||||
@ -242,18 +243,18 @@ close_end:
|
||||
void c64.CHKIN(11) ; use #11 as input channel again
|
||||
%asm {{
|
||||
lda bufferpointer
|
||||
sta _in_buffer+1
|
||||
sta m_in_buffer+1
|
||||
lda bufferpointer+1
|
||||
sta _in_buffer+2
|
||||
sta m_in_buffer+2
|
||||
}}
|
||||
repeat num_bytes {
|
||||
%asm {{
|
||||
jsr c64.CHRIN
|
||||
sta cx16.r5
|
||||
_in_buffer sta $ffff
|
||||
inc _in_buffer+1
|
||||
m_in_buffer sta $ffff
|
||||
inc m_in_buffer+1
|
||||
bne +
|
||||
inc _in_buffer+2
|
||||
inc m_in_buffer+2
|
||||
+ inc list_blocks
|
||||
bne +
|
||||
inc list_blocks+1
|
||||
@ -406,7 +407,7 @@ io_error:
|
||||
sub save(ubyte drivenumber, uword filenameptr, uword address, uword size) -> ubyte {
|
||||
c64.SETNAM(string.length(filenameptr), filenameptr)
|
||||
c64.SETLFS(1, drivenumber, 0)
|
||||
uword end_address = address + size
|
||||
uword @shared end_address = address + size
|
||||
first_byte = 0 ; result var reuse
|
||||
|
||||
%asm {{
|
||||
|
@ -4,6 +4,8 @@
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
|
||||
|
||||
orig_stackpointer .byte 0 ; stores the Stack pointer register at program start
|
||||
|
||||
read_byte_from_address_on_stack .proc
|
||||
; -- read the byte from the memory address on the top of the stack, return in A (stack remains unchanged)
|
||||
lda P8ESTACK_LO+1,x
|
||||
@ -683,8 +685,106 @@ shiftright_b .proc
|
||||
.pend
|
||||
|
||||
|
||||
orig_stackpointer .byte 0 ; stores the Stack pointer register at program start
|
||||
equalzero_b .proc
|
||||
lda P8ESTACK_LO+1,x
|
||||
beq _true
|
||||
bne _false
|
||||
_true lda #1
|
||||
sta P8ESTACK_LO+1,x
|
||||
rts
|
||||
_false lda #0
|
||||
sta P8ESTACK_LO+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
equalzero_w .proc
|
||||
lda P8ESTACK_LO+1,x
|
||||
ora P8ESTACK_HI+1,x
|
||||
beq equalzero_b._true
|
||||
bne equalzero_b._false
|
||||
.pend
|
||||
|
||||
notequalzero_b .proc
|
||||
lda P8ESTACK_LO+1,x
|
||||
beq equalzero_b._false
|
||||
bne equalzero_b._true
|
||||
.pend
|
||||
|
||||
notequalzero_w .proc
|
||||
lda P8ESTACK_LO+1,x
|
||||
ora P8ESTACK_HI+1,x
|
||||
beq equalzero_b._false
|
||||
bne equalzero_b._true
|
||||
.pend
|
||||
|
||||
lesszero_b .proc
|
||||
lda P8ESTACK_LO+1,x
|
||||
bmi equalzero_b._true
|
||||
jmp equalzero_b._false
|
||||
.pend
|
||||
|
||||
lesszero_w .proc
|
||||
lda P8ESTACK_HI+1,x
|
||||
bmi equalzero_b._true
|
||||
jmp equalzero_b._false
|
||||
.pend
|
||||
|
||||
greaterzero_ub .proc
|
||||
lda P8ESTACK_LO+1,x
|
||||
bne equalzero_b._true
|
||||
beq equalzero_b._false
|
||||
.pend
|
||||
|
||||
greaterzero_sb .proc
|
||||
lda P8ESTACK_LO+1,x
|
||||
beq equalzero_b._false
|
||||
bpl equalzero_b._true
|
||||
bmi equalzero_b._false
|
||||
.pend
|
||||
|
||||
greaterzero_uw .proc
|
||||
lda P8ESTACK_LO+1,x
|
||||
ora P8ESTACK_HI+1,x
|
||||
bne equalzero_b._true
|
||||
beq equalzero_b._false
|
||||
.pend
|
||||
|
||||
greaterzero_sw .proc
|
||||
lda P8ESTACK_HI+1,x
|
||||
bmi equalzero_b._false
|
||||
ora P8ESTACK_LO+1,x
|
||||
beq equalzero_b._false
|
||||
bne equalzero_b._true
|
||||
.pend
|
||||
|
||||
lessequalzero_sb .proc
|
||||
lda P8ESTACK_LO+1,x
|
||||
bmi equalzero_b._true
|
||||
beq equalzero_b._true
|
||||
bne equalzero_b._false
|
||||
.pend
|
||||
|
||||
lessequalzero_sw .proc
|
||||
lda P8ESTACK_HI+1,x
|
||||
bmi equalzero_b._true
|
||||
ora P8ESTACK_LO+1,x
|
||||
beq equalzero_b._true
|
||||
bne equalzero_b._false
|
||||
.pend
|
||||
|
||||
greaterequalzero_sb .proc
|
||||
lda P8ESTACK_LO+1,x
|
||||
bpl equalzero_b._true
|
||||
bmi equalzero_b._false
|
||||
.pend
|
||||
|
||||
greaterequalzero_sw .proc
|
||||
lda P8ESTACK_HI+1,x
|
||||
bmi equalzero_b._false
|
||||
ora P8ESTACK_LO+1,x
|
||||
beq equalzero_b._true
|
||||
bne equalzero_b._false
|
||||
.pend
|
||||
|
||||
|
||||
memcopy16_up .proc
|
||||
@ -1021,17 +1121,15 @@ strcmp_mem .proc
|
||||
; Returns -1,0,1 in A, depeding on the ordering. Clobbers Y.
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
_loop ldy #0
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
ldy #0
|
||||
_loop lda (P8ZP_SCRATCH_W1),y
|
||||
bne +
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
bne _return_minusone
|
||||
beq _return
|
||||
+ lda (P8ZP_SCRATCH_W2),y
|
||||
sec
|
||||
sbc (P8ZP_SCRATCH_W1),y
|
||||
bmi _return_one
|
||||
bne _return_minusone
|
||||
+ cmp (P8ZP_SCRATCH_W2),y
|
||||
bcc _return_minusone
|
||||
bne _return_one
|
||||
inc P8ZP_SCRATCH_W1
|
||||
bne +
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
@ -1058,20 +1156,6 @@ sign_extend_stack_byte .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
sign_extend_AY_byte .proc
|
||||
; -- sign extend the (signed) byte in AY to full 16 bits
|
||||
pha
|
||||
and #$80
|
||||
beq +
|
||||
ldy #$ff
|
||||
pla
|
||||
rts
|
||||
+ ldy #0
|
||||
pla
|
||||
rts
|
||||
.pend
|
||||
|
||||
strlen .proc
|
||||
; -- returns the number of bytes in the string in AY, in Y.
|
||||
sta P8ZP_SCRATCH_W1
|
||||
|
@ -6,24 +6,6 @@ prog8_lib {
|
||||
%asminclude "library:prog8_lib.asm"
|
||||
%asminclude "library:prog8_funcs.asm"
|
||||
|
||||
; to store intermediary expression results for return values:
|
||||
; NOTE: these variables are used in the StatementReorderer and StatementOptimizer
|
||||
uword @zp retval_interm_uw
|
||||
word @zp retval_interm_w
|
||||
ubyte @zp retval_interm_ub
|
||||
byte @zp retval_interm_b
|
||||
word retval_interm_w2
|
||||
byte retval_interm_b2
|
||||
|
||||
; prog8 "hooks" to be able to access the temporary scratch variables
|
||||
; YOU SHOULD NOT USE THESE IN USER CODE - THESE ARE MEANT FOR INTERNAL COMPILER USE
|
||||
; NOTE: the assembly code generator will match these names and not generate
|
||||
; new variables/memdefs for them, rather, they'll point to the scratch variables directly.
|
||||
&ubyte P8ZP_SCRATCH_REG = $ff
|
||||
&byte P8ZP_SCRATCH_B1 = $ff
|
||||
&uword P8ZP_SCRATCH_W1 = $ff
|
||||
&word P8ZP_SCRATCH_W2 = $ff
|
||||
|
||||
|
||||
asmsub pattern_match(str string @AY, str pattern @R0) clobbers(Y) -> ubyte @A {
|
||||
%asm {{
|
||||
|
@ -130,11 +130,11 @@ _startloop dey
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub find(uword string @R0, ubyte character @A) -> uword @AY {
|
||||
asmsub find(uword string @R0, ubyte character @A) -> ubyte @A, ubyte @Pc {
|
||||
; Locates the first position of the given character in the string,
|
||||
; returns the string starting with this character or $0000 if the character is not found.
|
||||
; returns Carry set if found + index in A, or Carry clear if not found.
|
||||
%asm {{
|
||||
; need to copy the the cx16 virtual registers to zeropage to be compatible with C64...
|
||||
; need to copy the the cx16 virtual registers to zeropage to make this run on C64...
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
@ -147,18 +147,11 @@ _startloop dey
|
||||
beq _found
|
||||
iny
|
||||
bne -
|
||||
_notfound lda #0
|
||||
ldy #0
|
||||
_notfound clc
|
||||
rts
|
||||
_found sty P8ZP_SCRATCH_B1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
lda P8ZP_SCRATCH_W1
|
||||
clc
|
||||
adc P8ZP_SCRATCH_B1
|
||||
bcc +
|
||||
iny
|
||||
+ rts
|
||||
|
||||
_found tya
|
||||
sec
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
@ -1 +1 @@
|
||||
7.6
|
||||
7.8
|
||||
|
@ -3,11 +3,11 @@ package prog8
|
||||
import kotlinx.cli.*
|
||||
import prog8.ast.base.AstException
|
||||
import prog8.codegen.target.C128Target
|
||||
import prog8.codegen.target.C64Target
|
||||
import prog8.codegen.target.Cx16Target
|
||||
import prog8.compiler.CompilationResult
|
||||
import prog8.compiler.CompilerArguments
|
||||
import prog8.compiler.compileProgram
|
||||
import prog8.codegen.target.C64Target
|
||||
import prog8.codegen.target.Cx16Target
|
||||
import java.io.File
|
||||
import java.nio.file.FileSystems
|
||||
import java.nio.file.Path
|
||||
@ -43,7 +43,8 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
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 asmListfile by cli.option(ArgType.Boolean, fullName = "asmlist", description = "make the assembler produce a listing file as well")
|
||||
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler (one of '${C64Target.name}', '${C128Target.name}', '${Cx16Target.name}')").default(C64Target.name)
|
||||
val experimentalCodegen by cli.option(ArgType.Boolean, fullName = "expericodegen", description = "use experimental codegen")
|
||||
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler (one of '${C64Target.NAME}', '${C128Target.NAME}', '${Cx16Target.NAME}')").default(C64Target.NAME)
|
||||
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)
|
||||
|
||||
@ -70,7 +71,7 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
if(srcdirs.firstOrNull()!=".")
|
||||
srcdirs.add(0, ".")
|
||||
|
||||
if (compilationTarget !in setOf(C64Target.name, C128Target.name, Cx16Target.name)) {
|
||||
if (compilationTarget !in setOf(C64Target.NAME, C128Target.NAME, Cx16Target.NAME)) {
|
||||
System.err.println("Invalid compilation target: $compilationTarget")
|
||||
return false
|
||||
}
|
||||
@ -93,6 +94,7 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
slowCodegenWarnings == true,
|
||||
quietAssembler == true,
|
||||
asmListfile == true,
|
||||
experimentalCodegen == true,
|
||||
compilationTarget,
|
||||
srcdirs,
|
||||
outputPath
|
||||
@ -143,6 +145,7 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
slowCodegenWarnings == true,
|
||||
quietAssembler == true,
|
||||
asmListfile == true,
|
||||
experimentalCodegen == true,
|
||||
compilationTarget,
|
||||
srcdirs,
|
||||
outputPath
|
||||
|
@ -1,25 +1,28 @@
|
||||
package prog8.compiler
|
||||
|
||||
import com.github.michaelbull.result.*
|
||||
import com.github.michaelbull.result.onFailure
|
||||
import prog8.ast.AstToSourceTextConverter
|
||||
import prog8.ast.IBuiltinFunctions
|
||||
import prog8.ast.IStatementContainer
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.AstException
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.statements.Directive
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.codegen.target.C128Target
|
||||
import prog8.compiler.astprocessing.*
|
||||
import prog8.codegen.target.C64Target
|
||||
import prog8.codegen.target.Cx16Target
|
||||
import prog8.codegen.target.cpu6502.codegen.AsmGen
|
||||
import prog8.compiler.astprocessing.*
|
||||
import prog8.compilerinterface.*
|
||||
import prog8.optimizer.*
|
||||
import prog8.parser.ParseError
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.nameWithoutExtension
|
||||
import kotlin.math.round
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
|
||||
@ -37,6 +40,7 @@ class CompilerArguments(val filepath: Path,
|
||||
val slowCodegenWarnings: Boolean,
|
||||
val quietAssembler: Boolean,
|
||||
val asmListfile: Boolean,
|
||||
val experimentalCodegen: Boolean,
|
||||
val compilationTarget: String,
|
||||
val sourceDirs: List<String> = emptyList(),
|
||||
val outputDir: Path = Path(""),
|
||||
@ -52,9 +56,9 @@ fun compileProgram(args: CompilerArguments): CompilationResult {
|
||||
|
||||
val compTarget =
|
||||
when(args.compilationTarget) {
|
||||
C64Target.name -> C64Target
|
||||
C128Target.name -> C128Target
|
||||
Cx16Target.name -> Cx16Target
|
||||
C64Target.NAME -> C64Target()
|
||||
C128Target.NAME -> C128Target()
|
||||
Cx16Target.NAME -> Cx16Target()
|
||||
else -> throw IllegalArgumentException("invalid compilation target")
|
||||
}
|
||||
|
||||
@ -62,6 +66,9 @@ fun compileProgram(args: CompilerArguments): CompilationResult {
|
||||
val totalTime = measureTimeMillis {
|
||||
// import main module and everything it needs
|
||||
val (programresult, compilationOptions, imported) = parseImports(args.filepath, args.errors, compTarget, args.sourceDirs)
|
||||
print("Parsed ${args.filepath}")
|
||||
ModuleImporter.ansiEraseRestOfLine(true)
|
||||
|
||||
with(compilationOptions) {
|
||||
slowCodegenWarnings = args.slowCodegenWarnings
|
||||
optimize = args.optimize
|
||||
@ -69,6 +76,8 @@ fun compileProgram(args: CompilerArguments): CompilationResult {
|
||||
dontReinitGlobals = args.dontReinitGlobals
|
||||
asmQuiet = args.quietAssembler
|
||||
asmListfile = args.asmListfile
|
||||
experimentalCodegen = args.experimentalCodegen
|
||||
outputDir = args.outputDir.normalize()
|
||||
}
|
||||
program = programresult
|
||||
importedFiles = imported
|
||||
@ -91,7 +100,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult {
|
||||
// printProgram(program)
|
||||
|
||||
if (args.writeAssembly) {
|
||||
when (val result = writeAssembly(program, args.errors, args.outputDir, compilationOptions)) {
|
||||
when (val result = writeAssembly(program, args.errors, compilationOptions)) {
|
||||
is WriteAssemblyResult.Ok -> programName = result.filename
|
||||
is WriteAssemblyResult.Fail -> {
|
||||
System.err.println(result.error)
|
||||
@ -102,34 +111,35 @@ fun compileProgram(args: CompilerArguments): CompilationResult {
|
||||
}
|
||||
System.out.flush()
|
||||
System.err.flush()
|
||||
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
|
||||
val seconds = totalTime/1000.0
|
||||
println("\nTotal compilation+assemble time: ${round(seconds*100.0)/100.0} sec.")
|
||||
return CompilationResult(true, program, programName, compTarget, importedFiles)
|
||||
} catch (px: ParseError) {
|
||||
System.err.print("\u001b[91m") // bright red
|
||||
System.err.print("\n\u001b[91m") // bright red
|
||||
System.err.println("${px.position.toClickableStr()} parse error: ${px.message}".trim())
|
||||
System.err.print("\u001b[0m") // reset
|
||||
} catch (ac: AbortCompilation) {
|
||||
if(!ac.message.isNullOrEmpty()) {
|
||||
System.err.print("\u001b[91m") // bright red
|
||||
System.err.print("\n\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("\n\u001b[91m") // bright red
|
||||
System.err.println("File not found: ${nsf.message}")
|
||||
System.err.print("\u001b[0m") // reset
|
||||
} catch (ax: AstException) {
|
||||
System.err.print("\u001b[91m") // bright red
|
||||
System.err.print("\n\u001b[91m") // bright red
|
||||
System.err.println(ax.toString())
|
||||
System.err.print("\u001b[0m") // reset
|
||||
} catch (x: Exception) {
|
||||
print("\u001b[91m") // bright red
|
||||
print("\n\u001b[91m") // bright red
|
||||
println("\n* internal error *")
|
||||
print("\u001b[0m") // reset
|
||||
System.out.flush()
|
||||
throw x
|
||||
} catch (x: NotImplementedError) {
|
||||
print("\u001b[91m") // bright red
|
||||
print("\n\u001b[91m") // bright red
|
||||
println("\n* internal error: missing feature/code *")
|
||||
print("\u001b[0m") // reset
|
||||
System.out.flush()
|
||||
@ -146,7 +156,7 @@ private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuilt
|
||||
override val names = functions.keys
|
||||
override val purefunctionNames = functions.filter { it.value.pure }.map { it.key }.toSet()
|
||||
|
||||
override fun constValue(name: String, args: List<Expression>, position: Position): NumericLiteralValue? {
|
||||
override fun constValue(name: String, args: List<Expression>, position: Position): NumericLiteral? {
|
||||
val func = BuiltinFunctions[name]
|
||||
if(func!=null) {
|
||||
val exprfunc = func.constExpressionFunc
|
||||
@ -174,7 +184,7 @@ fun parseImports(filepath: Path,
|
||||
errors: IErrorReporter,
|
||||
compTarget: ICompilationTarget,
|
||||
sourceDirs: List<String>): Triple<Program, CompilationOptions, List<Path>> {
|
||||
println("Compiler target: ${compTarget.name}. Parsing...")
|
||||
println("Compiler target: ${compTarget.name}")
|
||||
val bf = BuiltinFunctionsFacade(BuiltinFunctions)
|
||||
val program = Program(filepath.nameWithoutExtension, bf, compTarget, compTarget)
|
||||
bf.program = program
|
||||
@ -226,7 +236,7 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
|
||||
// error will be printed by the astchecker
|
||||
}
|
||||
|
||||
if (zpType == ZeropageType.FLOATSAFE && compTarget.name == Cx16Target.name) {
|
||||
if (zpType == ZeropageType.FLOATSAFE && compTarget.name == Cx16Target.NAME) {
|
||||
System.err.println("Warning: zp option floatsafe changed to basicsafe for cx16 target")
|
||||
zpType = ZeropageType.BASICSAFE
|
||||
}
|
||||
@ -235,6 +245,7 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
|
||||
.asSequence()
|
||||
.filter { it is Directive && it.directive == "%zpreserved" }
|
||||
.map { (it as Directive).args }
|
||||
.filter { it.size==2 && it[0].int!=null && it[1].int!=null }
|
||||
.map { it[0].int!!..it[1].int!! }
|
||||
.toList()
|
||||
|
||||
@ -265,9 +276,9 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
|
||||
|
||||
private fun processAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||
// perform initial syntax checks and processings
|
||||
println("Processing for target ${compilerOptions.compTarget.name}...")
|
||||
program.preprocessAst(program, errors)
|
||||
program.checkIdentifiers(errors, program, compilerOptions)
|
||||
println("Analyzing code...")
|
||||
program.preprocessAst(errors)
|
||||
program.checkIdentifiers(errors, compilerOptions)
|
||||
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
|
||||
// ...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.
|
||||
@ -282,11 +293,11 @@ private fun processAst(program: Program, errors: IErrorReporter, compilerOptions
|
||||
errors.report()
|
||||
program.addTypecasts(errors, compilerOptions)
|
||||
errors.report()
|
||||
program.variousCleanups(program, errors)
|
||||
program.variousCleanups(errors, compilerOptions)
|
||||
errors.report()
|
||||
program.checkValid(errors, compilerOptions)
|
||||
errors.report()
|
||||
program.checkIdentifiers(errors, program, compilerOptions)
|
||||
program.checkIdentifiers(errors, compilerOptions)
|
||||
errors.report()
|
||||
}
|
||||
|
||||
@ -300,7 +311,7 @@ private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, e
|
||||
|
||||
while (true) {
|
||||
// keep optimizing expressions and statements until no more steps remain
|
||||
val optsDone1 = program.simplifyExpressions()
|
||||
val optsDone1 = program.simplifyExpressions(errors)
|
||||
val optsDone2 = program.splitBinaryExpressions(compilerOptions, compTarget)
|
||||
val optsDone3 = program.optimizeStatements(errors, functions, compTarget)
|
||||
program.constantFold(errors, compTarget) // because simplified statements and expressions can result in more constants that can be folded away
|
||||
@ -315,7 +326,7 @@ private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOpt
|
||||
program.desugaring(errors)
|
||||
program.addTypecasts(errors, compilerOptions)
|
||||
errors.report()
|
||||
program.variousCleanups(program, errors)
|
||||
program.variousCleanups(errors, compilerOptions)
|
||||
val callGraph = CallGraph(program)
|
||||
callGraph.checkRecursiveCalls(errors)
|
||||
errors.report()
|
||||
@ -332,37 +343,56 @@ private sealed class WriteAssemblyResult {
|
||||
|
||||
private fun writeAssembly(program: Program,
|
||||
errors: IErrorReporter,
|
||||
outputDir: Path,
|
||||
compilerOptions: CompilationOptions
|
||||
): WriteAssemblyResult {
|
||||
// asm generation directly from the Ast
|
||||
program.processAstBeforeAsmGeneration(compilerOptions, errors)
|
||||
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions)
|
||||
val variables = VariableExtractor().extractVars(program)
|
||||
program.processAstBeforeAsmGeneration(compilerOptions, variables, errors)
|
||||
errors.report()
|
||||
|
||||
// TODO make removing all VarDecls work, but this needs inferType to be able to get its information from somewhere else as the VarDecl nodes in the Ast,
|
||||
// or don't use inferType at all anymore and "bake the type information" into the Ast somehow.
|
||||
// Note: we don't actually *need* to remove the VarDecl nodes, but it is nice as a temporary measure
|
||||
// to help clean up the code that still depends on them.
|
||||
// removeAllVardeclsFromAst(program)
|
||||
|
||||
// println("*********** AST RIGHT BEFORE ASM GENERATION *************")
|
||||
// printProgram(program)
|
||||
|
||||
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions)
|
||||
val assembly = asmGeneratorFor(compilerOptions.compTarget,
|
||||
program,
|
||||
errors,
|
||||
compilerOptions.compTarget.machine.zeropage,
|
||||
compilerOptions,
|
||||
outputDir).compileToAssembly()
|
||||
val assembly = asmGeneratorFor(program, errors, variables, compilerOptions).compileToAssembly()
|
||||
errors.report()
|
||||
|
||||
return if(assembly.valid && errors.noErrors()) {
|
||||
val assemblerReturnStatus = assembly.assemble(compilerOptions)
|
||||
if(assemblerReturnStatus!=0)
|
||||
WriteAssemblyResult.Fail("assembler step failed with return code $assemblerReturnStatus")
|
||||
else {
|
||||
return if(assembly!=null && errors.noErrors()) {
|
||||
if(assembly.assemble(compilerOptions)) {
|
||||
WriteAssemblyResult.Ok(assembly.name)
|
||||
}
|
||||
} else
|
||||
WriteAssemblyResult.Fail("assembler step failed")
|
||||
} else {
|
||||
WriteAssemblyResult.Fail("compiler failed with errors")
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeAllVardeclsFromAst(program: Program) {
|
||||
// remove all VarDecl nodes from the AST.
|
||||
// code generation doesn't require them anymore, it operates only on the 'variables' collection.
|
||||
|
||||
class SearchAndRemove: IAstVisitor {
|
||||
private val allVars = mutableListOf<VarDecl>()
|
||||
init {
|
||||
visit(program)
|
||||
for (it in allVars) {
|
||||
require((it.parent as IStatementContainer).statements.remove(it))
|
||||
}
|
||||
}
|
||||
override fun visit(decl: VarDecl) {
|
||||
allVars.add(decl)
|
||||
}
|
||||
}
|
||||
|
||||
SearchAndRemove()
|
||||
}
|
||||
|
||||
fun printProgram(program: Program) {
|
||||
println()
|
||||
val printer = AstToSourceTextConverter(::print, program)
|
||||
@ -370,15 +400,15 @@ fun printProgram(program: Program) {
|
||||
println()
|
||||
}
|
||||
|
||||
internal fun asmGeneratorFor(
|
||||
compTarget: ICompilationTarget,
|
||||
program: Program,
|
||||
errors: IErrorReporter,
|
||||
zp: Zeropage,
|
||||
options: CompilationOptions,
|
||||
outputDir: Path
|
||||
): IAssemblyGenerator
|
||||
internal fun asmGeneratorFor(program: Program, errors: IErrorReporter, variables: IVariablesAndConsts, options: CompilationOptions): IAssemblyGenerator
|
||||
{
|
||||
// at the moment we only have one code generation backend (for 6502 and 65c02)
|
||||
return AsmGen(program, errors, zp, options, compTarget, outputDir)
|
||||
if(options.experimentalCodegen) {
|
||||
if (options.compTarget.machine.cpu in arrayOf(CpuType.CPU6502, CpuType.CPU65c02))
|
||||
return prog8.codegen.experimental6502.AsmGen(program, errors, variables, options)
|
||||
} else {
|
||||
if (options.compTarget.machine.cpu in arrayOf(CpuType.CPU6502, CpuType.CPU65c02))
|
||||
return prog8.codegen.cpu6502.AsmGen(program, errors, variables, options)
|
||||
}
|
||||
|
||||
throw NotImplementedError("no asm generator for cpu ${options.compTarget.machine.cpu}")
|
||||
}
|
||||
|
@ -39,11 +39,8 @@ class ModuleImporter(private val program: Program,
|
||||
else -> candidates.first() // when more candiates, pick the one from the first location
|
||||
}
|
||||
|
||||
val logMsg = "importing '${filePath.nameWithoutExtension}' (from file $srcPath)"
|
||||
println(logMsg)
|
||||
|
||||
val module = importModule(SourceCode.File(srcPath))
|
||||
return Ok(module)
|
||||
val source = SourceCode.File(srcPath)
|
||||
return Ok(importModule(source))
|
||||
}
|
||||
|
||||
fun importLibraryModule(name: String): Module? {
|
||||
@ -54,6 +51,7 @@ class ModuleImporter(private val program: Program,
|
||||
}
|
||||
|
||||
private fun importModule(src: SourceCode) : Module {
|
||||
printImportingMessage(src.name, src.origin)
|
||||
val moduleAst = Prog8Parser.parseModule(src)
|
||||
program.addModule(moduleAst)
|
||||
|
||||
@ -91,7 +89,6 @@ class ModuleImporter(private val program: Program,
|
||||
val importedModule =
|
||||
moduleResourceSrc.fold(
|
||||
success = {
|
||||
println("importing '$moduleName' (from internal ${it.origin})")
|
||||
importModule(it)
|
||||
},
|
||||
failure = {
|
||||
@ -99,7 +96,6 @@ class ModuleImporter(private val program: Program,
|
||||
val moduleSrc = getModuleFromFile(moduleName, importingModule)
|
||||
moduleSrc.fold(
|
||||
success = {
|
||||
println("importing '$moduleName' (from file ${it.origin})")
|
||||
importModule(it)
|
||||
},
|
||||
failure = {
|
||||
@ -152,4 +148,18 @@ class ModuleImporter(private val program: Program,
|
||||
|
||||
return Err(NoSuchFileException(File("name")))
|
||||
}
|
||||
|
||||
fun printImportingMessage(module: String, origin: String) {
|
||||
print(" importing '$module' (from ${origin})")
|
||||
ansiEraseRestOfLine(false)
|
||||
print("\r")
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun ansiEraseRestOfLine(newline: Boolean) {
|
||||
print("\u001b[0K")
|
||||
if(newline)
|
||||
println()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,6 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.IStatementContainer
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.*
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
@ -13,6 +10,9 @@ import java.io.CharConversionException
|
||||
import java.io.File
|
||||
import kotlin.io.path.Path
|
||||
|
||||
/**
|
||||
* Semantic analysis.
|
||||
*/
|
||||
internal class AstChecker(private val program: Program,
|
||||
private val errors: IErrorReporter,
|
||||
private val compilerOptions: CompilationOptions
|
||||
@ -88,7 +88,7 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
override fun visit(forLoop: ForLoop) {
|
||||
|
||||
fun checkUnsignedLoopDownto0(range: RangeExpr?) {
|
||||
fun checkUnsignedLoopDownto0(range: RangeExpression?) {
|
||||
if(range==null)
|
||||
return
|
||||
val step = range.step.constValue(program)?.number ?: 1.0
|
||||
@ -100,7 +100,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
val iterableDt = forLoop.iterable.inferType(program).getOr(DataType.BYTE)
|
||||
if(iterableDt !in IterableDatatypes && forLoop.iterable !is RangeExpr) {
|
||||
if(iterableDt !in IterableDatatypes && forLoop.iterable !is RangeExpression) {
|
||||
errors.err("can only loop over an iterable type", forLoop.position)
|
||||
} else {
|
||||
val loopvar = forLoop.loopVar.targetVarDecl(program)
|
||||
@ -112,14 +112,14 @@ internal class AstChecker(private val program: Program,
|
||||
if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.ARRAY_UB && iterableDt != DataType.STR)
|
||||
errors.err("ubyte loop variable can only loop over unsigned bytes or strings", forLoop.position)
|
||||
|
||||
checkUnsignedLoopDownto0(forLoop.iterable as? RangeExpr)
|
||||
checkUnsignedLoopDownto0(forLoop.iterable as? RangeExpression)
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.UWORD && iterableDt != DataType.STR &&
|
||||
iterableDt != DataType.ARRAY_UB && iterableDt!= DataType.ARRAY_UW)
|
||||
errors.err("uword loop variable can only loop over unsigned bytes, words or strings", forLoop.position)
|
||||
|
||||
checkUnsignedLoopDownto0(forLoop.iterable as? RangeExpr)
|
||||
checkUnsignedLoopDownto0(forLoop.iterable as? RangeExpression)
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
if(iterableDt!= DataType.BYTE && iterableDt!= DataType.ARRAY_B)
|
||||
@ -140,10 +140,10 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
if(errors.noErrors()) {
|
||||
// check loop range values
|
||||
val range = forLoop.iterable as? RangeExpr
|
||||
val range = forLoop.iterable as? RangeExpression
|
||||
if(range!=null) {
|
||||
val from = range.from as? NumericLiteralValue
|
||||
val to = range.to as? NumericLiteralValue
|
||||
val from = range.from as? NumericLiteral
|
||||
val to = range.to as? NumericLiteral
|
||||
if(from != null)
|
||||
checkValueTypeAndRange(loopvar.datatype, from)
|
||||
else if(range.from.inferType(program) isnot loopvar.datatype)
|
||||
@ -208,8 +208,7 @@ internal class AstChecker(private val program: Program,
|
||||
is Label,
|
||||
is VarDecl,
|
||||
is InlineAssembly,
|
||||
is IStatementContainer,
|
||||
is Nop -> true
|
||||
is IStatementContainer -> true
|
||||
is Assignment -> {
|
||||
val target = statement.target.identifier!!.targetStatement(program)
|
||||
target === statement.previousSibling() // an initializer assignment is okay
|
||||
@ -454,6 +453,10 @@ internal class AstChecker(private val program: Program,
|
||||
errors.err("typecasting a float value in-place makes no sense", assignment.value.position)
|
||||
}
|
||||
|
||||
val numvalue = assignment.value.constValue(program)
|
||||
if(numvalue!=null && targetDt.isKnown)
|
||||
checkValueTypeAndRange(targetDt.getOr(DataType.UNDEFINED), numvalue)
|
||||
|
||||
super.visit(assignment)
|
||||
}
|
||||
|
||||
@ -488,23 +491,15 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
// target type check is already done at the Assignment:
|
||||
// val targetDt = assignTarget.inferType(program, assignment).typeOrElse(DataType.STR)
|
||||
// if(targetDt in IterableDatatypes)
|
||||
// errors.err("cannot assign to a string or array type", assignTarget.position)
|
||||
|
||||
if (assignment is Assignment) {
|
||||
|
||||
val targetDatatype = assignTarget.inferType(program)
|
||||
if (targetDatatype.isKnown) {
|
||||
val constVal = assignment.value.constValue(program)
|
||||
if (constVal != null) {
|
||||
checkValueTypeAndRange(targetDatatype.getOr(DataType.BYTE), constVal)
|
||||
} else {
|
||||
if(constVal==null) {
|
||||
val sourceDatatype = assignment.value.inferType(program)
|
||||
if (sourceDatatype.isUnknown) {
|
||||
if (assignment.value !is FunctionCallExpr)
|
||||
errors.err("assignment value is invalid or has no proper datatype", assignment.value.position)
|
||||
if (assignment.value !is FunctionCallExpression)
|
||||
errors.err("assignment value is invalid or has no proper datatype, maybe forgot '&' (address-of)", assignment.value.position)
|
||||
} else {
|
||||
checkAssignmentCompatible(targetDatatype.getOr(DataType.BYTE),
|
||||
sourceDatatype.getOr(DataType.BYTE), assignment.value)
|
||||
@ -534,14 +529,6 @@ internal class AstChecker(private val program: Program,
|
||||
err("const modifier can only be used on numeric types (byte, word, float)")
|
||||
}
|
||||
|
||||
// @zp can only occur on integers
|
||||
if(decl.datatype !in IntegerDatatypes) {
|
||||
if(decl.zeropage==ZeropageWish.PREFER_ZEROPAGE)
|
||||
errors.warn("this datatype can't be placed in zeropage", decl.position)
|
||||
if(decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE)
|
||||
errors.err("this datatype can't be placed in zeropage", decl.position)
|
||||
}
|
||||
|
||||
// FLOATS enabled?
|
||||
if(!compilerOptions.floats && decl.datatype.oneOf(DataType.FLOAT, DataType.ARRAY_F) && decl.type!= VarDeclType.MEMORY)
|
||||
err("floating point used, but that is not enabled via options")
|
||||
@ -554,11 +541,11 @@ internal class AstChecker(private val program: Program,
|
||||
err("array variable is missing a size specification or an initialization value")
|
||||
return
|
||||
}
|
||||
if(decl.value is NumericLiteralValue) {
|
||||
if(decl.value is NumericLiteral) {
|
||||
err("unsized array declaration cannot use a single literal initialization value")
|
||||
return
|
||||
}
|
||||
if(decl.value is RangeExpr)
|
||||
if(decl.value is RangeExpression)
|
||||
throw FatalAstException("range expressions in vardecls should have been converted into array values during constFolding $decl")
|
||||
}
|
||||
|
||||
@ -568,16 +555,16 @@ internal class AstChecker(private val program: Program,
|
||||
null -> {
|
||||
// a vardecl without an initial value, don't bother with it
|
||||
}
|
||||
is RangeExpr -> throw FatalAstException("range expression should have been converted to a true array value")
|
||||
is StringLiteralValue -> {
|
||||
checkValueTypeAndRangeString(decl.datatype, decl.value as StringLiteralValue)
|
||||
is RangeExpression -> throw FatalAstException("range expression should have been converted to a true array value")
|
||||
is StringLiteral -> {
|
||||
checkValueTypeAndRangeString(decl.datatype, decl.value as StringLiteral)
|
||||
}
|
||||
is ArrayLiteralValue -> {
|
||||
val arraySpec = decl.arraysize ?: ArrayIndex.forArray(decl.value as ArrayLiteralValue)
|
||||
checkValueTypeAndRangeArray(decl.datatype, arraySpec, decl.value as ArrayLiteralValue)
|
||||
is ArrayLiteral -> {
|
||||
val arraySpec = decl.arraysize ?: ArrayIndex.forArray(decl.value as ArrayLiteral)
|
||||
checkValueTypeAndRangeArray(decl.datatype, arraySpec, decl.value as ArrayLiteral)
|
||||
}
|
||||
is NumericLiteralValue -> {
|
||||
checkValueTypeAndRange(decl.datatype, decl.value as NumericLiteralValue)
|
||||
is NumericLiteral -> {
|
||||
checkValueTypeAndRange(decl.datatype, decl.value as NumericLiteral)
|
||||
}
|
||||
else -> {
|
||||
if(decl.type==VarDeclType.CONST) {
|
||||
@ -605,7 +592,7 @@ internal class AstChecker(private val program: Program,
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
val numvalue = decl.value as? NumericLiteralValue
|
||||
val numvalue = decl.value as? NumericLiteral
|
||||
if(numvalue!=null) {
|
||||
if (numvalue.type !in IntegerDatatypes || numvalue.number.toInt() < 0 || numvalue.number.toInt() > 65535) {
|
||||
err("memory address must be valid integer 0..\$ffff")
|
||||
@ -655,10 +642,11 @@ internal class AstChecker(private val program: Program,
|
||||
if(parameter==null)
|
||||
err("string var must be initialized with a string literal")
|
||||
}
|
||||
else if (decl.type==VarDeclType.VAR && decl.value !is StringLiteralValue)
|
||||
err("string var can only be initialized with a string literal")
|
||||
}
|
||||
|
||||
if(compilerOptions.zeropage==ZeropageType.DONTUSE && decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE)
|
||||
err("zeropage usage has been disabled by options")
|
||||
|
||||
super.visit(decl)
|
||||
}
|
||||
|
||||
@ -695,6 +683,8 @@ internal class AstChecker(private val program: Program,
|
||||
err("this directive may only occur at module level")
|
||||
if(directive.args.size!=2 || directive.args[0].int==null || directive.args[1].int==null)
|
||||
err("requires two addresses (start, end)")
|
||||
if(directive.args[0].int!! > 255u || directive.args[1].int!! > 255u)
|
||||
err("start and end addresss must be in Zeropage so 0..255")
|
||||
}
|
||||
"%address" -> {
|
||||
if(directive.parent !is Module)
|
||||
@ -763,7 +753,7 @@ internal class AstChecker(private val program: Program,
|
||||
errors.err("included file not found: $filename", directive.position)
|
||||
}
|
||||
|
||||
override fun visit(array: ArrayLiteralValue) {
|
||||
override fun visit(array: ArrayLiteral) {
|
||||
if(array.type.isKnown) {
|
||||
if (!compilerOptions.floats && array.type.oneOf(DataType.FLOAT, DataType.ARRAY_F)) {
|
||||
errors.err("floating point used, but that is not enabled via options", array.position)
|
||||
@ -777,11 +767,11 @@ internal class AstChecker(private val program: Program,
|
||||
val decl = e.targetVarDecl(program)!!
|
||||
return decl.datatype in PassByReferenceDatatypes
|
||||
}
|
||||
return e is StringLiteralValue
|
||||
return e is StringLiteral
|
||||
}
|
||||
|
||||
if(array.parent is VarDecl) {
|
||||
if (!array.value.all { it is NumericLiteralValue || it is AddressOf || isPassByReferenceElement(it) })
|
||||
if (!array.value.all { it is NumericLiteral || it is AddressOf || isPassByReferenceElement(it) })
|
||||
errors.err("array literal for variable initialization contains non-constant elements", array.position)
|
||||
} else if(array.parent is ForLoop) {
|
||||
if (!array.value.all { it.constValue(program) != null })
|
||||
@ -793,7 +783,7 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
override fun visit(char: CharLiteral) {
|
||||
try { // just *try* if it can be encoded, don't actually do it
|
||||
compilerOptions.compTarget.encodeString(char.value.toString(), char.altEncoding)
|
||||
compilerOptions.compTarget.encodeString(char.value.toString(), char.encoding)
|
||||
} catch (cx: CharConversionException) {
|
||||
errors.err(cx.message ?: "can't encode character", char.position)
|
||||
}
|
||||
@ -801,11 +791,13 @@ internal class AstChecker(private val program: Program,
|
||||
super.visit(char)
|
||||
}
|
||||
|
||||
override fun visit(string: StringLiteralValue) {
|
||||
override fun visit(string: StringLiteral) {
|
||||
checkValueTypeAndRangeString(DataType.STR, string)
|
||||
|
||||
try { // just *try* if it can be encoded, don't actually do it
|
||||
compilerOptions.compTarget.encodeString(string.value, string.altEncoding)
|
||||
val bytes = compilerOptions.compTarget.encodeString(string.value, string.encoding)
|
||||
if(0u in bytes)
|
||||
errors.warn("a character in the string encodes into the 0-byte, which will terminate the string prematurely", string.position)
|
||||
} catch (cx: CharConversionException) {
|
||||
errors.err(cx.message ?: "can't encode string", string.position)
|
||||
}
|
||||
@ -845,6 +837,13 @@ internal class AstChecker(private val program: Program,
|
||||
val leftDt = leftIDt.getOr(DataType.UNDEFINED)
|
||||
val rightDt = rightIDt.getOr(DataType.UNDEFINED)
|
||||
|
||||
if(expr.operator=="+" || expr.operator=="-") {
|
||||
if(leftDt == DataType.STR || rightDt == DataType.STR || leftDt in ArrayDatatypes || rightDt in ArrayDatatypes) {
|
||||
errors.err("missing & (address-of) on the string operand", expr.position)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
when(expr.operator){
|
||||
"/", "%" -> {
|
||||
val constvalRight = expr.right.constValue(program)
|
||||
@ -881,8 +880,15 @@ internal class AstChecker(private val program: Program,
|
||||
errors.err("left operand is not numeric or str", expr.left.position)
|
||||
if(rightDt!in NumericDatatypes && rightDt != DataType.STR)
|
||||
errors.err("right operand is not numeric or str", expr.right.position)
|
||||
if(leftDt!=rightDt)
|
||||
errors.err("left and right operands aren't the same type", expr.left.position)
|
||||
if(leftDt!=rightDt) {
|
||||
if(leftDt==DataType.STR && rightDt in IntegerDatatypes) {
|
||||
// only exception allowed: str * constvalue
|
||||
if(expr.right.constValue(program)!=null)
|
||||
errors.err("can only use string repeat with a constant number value", expr.left.position)
|
||||
} else {
|
||||
errors.err("left and right operands aren't the same type", expr.left.position)
|
||||
}
|
||||
}
|
||||
|
||||
if(expr.operator !in ComparisonOperators) {
|
||||
if (leftDt == DataType.STR && rightDt == DataType.STR || leftDt in ArrayDatatypes && rightDt in ArrayDatatypes) {
|
||||
@ -900,10 +906,13 @@ internal class AstChecker(private val program: Program,
|
||||
if(!typecast.expression.inferType(program).isKnown)
|
||||
errors.err("this expression doesn't return a value", typecast.expression.position)
|
||||
|
||||
if(typecast.expression is NumericLiteral)
|
||||
errors.err("can't cast the value to the requested target type", typecast.expression.position)
|
||||
|
||||
super.visit(typecast)
|
||||
}
|
||||
|
||||
override fun visit(range: RangeExpr) {
|
||||
override fun visit(range: RangeExpression) {
|
||||
fun err(msg: String) {
|
||||
errors.err(msg, range.position)
|
||||
}
|
||||
@ -936,7 +945,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(functionCallExpr: FunctionCallExpr) {
|
||||
override fun visit(functionCallExpr: FunctionCallExpression) {
|
||||
// this function call is (part of) an expression, which should be in a statement somewhere.
|
||||
val stmtOfExpression = findParentNode<Statement>(functionCallExpr)
|
||||
?: throw FatalAstException("cannot determine statement scope of function call expression at ${functionCallExpr.position}")
|
||||
@ -1081,16 +1090,14 @@ internal class AstChecker(private val program: Program,
|
||||
var ident: IdentifierReference? = null
|
||||
if(arg.value is IdentifierReference)
|
||||
ident = arg.value as IdentifierReference
|
||||
else if(arg.value is FunctionCallExpr) {
|
||||
val fcall = arg.value as FunctionCallExpr
|
||||
else if(arg.value is FunctionCallExpression) {
|
||||
val fcall = arg.value as FunctionCallExpression
|
||||
if(fcall.target.nameInSource == listOf("lsb") || fcall.target.nameInSource == listOf("msb"))
|
||||
ident = fcall.args[0] as? IdentifierReference
|
||||
}
|
||||
if(ident!=null && ident.nameInSource[0] == "cx16" && ident.nameInSource[1].startsWith("r")) {
|
||||
var regname = ident.nameInSource[1].uppercase()
|
||||
if(regname.endsWith('L'))
|
||||
regname=regname.substring(0, regname.length-1)
|
||||
if(regname.endsWith('s'))
|
||||
if(regname.endsWith('L') || regname.endsWith('H') || regname.endsWith('s'))
|
||||
regname=regname.substring(0, regname.length-1)
|
||||
val reg = RegisterOrPair.valueOf(regname)
|
||||
val same = params.filter { it.value.registerOrPair==reg }
|
||||
@ -1146,9 +1153,9 @@ internal class AstChecker(private val program: Program,
|
||||
if(index!=null && (index<0 || index>=arraysize))
|
||||
errors.err("array index out of bounds", arrayIndexedExpression.indexer.position)
|
||||
} else if(target.datatype == DataType.STR) {
|
||||
if(target.value is StringLiteralValue) {
|
||||
if(target.value is StringLiteral) {
|
||||
// check string lengths for non-memory mapped strings
|
||||
val stringLen = (target.value as StringLiteralValue).value.length
|
||||
val stringLen = (target.value as StringLiteral).value.length
|
||||
val index = arrayIndexedExpression.indexer.constIndex()
|
||||
if (index != null && (index < 0 || index >= stringLen))
|
||||
errors.err("index out of bounds", arrayIndexedExpression.indexer.position)
|
||||
@ -1208,38 +1215,153 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(containment: ContainmentCheck) {
|
||||
if(!containment.iterable.inferType(program).isIterable)
|
||||
errors.err("value set for containment check must be an iterable type", containment.iterable.position)
|
||||
val elementDt = containment.element.inferType(program)
|
||||
val iterableDt = containment.iterable.inferType(program)
|
||||
|
||||
if(containment.parent is BinaryExpression)
|
||||
errors.err("containment check is currently not supported in complex expressions", containment.position)
|
||||
|
||||
// TODO check that iterable contains the same types as the element that is searched
|
||||
if(iterableDt.isIterable) {
|
||||
val iterableEltDt = ArrayToElementTypes.getValue(iterableDt.getOr(DataType.UNDEFINED))
|
||||
val invalidDt = if (elementDt.isBytes) {
|
||||
iterableEltDt !in ByteDatatypes
|
||||
} else if (elementDt.isWords) {
|
||||
iterableEltDt !in WordDatatypes
|
||||
} else {
|
||||
false
|
||||
}
|
||||
if (invalidDt)
|
||||
errors.err("element datatype doesn't match iterable datatype", containment.position)
|
||||
} else {
|
||||
errors.err("value set for containment check must be an iterable type", containment.iterable.position)
|
||||
}
|
||||
|
||||
super.visit(containment)
|
||||
}
|
||||
|
||||
override fun visit(pipe: PipeExpression) {
|
||||
processPipe(pipe.expressions, pipe)
|
||||
val last = pipe.expressions.last() as IdentifierReference
|
||||
val target = last.targetStatement(program)!!
|
||||
when(target) {
|
||||
is BuiltinFunctionPlaceholder -> {
|
||||
if(BuiltinFunctions.getValue(target.name).known_returntype==null)
|
||||
errors.err("invalid pipe expression; last term doesn't return a value", last.position)
|
||||
}
|
||||
is Subroutine -> {
|
||||
if(target.returntypes.isEmpty())
|
||||
errors.err("invalid pipe expression; last term doesn't return a value", last.position)
|
||||
else if(target.returntypes.size!=1)
|
||||
errors.err("invalid pipe expression; last term doesn't return a single value", last.position)
|
||||
}
|
||||
else -> errors.err("invalid pipe expression; last term doesn't return a value", last.position)
|
||||
}
|
||||
super.visit(pipe)
|
||||
}
|
||||
|
||||
override fun visit(pipe: Pipe) {
|
||||
processPipe(pipe.expressions, pipe)
|
||||
super.visit(pipe)
|
||||
}
|
||||
|
||||
private fun processPipe(expressions: List<Expression>, scope: Node) {
|
||||
// first expression is just any expression producing a value
|
||||
// all other expressions should be the name of a unary function that returns a single value
|
||||
// the last expression should be the name of a unary function whose return value we don't care about.
|
||||
if (expressions.size < 2) {
|
||||
errors.err("pipe is missing one or more expressions", scope.position)
|
||||
} else {
|
||||
// invalid size and other issues will be handled by the ast checker later.
|
||||
var valueDt = expressions[0].inferType(program).getOrElse {
|
||||
throw FatalAstException("invalid dt ${expressions[0]} @ ${scope.position}")
|
||||
}
|
||||
|
||||
for(expr in expressions.drop(1)) { // just keep the first expression value as-is
|
||||
val functionName = expr as? IdentifierReference
|
||||
val function = functionName?.targetStatement(program)
|
||||
if(functionName!=null && function!=null) {
|
||||
when (function) {
|
||||
is BuiltinFunctionPlaceholder -> {
|
||||
val func = BuiltinFunctions.getValue(function.name)
|
||||
if(func.parameters.size!=1)
|
||||
errors.err("can only use unary function", expr.position)
|
||||
else if(func.known_returntype==null && expr !== expressions.last())
|
||||
errors.err("function must return a single value", expr.position)
|
||||
|
||||
val paramDts = func.parameters.firstOrNull()?.possibleDatatypes
|
||||
if(paramDts!=null && !paramDts.any { valueDt isAssignableTo it })
|
||||
errors.err("pipe value datatype $valueDt incompatible withfunction argument ${paramDts.toList()}", functionName.position)
|
||||
|
||||
valueDt = func.known_returntype!!
|
||||
}
|
||||
is Subroutine -> {
|
||||
if(function.parameters.size!=1)
|
||||
errors.err("can only use unary function", expr.position)
|
||||
else if(function.returntypes.size!=1 && expr !== expressions.last())
|
||||
errors.err("function must return a single value", expr.position)
|
||||
|
||||
val paramDt = function.parameters.firstOrNull()?.type
|
||||
if(paramDt!=null && !(valueDt isAssignableTo paramDt))
|
||||
errors.err("pipe value datatype $valueDt incompatible with function argument $paramDt", functionName.position)
|
||||
|
||||
if(function.returntypes.isNotEmpty())
|
||||
valueDt = function.returntypes.single()
|
||||
}
|
||||
else -> {
|
||||
throw FatalAstException("weird function")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(expr is IFunctionCall)
|
||||
errors.err("use only the name of the function, not a call", expr.position)
|
||||
else
|
||||
errors.err("can only use unary function", expr.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: Statement): Statement? {
|
||||
when (val targetStatement = target.targetStatement(program)) {
|
||||
is Label, is Subroutine, is BuiltinFunctionPlaceholder -> return targetStatement
|
||||
null -> errors.err("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position)
|
||||
else -> errors.err("cannot call that: ${target.nameInSource.joinToString(".")}", statement.position)
|
||||
is VarDecl -> {
|
||||
if(statement is Jump) {
|
||||
if (targetStatement.datatype == DataType.UWORD)
|
||||
return targetStatement
|
||||
else
|
||||
errors.err("wrong address variable datatype, expected uword", target.position)
|
||||
}
|
||||
else
|
||||
errors.err("cannot call that: ${target.nameInSource.joinToString(".")}", target.position)
|
||||
}
|
||||
null -> errors.err("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", target.position)
|
||||
else -> errors.err("cannot call that: ${target.nameInSource.joinToString(".")}", target.position)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun checkValueTypeAndRangeString(targetDt: DataType, value: StringLiteralValue) : Boolean {
|
||||
private fun checkValueTypeAndRangeString(targetDt: DataType, value: StringLiteral) : Boolean {
|
||||
return if (targetDt == DataType.STR) {
|
||||
if (value.value.length > 255) {
|
||||
errors.err("string length must be 0-255", value.position)
|
||||
false
|
||||
when {
|
||||
value.value.length > 255 -> {
|
||||
errors.err("string length must be 0-255", value.position)
|
||||
false
|
||||
}
|
||||
value.value.isEmpty() -> {
|
||||
val decl = value.parent as? VarDecl
|
||||
if(decl!=null && (decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE || decl.zeropage==ZeropageWish.PREFER_ZEROPAGE)) {
|
||||
errors.err("string in Zeropage must be non-empty", value.position)
|
||||
false
|
||||
}
|
||||
else true
|
||||
}
|
||||
else -> true
|
||||
}
|
||||
else
|
||||
true
|
||||
}
|
||||
else false
|
||||
}
|
||||
|
||||
private fun checkValueTypeAndRangeArray(targetDt: DataType, arrayspec: ArrayIndex, value: ArrayLiteralValue) : Boolean {
|
||||
private fun checkValueTypeAndRangeArray(targetDt: DataType, arrayspec: ArrayIndex, value: ArrayLiteral) : Boolean {
|
||||
fun err(msg: String) : Boolean {
|
||||
errors.err(msg, value.position)
|
||||
return false
|
||||
@ -1316,7 +1438,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkValueTypeAndRange(targetDt: DataType, value: NumericLiteralValue) : Boolean {
|
||||
private fun checkValueTypeAndRange(targetDt: DataType, value: NumericLiteral) : Boolean {
|
||||
fun err(msg: String) : Boolean {
|
||||
errors.err(msg, value.position)
|
||||
return false
|
||||
@ -1360,10 +1482,10 @@ internal class AstChecker(private val program: Program,
|
||||
return true
|
||||
}
|
||||
|
||||
private fun checkArrayValues(value: ArrayLiteralValue, type: DataType): Boolean {
|
||||
private fun checkArrayValues(value: ArrayLiteral, type: DataType): Boolean {
|
||||
val array = value.value.map {
|
||||
when (it) {
|
||||
is NumericLiteralValue -> it.number.toInt()
|
||||
is NumericLiteral -> it.number.toInt()
|
||||
is AddressOf -> it.identifier.hashCode() and 0xffff
|
||||
is TypecastExpression -> {
|
||||
val constVal = it.expression.constValue(program)
|
||||
@ -1403,7 +1525,7 @@ internal class AstChecker(private val program: Program,
|
||||
sourceValue: Expression) : Boolean {
|
||||
val position = sourceValue.position
|
||||
|
||||
if(sourceValue is RangeExpr)
|
||||
if(sourceValue is RangeExpression)
|
||||
errors.err("can't assign a range value to something else", position)
|
||||
|
||||
val result = when(targetDatatype) {
|
||||
|
@ -5,29 +5,36 @@ import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.expressions.CharLiteral
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.statements.Directive
|
||||
import prog8.ast.statements.VarDeclOrigin
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compiler.BeforeAsmGenerationAstChanger
|
||||
import prog8.compilerinterface.CompilationOptions
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
import prog8.compilerinterface.IStringEncoding
|
||||
import prog8.compilerinterface.IVariablesAndConsts
|
||||
|
||||
|
||||
internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||
// semantic analysis to see if the program is valid.
|
||||
val parentChecker = ParentNodeChecker()
|
||||
parentChecker.visit(this)
|
||||
val checker = AstChecker(this, errors, compilerOptions)
|
||||
checker.visit(this)
|
||||
}
|
||||
|
||||
internal fun Program.processAstBeforeAsmGeneration(compilerOptions: CompilationOptions, errors: IErrorReporter) {
|
||||
val fixer = BeforeAsmGenerationAstChanger(this, compilerOptions, errors)
|
||||
internal fun Program.processAstBeforeAsmGeneration(compilerOptions: CompilationOptions, variables: IVariablesAndConsts, errors: IErrorReporter) {
|
||||
val fixer = BeforeAsmAstChanger(this, compilerOptions, variables, errors)
|
||||
fixer.visit(this)
|
||||
while(errors.noErrors() && fixer.applyModifications()>0) {
|
||||
fixer.visit(this)
|
||||
}
|
||||
val cleaner = BeforeAsmTypecastCleaner(this, errors)
|
||||
cleaner.visit(this)
|
||||
while(errors.noErrors() && cleaner.applyModifications()>0) {
|
||||
cleaner.visit(this)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Program.reorderStatements(errors: IErrorReporter, options: CompilationOptions) {
|
||||
@ -46,7 +53,7 @@ internal fun Program.charLiteralsToUByteLiterals(enc: IStringEncoding) {
|
||||
override fun after(char: CharLiteral, parent: Node): Iterable<IAstModification> {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
char,
|
||||
NumericLiteralValue(DataType.UBYTE, enc.encodeString(char.value.toString(), char.altEncoding)[0].toDouble(), char.position),
|
||||
NumericLiteral(DataType.UBYTE, enc.encodeString(char.value.toString(), char.encoding)[0].toDouble(), char.position),
|
||||
parent
|
||||
))
|
||||
}
|
||||
@ -72,17 +79,17 @@ internal fun Program.verifyFunctionArgTypes() {
|
||||
fixer.visit(this)
|
||||
}
|
||||
|
||||
internal fun Program.preprocessAst(program: Program, errors: IErrorReporter) {
|
||||
val transforms = AstPreprocessor(program, errors)
|
||||
internal fun Program.preprocessAst(errors: IErrorReporter) {
|
||||
val transforms = AstPreprocessor(this, errors)
|
||||
transforms.visit(this)
|
||||
var mods = transforms.applyModifications()
|
||||
while(mods>0)
|
||||
mods = transforms.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.checkIdentifiers(errors: IErrorReporter, program: Program, options: CompilationOptions) {
|
||||
internal fun Program.checkIdentifiers(errors: IErrorReporter, options: CompilationOptions) {
|
||||
|
||||
val checker2 = AstIdentifiersChecker(errors, program, options.compTarget)
|
||||
val checker2 = AstIdentifiersChecker(errors, this, options.compTarget)
|
||||
checker2.visit(this)
|
||||
|
||||
if(errors.noErrors()) {
|
||||
@ -96,8 +103,8 @@ internal fun Program.checkIdentifiers(errors: IErrorReporter, program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Program.variousCleanups(program: Program, errors: IErrorReporter) {
|
||||
val process = VariousCleanups(program, errors)
|
||||
internal fun Program.variousCleanups(errors: IErrorReporter, options: CompilationOptions) {
|
||||
val process = VariousCleanups(this, errors, options)
|
||||
process.visit(this)
|
||||
if(errors.noErrors())
|
||||
process.applyModifications()
|
||||
@ -135,7 +142,7 @@ internal fun Program.moveMainAndStartToFirst() {
|
||||
|
||||
internal fun IdentifierReference.isSubroutineParameter(program: Program): Boolean {
|
||||
val vardecl = this.targetVarDecl(program)
|
||||
if(vardecl!=null && vardecl.autogeneratedDontRemove) {
|
||||
if(vardecl!=null && vardecl.origin==VarDeclOrigin.SUBROUTINEPARAM) {
|
||||
return vardecl.definingSubroutine?.parameters?.any { it.name==vardecl.name } == true
|
||||
}
|
||||
return false
|
||||
|
@ -4,11 +4,12 @@ import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.FunctionCallExpr
|
||||
import prog8.ast.expressions.StringLiteralValue
|
||||
import prog8.ast.expressions.FunctionCallExpression
|
||||
import prog8.ast.expressions.StringLiteral
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.compilerinterface.BuiltinFunctions
|
||||
import prog8.compilerinterface.Encoding
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
|
||||
@ -121,14 +122,14 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
super.visit(label)
|
||||
}
|
||||
|
||||
override fun visit(string: StringLiteralValue) {
|
||||
override fun visit(string: StringLiteral) {
|
||||
if (string.value.length > 255)
|
||||
errors.err("string literal length max is 255", string.position)
|
||||
|
||||
super.visit(string)
|
||||
}
|
||||
|
||||
override fun visit(functionCallExpr: FunctionCallExpr) = visitFunctionCall(functionCallExpr)
|
||||
override fun visit(functionCallExpr: FunctionCallExpression) = visitFunctionCall(functionCallExpr)
|
||||
override fun visit(functionCallStatement: FunctionCallStatement) = visitFunctionCall(functionCallStatement)
|
||||
|
||||
private fun visitFunctionCall(call: IFunctionCall) {
|
||||
@ -146,7 +147,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
errors.err("invalid number of arguments", pos)
|
||||
}
|
||||
if(func.name=="memory") {
|
||||
val name = call.args[0] as? StringLiteralValue
|
||||
val name = call.args[0] as? StringLiteral
|
||||
if(name!=null) {
|
||||
val processed = name.value.map {
|
||||
if(it.isLetterOrDigit())
|
||||
@ -154,7 +155,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
else
|
||||
'_'
|
||||
}.joinToString("")
|
||||
call.args[0] = StringLiteralValue(processed, false, name.position)
|
||||
call.args[0] = StringLiteral(processed, Encoding.PETSCII, name.position)
|
||||
call.args[0].linkParents(call as Node)
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,9 @@ package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.base.NumericDatatypes
|
||||
import prog8.ast.base.SyntaxError
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
@ -12,10 +14,10 @@ import prog8.compilerinterface.IErrorReporter
|
||||
|
||||
class AstPreprocessor(val program: Program, val errors: IErrorReporter) : AstWalker() {
|
||||
|
||||
override fun after(range: RangeExpr, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(range: RangeExpression, parent: Node): Iterable<IAstModification> {
|
||||
// has to be done before the constant folding, otherwise certain checks there will fail on invalid range sizes
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
if(range.from !is NumericLiteralValue) {
|
||||
if(range.from !is NumericLiteral) {
|
||||
try {
|
||||
val constval = range.from.constValue(program)
|
||||
if (constval != null)
|
||||
@ -24,7 +26,7 @@ class AstPreprocessor(val program: Program, val errors: IErrorReporter) : AstWal
|
||||
// syntax errors will be reported later
|
||||
}
|
||||
}
|
||||
if(range.to !is NumericLiteralValue) {
|
||||
if(range.to !is NumericLiteral) {
|
||||
try {
|
||||
val constval = range.to.constValue(program)
|
||||
if(constval!=null)
|
||||
@ -33,7 +35,7 @@ class AstPreprocessor(val program: Program, val errors: IErrorReporter) : AstWal
|
||||
// syntax errors will be reported later
|
||||
}
|
||||
}
|
||||
if(range.step !is NumericLiteralValue) {
|
||||
if(range.step !is NumericLiteral) {
|
||||
try {
|
||||
val constval = range.step.constValue(program)
|
||||
if(constval!=null)
|
||||
@ -49,7 +51,7 @@ class AstPreprocessor(val program: Program, val errors: IErrorReporter) : AstWal
|
||||
|
||||
// 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 vars = scope.statements.asSequence().filterIsInstance<VarDecl>()
|
||||
val parentscope = scope.definingScope
|
||||
if(vars.any() && parentscope !== parent) {
|
||||
val movements = mutableListOf<IAstModification>()
|
||||
@ -62,7 +64,7 @@ class AstPreprocessor(val program: Program, val errors: IErrorReporter) : AstWal
|
||||
} 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)
|
||||
val assign = Assignment(target, decl.value!!, AssignmentOrigin.VARINIT, decl.position)
|
||||
replacements.add(IAstModification.ReplaceNode(decl, assign, scope))
|
||||
decl.value = null
|
||||
decl.allowInitializeWithZero = false
|
||||
@ -85,4 +87,16 @@ class AstPreprocessor(val program: Program, val errors: IErrorReporter) : AstWal
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
val nextAssignment = decl.nextSibling() as? Assignment
|
||||
if(nextAssignment!=null && nextAssignment.origin!=AssignmentOrigin.VARINIT) {
|
||||
// check if it's a proper initializer assignment for the variable
|
||||
if(decl.value==null && nextAssignment.target.identifier?.targetVarDecl(program)===decl) {
|
||||
if(!nextAssignment.value.referencesIdentifier(nextAssignment.target.identifier!!.nameInSource))
|
||||
nextAssignment.origin = AssignmentOrigin.VARINIT
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
@ -6,8 +6,11 @@ import prog8.ast.base.DataType
|
||||
import prog8.ast.expressions.ArrayIndexedExpression
|
||||
import prog8.ast.expressions.BinaryExpression
|
||||
import prog8.ast.expressions.DirectMemoryRead
|
||||
import prog8.ast.expressions.StringLiteralValue
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.expressions.StringLiteral
|
||||
import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.DirectMemoryWrite
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
|
||||
@ -21,7 +24,7 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
|
||||
val namesInSub = symbolsInSub.map{ it.first }.toSet()
|
||||
if(subroutine.asmAddress==null) {
|
||||
if(!subroutine.isAsmSubroutine && subroutine.parameters.isNotEmpty()) {
|
||||
val vars = subroutine.statements.filterIsInstance<VarDecl>().map { it.name }.toSet()
|
||||
val vars = subroutine.statements.asSequence().filterIsInstance<VarDecl>().map { it.name }.toSet()
|
||||
if(!vars.containsAll(subroutine.parameters.map{it.name})) {
|
||||
return subroutine.parameters
|
||||
.filter { it.name !in namesInSub }
|
||||
@ -37,8 +40,8 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
|
||||
}
|
||||
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
val leftStr = expr.left as? StringLiteralValue
|
||||
val rightStr = expr.right as? StringLiteralValue
|
||||
val leftStr = expr.left as? StringLiteral
|
||||
val rightStr = expr.right as? StringLiteral
|
||||
if(expr.operator == "+") {
|
||||
val concatenatedString = concatString(expr)
|
||||
if(concatenatedString!=null)
|
||||
@ -49,7 +52,7 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
|
||||
val amount = expr.right.constValue(program)
|
||||
if(amount!=null) {
|
||||
val string = leftStr.value.repeat(amount.number.toInt())
|
||||
val strval = StringLiteralValue(string, leftStr.altEncoding, expr.position)
|
||||
val strval = StringLiteral(string, leftStr.encoding, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, strval, parent))
|
||||
}
|
||||
}
|
||||
@ -57,7 +60,7 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
|
||||
val amount = expr.right.constValue(program)
|
||||
if(amount!=null) {
|
||||
val string = rightStr.value.repeat(amount.number.toInt())
|
||||
val strval = StringLiteralValue(string, rightStr.altEncoding, expr.position)
|
||||
val strval = StringLiteral(string, rightStr.encoding, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, strval, parent))
|
||||
}
|
||||
}
|
||||
@ -70,9 +73,9 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
|
||||
return replacePointerVarIndexWithMemreadOrMemwrite(program, arrayIndexedExpression, parent)
|
||||
}
|
||||
|
||||
private fun concatString(expr: BinaryExpression): StringLiteralValue? {
|
||||
val rightStrval = expr.right as? StringLiteralValue
|
||||
val leftStrval = expr.left as? StringLiteralValue
|
||||
private fun concatString(expr: BinaryExpression): StringLiteral? {
|
||||
val rightStrval = expr.right as? StringLiteral
|
||||
val leftStrval = expr.left as? StringLiteral
|
||||
return when {
|
||||
expr.operator!="+" -> null
|
||||
expr.left is BinaryExpression && rightStrval!=null -> {
|
||||
@ -80,17 +83,17 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
|
||||
if(subStrVal==null)
|
||||
null
|
||||
else
|
||||
StringLiteralValue("${subStrVal.value}${rightStrval.value}", subStrVal.altEncoding, rightStrval.position)
|
||||
StringLiteral("${subStrVal.value}${rightStrval.value}", subStrVal.encoding, rightStrval.position)
|
||||
}
|
||||
expr.right is BinaryExpression && leftStrval!=null -> {
|
||||
val subStrVal = concatString(expr.right as BinaryExpression)
|
||||
if(subStrVal==null)
|
||||
null
|
||||
else
|
||||
StringLiteralValue("${leftStrval.value}${subStrVal.value}", subStrVal.altEncoding, leftStrval.position)
|
||||
StringLiteral("${leftStrval.value}${subStrVal.value}", subStrVal.encoding, leftStrval.position)
|
||||
}
|
||||
leftStrval!=null && rightStrval!=null -> {
|
||||
StringLiteralValue("${leftStrval.value}${rightStrval.value}", leftStrval.altEncoding, leftStrval.position)
|
||||
StringLiteral("${leftStrval.value}${rightStrval.value}", leftStrval.encoding, leftStrval.position)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
@ -1,35 +1,20 @@
|
||||
package prog8.compiler
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.IStatementContainer
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.*
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.compiler.astprocessing.isSubroutineParameter
|
||||
import prog8.codegen.target.AssemblyError
|
||||
import prog8.compilerinterface.*
|
||||
import prog8.optimizer.getTempVarName
|
||||
import prog8.optimizer.getTempRegisterName
|
||||
|
||||
|
||||
internal class BeforeAsmGenerationAstChanger(val program: Program, private val options: CompilationOptions,
|
||||
private val errors: IErrorReporter) : AstWalker() {
|
||||
|
||||
private val subroutineVariables = mutableMapOf<Subroutine, MutableList<Pair<String, VarDecl>>>()
|
||||
|
||||
private fun rememberSubroutineVar(decl: VarDecl) {
|
||||
val sub = decl.definingSubroutine ?: return
|
||||
var varsList = subroutineVariables[sub]
|
||||
if(varsList==null) {
|
||||
varsList = mutableListOf()
|
||||
subroutineVariables[sub] = varsList
|
||||
}
|
||||
varsList.add(decl.name to decl)
|
||||
}
|
||||
internal class BeforeAsmAstChanger(val program: Program,
|
||||
private val options: CompilationOptions,
|
||||
private val variables: IVariablesAndConsts,
|
||||
private val errors: IErrorReporter
|
||||
) : AstWalker() {
|
||||
|
||||
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
|
||||
throw FatalAstException("break should have been replaced by goto $breakStmt")
|
||||
@ -48,13 +33,28 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
val subs = block.statements.filterIsInstance<Subroutine>()
|
||||
block.statements.removeAll(subs)
|
||||
block.statements.addAll(subs)
|
||||
|
||||
// adjust global variables initialization
|
||||
if(options.dontReinitGlobals) {
|
||||
block.statements.asSequence().filterIsInstance<VarDecl>().forEach {
|
||||
if(it.type==VarDeclType.VAR) {
|
||||
it.zeropage = ZeropageWish.NOT_IN_ZEROPAGE
|
||||
it.findInitializer(program)?.let { initializer ->
|
||||
it.value = initializer.value // put the init value back into the vardecl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
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")
|
||||
rememberSubroutineVar(decl)
|
||||
if(!options.dontReinitGlobals) {
|
||||
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")
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@ -63,8 +63,8 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
// this triggers the more efficent augmented assignment code generation more often.
|
||||
// But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF.
|
||||
if(!assignment.isAugmentable
|
||||
&& assignment.target.identifier != null
|
||||
&& !assignment.target.isIOAddress(options.compTarget.machine)) {
|
||||
&& assignment.target.identifier != null
|
||||
&& !assignment.target.isIOAddress(options.compTarget.machine)) {
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
|
||||
if(binExpr!=null && binExpr.inferType(program) istype DataType.FLOAT && !options.optimizeFloatExpressions)
|
||||
@ -79,20 +79,28 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
// A = <something-without-A> <associativeoperator> <otherthing-with-A>
|
||||
// use the other part of the expression to split.
|
||||
val sourceDt = binExpr.right.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
|
||||
val (_, right) = binExpr.right.typecastTo(assignment.target.inferType(program).getOrElse { throw AssemblyError("unknown dt") }, sourceDt, implicit=true)
|
||||
val assignRight = Assignment(assignment.target, right, assignment.position)
|
||||
val (_, right) = binExpr.right.typecastTo(assignment.target.inferType(program).getOrElse { throw AssemblyError(
|
||||
"unknown dt"
|
||||
)
|
||||
}, sourceDt, implicit=true)
|
||||
val assignRight = Assignment(assignment.target, right, AssignmentOrigin.BEFOREASMGEN, assignment.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, assignRight, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr),
|
||||
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
|
||||
IAstModification.InsertBefore(assignment, assignRight, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr),
|
||||
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val sourceDt = binExpr.left.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
|
||||
val (_, left) = binExpr.left.typecastTo(assignment.target.inferType(program).getOrElse { throw AssemblyError("unknown dt") }, sourceDt, implicit=true)
|
||||
val assignLeft = Assignment(assignment.target, left, assignment.position)
|
||||
val (_, left) = binExpr.left.typecastTo(assignment.target.inferType(program).getOrElse { throw AssemblyError(
|
||||
"unknown dt"
|
||||
)
|
||||
}, sourceDt, implicit=true)
|
||||
val assignLeft = Assignment(assignment.target, left, AssignmentOrigin.BEFOREASMGEN, assignment.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, assignLeft, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
|
||||
IAstModification.InsertBefore(assignment, assignLeft, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -107,28 +115,18 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
}
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
val firstDeclarations = mutableMapOf<String, VarDecl>()
|
||||
val rememberedSubroutineVars = subroutineVariables.getOrDefault(subroutine, mutableListOf())
|
||||
for(decl in rememberedSubroutineVars) {
|
||||
val existing = firstDeclarations[decl.first]
|
||||
if(existing!=null && existing !== decl.second) {
|
||||
errors.err("variable ${decl.first} already defined in subroutine ${subroutine.name} at ${existing.position}", decl.second.position)
|
||||
} else {
|
||||
firstDeclarations[decl.first] = decl.second
|
||||
}
|
||||
}
|
||||
rememberedSubroutineVars.clear()
|
||||
val mods = mutableListOf<IAstModification>()
|
||||
|
||||
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernal routine.
|
||||
// and if an assembly block doesn't contain a rts/rti, and some other situations.
|
||||
val mods = mutableListOf<IAstModification>()
|
||||
val returnStmt = Return(null, subroutine.position)
|
||||
if (subroutine.asmAddress == null && !subroutine.inline) {
|
||||
if (!subroutine.isAsmSubroutine && !subroutine.inline) {
|
||||
if(subroutine.statements.isEmpty() ||
|
||||
(subroutine.amountOfRtsInAsm() == 0
|
||||
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
|
||||
&& subroutine.statements.last() !is Subroutine)) {
|
||||
mods += IAstModification.InsertLast(returnStmt, subroutine)
|
||||
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
|
||||
&& subroutine.statements.last() !is Subroutine
|
||||
&& subroutine.statements.last() !is Return)) {
|
||||
val returnStmt = Return(null, subroutine.position)
|
||||
mods += IAstModification.InsertLast(returnStmt, subroutine)
|
||||
}
|
||||
}
|
||||
|
||||
@ -138,78 +136,54 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
val subroutineStmtIdx = outerStatements.indexOf(subroutine)
|
||||
if (subroutineStmtIdx > 0) {
|
||||
val prevStmt = outerStatements[subroutineStmtIdx-1]
|
||||
if(outerScope !is Block
|
||||
&& (prevStmt !is Jump)
|
||||
&& prevStmt !is Subroutine
|
||||
&& prevStmt !is Return) {
|
||||
if(outerScope !is Block
|
||||
&& (prevStmt !is Jump)
|
||||
&& prevStmt !is Subroutine
|
||||
&& prevStmt !is Return
|
||||
) {
|
||||
val returnStmt = Return(null, subroutine.position)
|
||||
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope)
|
||||
}
|
||||
}
|
||||
|
||||
if (!subroutine.inline) {
|
||||
if (subroutine.isAsmSubroutine && subroutine.asmAddress==null && subroutine.amountOfRtsInAsm() == 0) {
|
||||
// make sure the NOT INLINED asm subroutine actually has a rts at the end
|
||||
// (non-asm routines get a Return statement as needed, above)
|
||||
mods += IAstModification.InsertLast(InlineAssembly(" rts\n", Position.DUMMY), subroutine)
|
||||
}
|
||||
}
|
||||
|
||||
return mods
|
||||
}
|
||||
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
// see if we can remove redundant typecasts (outside of expressions)
|
||||
// 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,
|
||||
// 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)
|
||||
if (typecast.type in ByteDatatypes && sourceDt in ByteDatatypes
|
||||
|| typecast.type in WordDatatypes && sourceDt in WordDatatypes) {
|
||||
if(typecast.parent !is Expression) {
|
||||
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
|
||||
}
|
||||
}
|
||||
|
||||
if(sourceDt in PassByReferenceDatatypes) {
|
||||
if(typecast.type==DataType.UWORD) {
|
||||
val identifier = typecast.expression as? IdentifierReference
|
||||
if(identifier!=null) {
|
||||
return if(identifier.isSubroutineParameter(program)) {
|
||||
listOf(IAstModification.ReplaceNode(
|
||||
typecast,
|
||||
typecast.expression,
|
||||
parent
|
||||
))
|
||||
} else {
|
||||
listOf(IAstModification.ReplaceNode(
|
||||
typecast,
|
||||
AddressOf(identifier, typecast.position),
|
||||
parent
|
||||
))
|
||||
}
|
||||
} else if(typecast.expression is IFunctionCall) {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
typecast,
|
||||
typecast.expression,
|
||||
parent
|
||||
))
|
||||
}
|
||||
} else {
|
||||
errors.err("cannot cast pass-by-reference value to type ${typecast.type} (only to UWORD)", typecast.position)
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(ifElse: IfElse, parent: Node): Iterable<IAstModification> {
|
||||
val prefixExpr = ifElse.condition as? PrefixExpression
|
||||
if(prefixExpr!=null && prefixExpr.operator=="not") {
|
||||
// if not x -> if x==0
|
||||
val booleanExpr = BinaryExpression(prefixExpr.expression, "==", NumericLiteralValue.optimalInteger(0, ifElse.condition.position), ifElse.condition.position)
|
||||
val booleanExpr = BinaryExpression(
|
||||
prefixExpr.expression,
|
||||
"==",
|
||||
NumericLiteral.optimalInteger(0, ifElse.condition.position),
|
||||
ifElse.condition.position
|
||||
)
|
||||
return listOf(IAstModification.ReplaceNode(ifElse.condition, booleanExpr, ifElse))
|
||||
}
|
||||
|
||||
val binExpr = ifElse.condition as? BinaryExpression
|
||||
if(binExpr==null || binExpr.operator !in ComparisonOperators) {
|
||||
// if x -> if x!=0, if x+5 -> if x+5 != 0
|
||||
val booleanExpr = BinaryExpression(ifElse.condition, "!=", NumericLiteralValue.optimalInteger(0, ifElse.condition.position), ifElse.condition.position)
|
||||
val booleanExpr = BinaryExpression(
|
||||
ifElse.condition,
|
||||
"!=",
|
||||
NumericLiteral.optimalInteger(0, ifElse.condition.position),
|
||||
ifElse.condition.position
|
||||
)
|
||||
return listOf(IAstModification.ReplaceNode(ifElse.condition, booleanExpr, ifElse))
|
||||
}
|
||||
|
||||
if((binExpr.left as? NumericLiteralValue)?.number==0.0 &&
|
||||
(binExpr.right as? NumericLiteralValue)?.number!=0.0)
|
||||
if((binExpr.left as? NumericLiteral)?.number==0.0 &&
|
||||
(binExpr.right as? NumericLiteral)?.number!=0.0)
|
||||
throw FatalAstException("0==X should have been swapped to if X==0")
|
||||
|
||||
// simplify the conditional expression, introduce simple assignments if required.
|
||||
@ -219,11 +193,19 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
if(simplify.rightVarAssignment!=null) {
|
||||
modifications += IAstModification.ReplaceNode(binExpr.right, simplify.rightOperandReplacement!!, binExpr)
|
||||
modifications += IAstModification.InsertBefore(ifElse, simplify.rightVarAssignment, parent as IStatementContainer)
|
||||
modifications += IAstModification.InsertBefore(
|
||||
ifElse,
|
||||
simplify.rightVarAssignment,
|
||||
parent as IStatementContainer
|
||||
)
|
||||
}
|
||||
if(simplify.leftVarAssignment!=null) {
|
||||
modifications += IAstModification.ReplaceNode(binExpr.left, simplify.leftOperandReplacement!!, binExpr)
|
||||
modifications += IAstModification.InsertBefore(ifElse, simplify.leftVarAssignment, parent as IStatementContainer)
|
||||
modifications += IAstModification.InsertBefore(
|
||||
ifElse,
|
||||
simplify.leftVarAssignment,
|
||||
parent as IStatementContainer
|
||||
)
|
||||
}
|
||||
|
||||
return modifications
|
||||
@ -240,9 +222,9 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
|
||||
// TODO: somehow figure out if the expr will result in stack-evaluation STILL after being split off,
|
||||
// in that case: do *not* split it off but just keep it as it is (otherwise code size increases)
|
||||
// TODO: do NOT move this to an earler ast transform phase (such as StatementReorderer or StatementOptimizer) - it WILL result in larger code.
|
||||
// TODO: this should be replaced by a general expression-evaluation optimization step.
|
||||
// the actual conditional expression in the statement should be no more than VARIABLE <COMPARISON-OPERATOR> SIMPLE-EXPRESSION
|
||||
// NOTE: do NOT move this to an earler ast transform phase (such as StatementReorderer or StatementOptimizer) - it WILL result in larger code.
|
||||
|
||||
var leftAssignment: Assignment? = null
|
||||
var leftOperandReplacement: Expression? = null
|
||||
@ -260,27 +242,23 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
}
|
||||
|
||||
if(separateLeftExpr) {
|
||||
val name = getTempVarName(leftDt)
|
||||
val name = getTempRegisterName(leftDt)
|
||||
leftOperandReplacement = IdentifierReference(name, expr.position)
|
||||
leftAssignment = Assignment(
|
||||
AssignTarget(IdentifierReference(name, expr.position), null, null, expr.position),
|
||||
expr.left,
|
||||
expr.position
|
||||
AssignmentOrigin.BEFOREASMGEN, expr.position
|
||||
)
|
||||
}
|
||||
if(separateRightExpr) {
|
||||
val name = when {
|
||||
rightDt istype DataType.UBYTE -> listOf("prog8_lib","retval_interm_ub")
|
||||
rightDt istype DataType.UWORD -> listOf("prog8_lib","retval_interm_uw")
|
||||
rightDt istype DataType.BYTE -> listOf("prog8_lib","retval_interm_b2")
|
||||
rightDt istype DataType.WORD -> listOf("prog8_lib","retval_interm_w2")
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
val name = program.getTempVar(rightDt.getOrElse { throw FatalAstException("invalid dt") }, true)
|
||||
val tempvardecl = program.toplevelModule.lookup(name) as VarDecl
|
||||
variables.addIfUnknown(tempvardecl.definingBlock, tempvardecl)
|
||||
rightOperandReplacement = IdentifierReference(name, expr.position)
|
||||
rightAssignment = Assignment(
|
||||
AssignTarget(IdentifierReference(name, expr.position), null, null, expr.position),
|
||||
expr.right,
|
||||
expr.position
|
||||
AssignmentOrigin.BEFOREASMGEN, expr.position
|
||||
)
|
||||
}
|
||||
return CondExprSimplificationResult(
|
||||
@ -299,13 +277,13 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
if(dt1 in ByteDatatypes) {
|
||||
if(dt2 in ByteDatatypes)
|
||||
return noModifications
|
||||
val (replaced, cast) = arg1.typecastTo(if(dt1==DataType.UBYTE) DataType.UWORD else DataType.WORD, dt1, true)
|
||||
val (replaced, cast) = arg1.typecastTo(if(dt1== DataType.UBYTE) DataType.UWORD else DataType.WORD, dt1, true)
|
||||
if(replaced)
|
||||
return listOf(IAstModification.ReplaceNode(arg1, cast, functionCallStatement))
|
||||
} else {
|
||||
if(dt2 in WordDatatypes)
|
||||
return noModifications
|
||||
val (replaced, cast) = arg2.typecastTo(if(dt2==DataType.UBYTE) DataType.UWORD else DataType.WORD, dt2, true)
|
||||
val (replaced, cast) = arg2.typecastTo(if(dt2== DataType.UBYTE) DataType.UWORD else DataType.WORD, dt2, true)
|
||||
if(replaced)
|
||||
return listOf(IAstModification.ReplaceNode(arg2, cast, functionCallStatement))
|
||||
}
|
||||
@ -323,7 +301,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
|
||||
|
||||
val index = arrayIndexedExpression.indexer.indexExpr
|
||||
if(index !is NumericLiteralValue && index !is IdentifierReference) {
|
||||
if(index !is NumericLiteral && index !is IdentifierReference) {
|
||||
// replace complex indexing expression with a temp variable to hold the computed index first
|
||||
return getAutoIndexerVarFor(arrayIndexedExpression)
|
||||
}
|
||||
@ -337,11 +315,11 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
val complexArrayIndexedExpressions = mutableListOf<ArrayIndexedExpression>()
|
||||
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
|
||||
val ix = arrayIndexedExpression.indexer.indexExpr
|
||||
if(ix !is NumericLiteralValue && ix !is IdentifierReference)
|
||||
if(ix !is NumericLiteral && ix !is IdentifierReference)
|
||||
complexArrayIndexedExpressions.add(arrayIndexedExpression)
|
||||
}
|
||||
|
||||
override fun visit(branch: Branch) {}
|
||||
override fun visit(branch: ConditionalBranch) {}
|
||||
|
||||
override fun visit(forLoop: ForLoop) {}
|
||||
|
||||
@ -371,11 +349,19 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
val statement = expr.containingStatement
|
||||
val dt = expr.indexer.indexExpr.inferType(program)
|
||||
val tempvar = if(dt.isBytes) listOf("prog8_lib","retval_interm_ub") else listOf("prog8_lib","retval_interm_b")
|
||||
val tempvar = program.getTempVar(dt.getOrElse { throw FatalAstException("invalid dt") })
|
||||
val tempvardecl = program.toplevelModule.lookup(tempvar) as VarDecl
|
||||
variables.addIfUnknown(tempvardecl.definingBlock, tempvardecl)
|
||||
val target = AssignTarget(IdentifierReference(tempvar, 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, AssignmentOrigin.BEFOREASMGEN, expr.indexer.position)
|
||||
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
|
||||
}
|
||||
|
@ -0,0 +1,76 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.ByteDatatypes
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.PassByReferenceDatatypes
|
||||
import prog8.ast.base.WordDatatypes
|
||||
import prog8.ast.expressions.AddressOf
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.TypecastExpression
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
|
||||
internal class BeforeAsmTypecastCleaner(val program: Program,
|
||||
private val errors: IErrorReporter
|
||||
) : AstWalker() {
|
||||
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
// see if we can remove redundant typecasts (outside of expressions)
|
||||
// such as casting byte<->ubyte, word<->uword or even redundant casts (sourcetype = target type).
|
||||
// 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)
|
||||
if (typecast.type in ByteDatatypes && sourceDt in ByteDatatypes
|
||||
|| typecast.type in WordDatatypes && sourceDt in WordDatatypes
|
||||
) {
|
||||
if(typecast.parent !is Expression) {
|
||||
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
|
||||
}
|
||||
}
|
||||
|
||||
if(typecast.type==sourceDt)
|
||||
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
|
||||
|
||||
if(sourceDt in PassByReferenceDatatypes) {
|
||||
if(typecast.type== DataType.UWORD) {
|
||||
val identifier = typecast.expression as? IdentifierReference
|
||||
if(identifier!=null) {
|
||||
return if(identifier.isSubroutineParameter(program)) {
|
||||
listOf(
|
||||
IAstModification.ReplaceNode(
|
||||
typecast,
|
||||
typecast.expression,
|
||||
parent
|
||||
)
|
||||
)
|
||||
} else {
|
||||
listOf(
|
||||
IAstModification.ReplaceNode(
|
||||
typecast,
|
||||
AddressOf(identifier, typecast.position),
|
||||
parent
|
||||
)
|
||||
)
|
||||
}
|
||||
} else if(typecast.expression is IFunctionCall) {
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(
|
||||
typecast,
|
||||
typecast.expression,
|
||||
parent
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
errors.err("cannot cast pass-by-reference value to type ${typecast.type} (only to UWORD)", typecast.position)
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
}
|
@ -6,11 +6,14 @@ import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.ParentSentinel
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.expressions.DirectMemoryRead
|
||||
import prog8.ast.expressions.FunctionCallExpression
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.PrefixExpression
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compilerinterface.*
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
|
||||
|
||||
internal class CodeDesugarer(val program: Program, private val errors: IErrorReporter) : AstWalker() {
|
||||
@ -117,7 +120,7 @@ _after:
|
||||
override fun before(functionCallStatement: FunctionCallStatement, parent: Node) =
|
||||
before(functionCallStatement as IFunctionCall, parent, functionCallStatement.position)
|
||||
|
||||
override fun before(functionCallExpr: FunctionCallExpr, parent: Node) =
|
||||
override fun before(functionCallExpr: FunctionCallExpression, parent: Node) =
|
||||
before(functionCallExpr as IFunctionCall, parent, functionCallExpr.position)
|
||||
|
||||
private fun before(functionCall: IFunctionCall, parent: Node, position: Position): Iterable<IAstModification> {
|
||||
@ -129,16 +132,9 @@ _after:
|
||||
if(functionCall.target.nameInSource==listOf("poke")) {
|
||||
// poke(a, v) is synonymous with @(a) = v
|
||||
val tgt = AssignTarget(null, null, DirectMemoryWrite(functionCall.args[0], position), position)
|
||||
val assign = Assignment(tgt, functionCall.args[1], position)
|
||||
val assign = Assignment(tgt, functionCall.args[1], AssignmentOrigin.OPTIMIZER, position)
|
||||
return listOf(IAstModification.ReplaceNode(functionCall as Node, assign, parent))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(expr.operator=="in") {
|
||||
println("IN-TEST: $expr\n in: $parent")
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.expressions.ArrayLiteralValue
|
||||
import prog8.ast.expressions.ArrayLiteral
|
||||
import prog8.ast.expressions.ContainmentCheck
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.StringLiteralValue
|
||||
import prog8.ast.expressions.StringLiteral
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.ast.statements.WhenChoice
|
||||
import prog8.ast.walk.AstWalker
|
||||
@ -15,9 +16,19 @@ import prog8.ast.walk.IAstModification
|
||||
|
||||
internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
|
||||
|
||||
override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||
if(string.parent !is VarDecl && string.parent !is WhenChoice && string.parent !is ContainmentCheck) {
|
||||
override fun after(string: StringLiteral, parent: Node): Iterable<IAstModification> {
|
||||
if(string.parent !is VarDecl
|
||||
&& string.parent !is WhenChoice
|
||||
&& (string.parent !is ContainmentCheck || string.value.length>ContainmentCheck.max_inlined_string_length)) {
|
||||
// replace the literal string by an identifier reference to the interned string
|
||||
|
||||
val parentFunc = (string.parent as? IFunctionCall)?.target
|
||||
if(parentFunc!=null) {
|
||||
if(parentFunc.nameInSource.size==1 && parentFunc.nameInSource[0]=="memory") {
|
||||
// memory() builtin function just uses the string as a label name
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
val scopedName = program.internString(string)
|
||||
val identifier = IdentifierReference(scopedName, string.position)
|
||||
return listOf(IAstModification.ReplaceNode(string, identifier, parent))
|
||||
@ -25,7 +36,7 @@ internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(array: ArrayLiteral, parent: Node): Iterable<IAstModification> {
|
||||
val vardecl = array.parent as? VarDecl
|
||||
if(vardecl!=null) {
|
||||
// adjust the datatype of the array (to an educated guess from the vardecl type)
|
||||
@ -36,7 +47,7 @@ internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
|
||||
return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl))
|
||||
}
|
||||
} else {
|
||||
if(array.parent is ContainmentCheck)
|
||||
if(array.parent is ContainmentCheck && array.value.size<ContainmentCheck.max_inlined_string_length)
|
||||
return noModifications
|
||||
|
||||
val arrayDt = array.guessDatatype(program)
|
||||
|
@ -17,7 +17,7 @@ internal class ParentNodeChecker: AstWalker() {
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||
override fun before(array: ArrayLiteral, parent: Node): Iterable<IAstModification> {
|
||||
if(array.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $array")
|
||||
return noModifications
|
||||
@ -47,7 +47,7 @@ internal class ParentNodeChecker: AstWalker() {
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(branch: Branch, parent: Node): Iterable<IAstModification> {
|
||||
override fun before(branch: ConditionalBranch, parent: Node): Iterable<IAstModification> {
|
||||
if(branch.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $branch")
|
||||
return noModifications
|
||||
@ -149,7 +149,7 @@ internal class ParentNodeChecker: AstWalker() {
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(numLiteral: NumericLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||
override fun before(numLiteral: NumericLiteral, parent: Node): Iterable<IAstModification> {
|
||||
if(numLiteral.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $numLiteral")
|
||||
return noModifications
|
||||
@ -161,7 +161,7 @@ internal class ParentNodeChecker: AstWalker() {
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(range: RangeExpr, parent: Node): Iterable<IAstModification> {
|
||||
override fun before(range: RangeExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(range.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $range")
|
||||
return noModifications
|
||||
@ -185,7 +185,7 @@ internal class ParentNodeChecker: AstWalker() {
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||
override fun before(string: StringLiteral, parent: Node): Iterable<IAstModification> {
|
||||
if(string.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $string")
|
||||
return noModifications
|
||||
@ -221,7 +221,7 @@ internal class ParentNodeChecker: AstWalker() {
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(functionCallExpr: FunctionCallExpr, parent: Node): Iterable<IAstModification> {
|
||||
override fun before(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(functionCallExpr.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $functionCallExpr")
|
||||
return noModifications
|
||||
@ -233,12 +233,6 @@ internal class ParentNodeChecker: AstWalker() {
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(nop: Nop, parent: Node): Iterable<IAstModification> {
|
||||
if(nop.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $nop")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
if(scope.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $scope")
|
||||
|
@ -13,7 +13,7 @@ Too bad, because the code is very small
|
||||
//import prog8.ast.Program
|
||||
//import prog8.ast.base.Position
|
||||
//import prog8.ast.expressions.BinaryExpression
|
||||
//import prog8.ast.expressions.NumericLiteralValue
|
||||
//import prog8.ast.expressions.NumericLiteral
|
||||
//import kotlin.reflect.KClass
|
||||
//import kotlin.reflect.KVisibility
|
||||
//import kotlin.reflect.full.declaredMemberProperties
|
||||
@ -59,9 +59,9 @@ Too bad, because the code is very small
|
||||
//
|
||||
//fun main() {
|
||||
// val ast = BinaryExpression(
|
||||
// NumericLiteralValue.optimalInteger(100, Position.DUMMY),
|
||||
// NumericLiteral.optimalInteger(100, Position.DUMMY),
|
||||
// "+",
|
||||
// NumericLiteralValue.optimalInteger(200, Position.DUMMY),
|
||||
// NumericLiteral.optimalInteger(200, Position.DUMMY),
|
||||
// Position.DUMMY
|
||||
// )
|
||||
//
|
||||
|
@ -31,7 +31,7 @@ internal class StatementReorderer(val program: Program,
|
||||
val (blocks, other) = module.statements.partition { it is Block }
|
||||
module.statements = other.asSequence().plus(blocks.sortedBy { (it as Block).address ?: UInt.MAX_VALUE }).toMutableList()
|
||||
|
||||
val mainBlock = module.statements.filterIsInstance<Block>().firstOrNull { it.name=="main" }
|
||||
val mainBlock = module.statements.asSequence().filterIsInstance<Block>().firstOrNull { it.name=="main" }
|
||||
if(mainBlock!=null && mainBlock.address==null) {
|
||||
module.statements.remove(mainBlock)
|
||||
module.statements.add(0, mainBlock)
|
||||
@ -44,52 +44,65 @@ internal class StatementReorderer(val program: Program,
|
||||
private val declsProcessedWithInitAssignment = mutableSetOf<VarDecl>()
|
||||
|
||||
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.
|
||||
return noModifications
|
||||
}
|
||||
val nextStmt = decl.nextSibling()
|
||||
val nextAssign = nextStmt as? Assignment
|
||||
if(nextAssign!=null && !nextAssign.isAugmentable) {
|
||||
val target = nextAssign.target.identifier?.targetStatement(program)
|
||||
if(target === decl) {
|
||||
// an initializer assignment for a vardecl is already here
|
||||
if (decl.type == VarDeclType.VAR) {
|
||||
if (decl.datatype in NumericDatatypes) {
|
||||
if(decl !in declsProcessedWithInitAssignment) {
|
||||
declsProcessedWithInitAssignment.add(decl)
|
||||
if (decl.value == null) {
|
||||
if (decl.origin==VarDeclOrigin.USERCODE && 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("tempvar_") && decl.definingScope.name=="prog8_lib") {
|
||||
// no need to zero out the special internal temporary variables.
|
||||
return noModifications
|
||||
}
|
||||
if(decl.findInitializer(program)!=null)
|
||||
return noModifications // an initializer assignment for a vardecl is already here
|
||||
val nextFor = decl.nextSibling() as? ForLoop
|
||||
val hasNextForWithThisLoopvar = nextFor?.loopVar?.nameInSource==listOf(decl.name)
|
||||
if (!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(), AssignmentOrigin.VARINIT, decl.position)
|
||||
return listOf(IAstModification.InsertAfter(
|
||||
decl, assignzero, parent as IStatementContainer
|
||||
))
|
||||
}
|
||||
}
|
||||
val nextFor = nextStmt as? ForLoop
|
||||
val hasNextForWithThisLoopvar = nextFor?.loopVar?.nameInSource==listOf(decl.name)
|
||||
if (!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!!, AssignmentOrigin.VARINIT, pos)
|
||||
decl.value = null
|
||||
return listOf(IAstModification.InsertAfter(
|
||||
decl, assign, parent as IStatementContainer
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(decl.datatype in ArrayDatatypes) {
|
||||
// only if the initializer expression is a reference to another array, split it into a separate assignment.
|
||||
// this is so that it later can be changed into a memcopy.
|
||||
// (that code only triggers on regular assignment, not on variable initializers)
|
||||
val ident = decl.value as? IdentifierReference
|
||||
if(ident!=null) {
|
||||
val target = ident.targetVarDecl(program)
|
||||
if(target!=null && target.isArray) {
|
||||
val pos = decl.value!!.position
|
||||
val identifier = IdentifierReference(listOf(decl.name), pos)
|
||||
val assign = Assignment(AssignTarget(identifier, null, null, pos), decl.value!!, AssignmentOrigin.VARINIT, pos)
|
||||
decl.value = null
|
||||
return listOf(IAstModification.InsertAfter(
|
||||
decl, assign, 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
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -118,7 +131,7 @@ internal class StatementReorderer(val program: Program,
|
||||
|
||||
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
if(subroutine.name=="start" && parent is Block) {
|
||||
if(parent.statements.filterIsInstance<Subroutine>().first().name!="start") {
|
||||
if(parent.statements.asSequence().filterIsInstance<Subroutine>().first().name!="start") {
|
||||
return listOf(
|
||||
IAstModification.Remove(subroutine, parent),
|
||||
IAstModification.InsertFirst(subroutine, parent)
|
||||
@ -150,14 +163,24 @@ internal class StatementReorderer(val program: Program,
|
||||
varsChanges +=
|
||||
if(stringParamsByNames.isNotEmpty()) {
|
||||
subroutine.statements
|
||||
.asSequence()
|
||||
.filterIsInstance<VarDecl>()
|
||||
.filter { it.subroutineParameter!=null && it.name in stringParamsByNames }
|
||||
.map {
|
||||
val newvar = VarDecl(it.type, DataType.UWORD, it.zeropage, null, it.name, null, false, true, it.sharedWithAsm, stringParamsByNames.getValue(it.name), it.position)
|
||||
val newvar = VarDecl(it.type, it.origin, DataType.UWORD,
|
||||
it.zeropage,
|
||||
null,
|
||||
it.name,
|
||||
null,
|
||||
false,
|
||||
it.sharedWithAsm,
|
||||
stringParamsByNames.getValue(it.name),
|
||||
it.position
|
||||
)
|
||||
IAstModification.ReplaceNode(it, newvar, subroutine)
|
||||
}
|
||||
}
|
||||
else emptyList()
|
||||
else emptySequence()
|
||||
}
|
||||
|
||||
return modifications + parameterChanges + varsChanges
|
||||
@ -233,7 +256,7 @@ internal class StatementReorderer(val program: Program,
|
||||
// generating the wrong results later
|
||||
|
||||
fun wrapped(expr: Expression): Expression =
|
||||
BinaryExpression(expr, "!=", NumericLiteralValue(DataType.UBYTE, 0.0, expr.position), expr.position)
|
||||
BinaryExpression(expr, "!=", NumericLiteral(DataType.UBYTE, 0.0, expr.position), expr.position)
|
||||
|
||||
fun isLogicalExpr(expr: Expression?): Boolean {
|
||||
if(expr is BinaryExpression && expr.operator in (LogicalOperators + ComparisonOperators))
|
||||
@ -283,7 +306,7 @@ internal class StatementReorderer(val program: Program,
|
||||
val targetType = assignment.target.inferType(program)
|
||||
|
||||
if(targetType.isArray && valueType.isArray) {
|
||||
if (assignment.value is ArrayLiteralValue) {
|
||||
if (assignment.value is ArrayLiteral) {
|
||||
errors.err("cannot assign array literal here, use separate assignment per element", assignment.position)
|
||||
} else {
|
||||
return copyArrayValue(assignment)
|
||||
@ -346,8 +369,10 @@ internal class StatementReorderer(val program: Program,
|
||||
val identifier = assign.target.identifier!!
|
||||
val targetVar = identifier.targetVarDecl(program)!!
|
||||
|
||||
if(targetVar.arraysize==null)
|
||||
if(targetVar.arraysize==null) {
|
||||
errors.err("array has no defined size", assign.position)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
if(assign.value !is IdentifierReference) {
|
||||
errors.err("invalid array value to assign to other array", assign.value.position)
|
||||
@ -367,11 +392,13 @@ internal class StatementReorderer(val program: Program,
|
||||
if(!errors.noErrors())
|
||||
return noModifications
|
||||
|
||||
val numelements = targetVar.arraysize!!.constIndex()!!
|
||||
val eltsize = program.memsizer.memorySize(ArrayToElementTypes.getValue(sourceVar.datatype))
|
||||
val memcopy = FunctionCallStatement(IdentifierReference(listOf("sys", "memcopy"), assign.position),
|
||||
mutableListOf(
|
||||
AddressOf(sourceIdent, assign.position),
|
||||
AddressOf(identifier, assign.position),
|
||||
NumericLiteralValue.optimalInteger(targetVar.arraysize!!.constIndex()!!, assign.position)
|
||||
NumericLiteral.optimalInteger(numelements*eltsize, assign.position)
|
||||
),
|
||||
true,
|
||||
assign.position
|
||||
@ -382,118 +409,130 @@ internal class StatementReorderer(val program: Program,
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
val function = functionCallStatement.target.targetStatement(program)!!
|
||||
checkUnusedReturnValues(functionCallStatement, function, program, errors)
|
||||
if(function is Subroutine) {
|
||||
if(function.inline)
|
||||
return noModifications
|
||||
return if(function.isAsmSubroutine)
|
||||
replaceCallAsmSubStatementWithGosub(function, functionCallStatement, parent)
|
||||
else
|
||||
replaceCallSubStatementWithGosub(function, functionCallStatement, parent)
|
||||
}
|
||||
return noModifications
|
||||
return replaceCallByGosub(functionCallStatement, parent, program, options)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal fun replaceCallByGosub(functionCallStatement: FunctionCallStatement,
|
||||
parent: Node,
|
||||
program: Program,
|
||||
options: CompilationOptions): Iterable<IAstModification> {
|
||||
val function = functionCallStatement.target.targetStatement(program)!!
|
||||
if(function is Subroutine) {
|
||||
if(function.inline)
|
||||
return emptyList()
|
||||
return if(function.isAsmSubroutine)
|
||||
replaceCallAsmSubStatementWithGosub(function, functionCallStatement, parent, options)
|
||||
else
|
||||
replaceCallSubStatementWithGosub(function, functionCallStatement, parent, program)
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
private fun replaceCallSubStatementWithGosub(function: Subroutine, call: FunctionCallStatement, parent: Node, program: Program): Iterable<IAstModification> {
|
||||
val noModifications = emptyList<IAstModification>()
|
||||
|
||||
if(function.parameters.isEmpty()) {
|
||||
// 0 params -> just GoSub
|
||||
return listOf(IAstModification.ReplaceNode(call, GoSub(null, call.target, null, call.position), parent))
|
||||
}
|
||||
|
||||
private fun replaceCallSubStatementWithGosub(function: Subroutine, call: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
if(function.parameters.isEmpty()) {
|
||||
// 0 params -> just GoSub
|
||||
return listOf(IAstModification.ReplaceNode(call, GoSub(null, call.target, null, call.position), parent))
|
||||
if(function.parameters.size==1) {
|
||||
if(function.parameters[0].type in IntegerDatatypes) {
|
||||
// optimization: 1 integer param is passed via register(s) directly, not by assignment to param variable
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
else if(function.parameters.size==2) {
|
||||
if(function.parameters[0].type in ByteDatatypes && function.parameters[1].type in ByteDatatypes) {
|
||||
// optimization: 2 simple byte param is passed via 2 registers directly, not by assignment to param variables
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
||||
if(function.parameters.size==1) {
|
||||
if(function.parameters[0].type in IntegerDatatypes) {
|
||||
// optimization: 1 integer param is passed via register(s) directly, not by assignment to param variable
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
else if(function.parameters.size==2) {
|
||||
if(function.parameters[0].type in ByteDatatypes && function.parameters[1].type in ByteDatatypes) {
|
||||
// optimization: 2 simple byte param is passed via 2 registers directly, not by assignment to param variables
|
||||
return noModifications
|
||||
val assignParams =
|
||||
function.parameters.zip(call.args).map {
|
||||
var argumentValue = it.second
|
||||
val paramIdentifier = IdentifierReference(function.scopedName + it.first.name, argumentValue.position)
|
||||
val argDt = argumentValue.inferType(program).getOrElse { throw FatalAstException("invalid dt") }
|
||||
if(argDt in ArrayDatatypes) {
|
||||
// pass the address of the array instead
|
||||
if(argumentValue is IdentifierReference)
|
||||
argumentValue = AddressOf(argumentValue, argumentValue.position)
|
||||
}
|
||||
Assignment(AssignTarget(paramIdentifier, null, null, argumentValue.position), argumentValue, AssignmentOrigin.PARAMETERASSIGN, argumentValue.position)
|
||||
}
|
||||
val scope = AnonymousScope(assignParams.toMutableList(), call.position)
|
||||
scope.statements += GoSub(null, call.target, null, call.position)
|
||||
return listOf(IAstModification.ReplaceNode(call, scope, parent))
|
||||
}
|
||||
|
||||
val assignParams =
|
||||
function.parameters.zip(call.args).map {
|
||||
var argumentValue = it.second
|
||||
val paramIdentifier = IdentifierReference(function.scopedName + it.first.name, argumentValue.position)
|
||||
val argDt = argumentValue.inferType(program).getOrElse { throw FatalAstException("invalid dt") }
|
||||
if(argDt in ArrayDatatypes) {
|
||||
// pass the address of the array instead
|
||||
if(argumentValue is IdentifierReference)
|
||||
argumentValue = AddressOf(argumentValue, argumentValue.position)
|
||||
}
|
||||
Assignment(AssignTarget(paramIdentifier, null, null, argumentValue.position), argumentValue, argumentValue.position)
|
||||
}
|
||||
val scope = AnonymousScope(assignParams.toMutableList(), call.position)
|
||||
private fun replaceCallAsmSubStatementWithGosub(function: Subroutine, call: FunctionCallStatement, parent: Node, options: CompilationOptions): Iterable<IAstModification> {
|
||||
val noModifications = emptyList<IAstModification>()
|
||||
|
||||
if(function.parameters.isEmpty()) {
|
||||
// 0 params -> just GoSub
|
||||
val scope = AnonymousScope(mutableListOf(), call.position)
|
||||
if(function.shouldSaveX()) {
|
||||
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rsavex"), call.position), mutableListOf(), true, call.position)
|
||||
}
|
||||
scope.statements += GoSub(null, call.target, null, call.position)
|
||||
if(function.shouldSaveX()) {
|
||||
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rrestorex"), call.position), mutableListOf(), true, call.position)
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(call, scope, parent))
|
||||
} else if(!options.compTarget.asmsubArgsHaveRegisterClobberRisk(call.args, function.asmParameterRegisters)) {
|
||||
// No register clobber risk, let the asmgen assign values to the registers directly.
|
||||
// this is more efficient than first evaluating them to the stack.
|
||||
// As complex expressions will be flagged as a clobber-risk, these will be simplified below.
|
||||
return noModifications
|
||||
} else {
|
||||
// clobber risk; evaluate the arguments on the CPU stack first (in reverse order)...
|
||||
val argOrder = options.compTarget.asmsubArgsEvalOrder(function)
|
||||
val scope = AnonymousScope(mutableListOf(), call.position)
|
||||
if(function.shouldSaveX()) {
|
||||
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rsavex"), call.position), mutableListOf(), true, call.position)
|
||||
}
|
||||
argOrder.reversed().forEach {
|
||||
val arg = call.args[it]
|
||||
val param = function.parameters[it]
|
||||
scope.statements += pushCall(arg, param.type, arg.position)
|
||||
}
|
||||
// ... and pop them off again into the registers.
|
||||
argOrder.forEach {
|
||||
val param = function.parameters[it]
|
||||
val targetName = function.scopedName + param.name
|
||||
scope.statements += popCall(targetName, param.type, call.position)
|
||||
}
|
||||
scope.statements += GoSub(null, call.target, null, call.position)
|
||||
if(function.shouldSaveX()) {
|
||||
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rrestorex"), call.position), mutableListOf(), true, call.position)
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(call, scope, parent))
|
||||
}
|
||||
|
||||
private fun replaceCallAsmSubStatementWithGosub(function: Subroutine, call: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
if(function.parameters.isEmpty()) {
|
||||
// 0 params -> just GoSub
|
||||
val scope = AnonymousScope(mutableListOf(), call.position)
|
||||
if(function.shouldSaveX()) {
|
||||
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rsavex"), call.position), mutableListOf(), true, call.position)
|
||||
}
|
||||
scope.statements += GoSub(null, call.target, null, call.position)
|
||||
if(function.shouldSaveX()) {
|
||||
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rrestorex"), call.position), mutableListOf(), true, call.position)
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(call, scope, parent))
|
||||
} else if(!options.compTarget.asmsubArgsHaveRegisterClobberRisk(call.args, function.asmParameterRegisters)) {
|
||||
// No register clobber risk, let the asmgen assign values to the registers directly.
|
||||
// this is more efficient than first evaluating them to the stack.
|
||||
// As complex expressions will be flagged as a clobber-risk, these will be simplified below.
|
||||
return noModifications
|
||||
} else {
|
||||
// clobber risk; evaluate the arguments on the CPU stack first (in reverse order)...
|
||||
val argOrder = options.compTarget.asmsubArgsEvalOrder(function)
|
||||
val scope = AnonymousScope(mutableListOf(), call.position)
|
||||
if(function.shouldSaveX()) {
|
||||
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rsavex"), call.position), mutableListOf(), true, call.position)
|
||||
}
|
||||
argOrder.reversed().forEach {
|
||||
val arg = call.args[it]
|
||||
val param = function.parameters[it]
|
||||
scope.statements += pushCall(arg, param.type, arg.position)
|
||||
}
|
||||
// ... and pop them off again into the registers.
|
||||
argOrder.forEach {
|
||||
val param = function.parameters[it]
|
||||
val targetName = function.scopedName + param.name
|
||||
scope.statements += popCall(targetName, param.type, call.position)
|
||||
}
|
||||
scope.statements += GoSub(null, call.target, null, call.position)
|
||||
if(function.shouldSaveX()) {
|
||||
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rrestorex"), call.position), mutableListOf(), true, call.position)
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(call, scope, parent))
|
||||
}
|
||||
}
|
||||
|
||||
private fun popCall(targetName: List<String>, dt: DataType, position: Position): FunctionCallStatement {
|
||||
return FunctionCallStatement(
|
||||
IdentifierReference(listOf(if(dt in ByteDatatypes) "pop" else "popw"), position),
|
||||
mutableListOf(IdentifierReference(targetName, position)),
|
||||
true, position
|
||||
)
|
||||
}
|
||||
|
||||
private fun pushCall(value: Expression, dt: DataType, position: Position): FunctionCallStatement {
|
||||
val pushvalue = when(dt) {
|
||||
DataType.UBYTE, DataType.UWORD -> value
|
||||
in PassByReferenceDatatypes -> value
|
||||
DataType.BYTE -> TypecastExpression(value, DataType.UBYTE, true, position)
|
||||
DataType.WORD -> TypecastExpression(value, DataType.UWORD, true, position)
|
||||
else -> throw FatalAstException("invalid dt $dt $value")
|
||||
}
|
||||
|
||||
return FunctionCallStatement(
|
||||
IdentifierReference(listOf(if(dt in ByteDatatypes) "push" else "pushw"), position),
|
||||
mutableListOf(pushvalue),
|
||||
true, position
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun popCall(targetName: List<String>, dt: DataType, position: Position): FunctionCallStatement {
|
||||
return FunctionCallStatement(
|
||||
IdentifierReference(listOf(if(dt in ByteDatatypes) "pop" else "popw"), position),
|
||||
mutableListOf(IdentifierReference(targetName, position)),
|
||||
true, position
|
||||
)
|
||||
}
|
||||
|
||||
private fun pushCall(value: Expression, dt: DataType, position: Position): FunctionCallStatement {
|
||||
val pushvalue = when(dt) {
|
||||
DataType.UBYTE, DataType.UWORD -> value
|
||||
in PassByReferenceDatatypes -> value
|
||||
DataType.BYTE -> TypecastExpression(value, DataType.UBYTE, true, position)
|
||||
DataType.WORD -> TypecastExpression(value, DataType.UWORD, true, position)
|
||||
else -> throw FatalAstException("invalid dt $dt $value")
|
||||
}
|
||||
|
||||
return FunctionCallStatement(
|
||||
IdentifierReference(listOf(if(dt in ByteDatatypes) "push" else "pushw"), position),
|
||||
mutableListOf(pushvalue),
|
||||
true, position
|
||||
)
|
||||
}
|
||||
|
@ -33,11 +33,9 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
if(valueDt isNotAssignableTo decl.datatype)
|
||||
return noModifications
|
||||
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
declValue,
|
||||
TypecastExpression(declValue, decl.datatype, true, declValue.position),
|
||||
decl
|
||||
))
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
addTypecastOrCastedValueModification(modifications, declValue, decl.datatype, decl)
|
||||
return modifications
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
@ -55,7 +53,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
val value = if(rightDt.isBytes) 256+leftCv.number else 65536+leftCv.number
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr.left,
|
||||
NumericLiteralValue(rightDt.getOr(DataType.UNDEFINED), value, expr.left.position),
|
||||
NumericLiteral(rightDt.getOr(DataType.UNDEFINED), value, expr.left.position),
|
||||
expr))
|
||||
}
|
||||
val rightCv = expr.right.constValue(program)
|
||||
@ -63,21 +61,43 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
val value = if(leftDt.isBytes) 256+rightCv.number else 65536+rightCv.number
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr.right,
|
||||
NumericLiteralValue(leftDt.getOr(DataType.UNDEFINED), value, expr.right.position),
|
||||
NumericLiteral(leftDt.getOr(DataType.UNDEFINED), value, expr.right.position),
|
||||
expr))
|
||||
}
|
||||
|
||||
if(leftDt istype DataType.BYTE && rightDt.oneOf(DataType.UBYTE, DataType.UWORD)) {
|
||||
// cast left to unsigned
|
||||
val cast = TypecastExpression(expr.left, rightDt.getOr(DataType.UNDEFINED), true, expr.left.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||
}
|
||||
if(leftDt istype DataType.WORD && rightDt.oneOf(DataType.UBYTE, DataType.UWORD)) {
|
||||
// cast left to unsigned
|
||||
val cast = TypecastExpression(expr.left, rightDt.getOr(DataType.UNDEFINED), true, expr.left.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||
}
|
||||
if(rightDt istype DataType.BYTE && leftDt.oneOf(DataType.UBYTE, DataType.UWORD)) {
|
||||
// cast right to unsigned
|
||||
val cast = TypecastExpression(expr.right, leftDt.getOr(DataType.UNDEFINED), true, expr.right.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, cast, expr))
|
||||
}
|
||||
if(rightDt istype DataType.WORD && leftDt.oneOf(DataType.UBYTE, DataType.UWORD)) {
|
||||
// cast right to unsigned
|
||||
val cast = TypecastExpression(expr.right, leftDt.getOr(DataType.UNDEFINED), true, expr.right.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, cast, expr))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// determine common datatype and add typecast as required to make left and right equal types
|
||||
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.getOr(DataType.UNDEFINED), rightDt.getOr(DataType.UNDEFINED), expr.left, expr.operator, expr.right)
|
||||
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.getOr(DataType.UNDEFINED), rightDt.getOr(DataType.UNDEFINED), expr.left, expr.right)
|
||||
if(toFix!=null) {
|
||||
return when {
|
||||
toFix===expr.left -> listOf(IAstModification.ReplaceNode(
|
||||
expr.left, TypecastExpression(expr.left, commonDt, true, expr.left.position), expr))
|
||||
toFix===expr.right -> listOf(IAstModification.ReplaceNode(
|
||||
expr.right, TypecastExpression(expr.right, commonDt, true, expr.right.position), expr))
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
when {
|
||||
toFix===expr.left -> addTypecastOrCastedValueModification(modifications, expr.left, commonDt, expr)
|
||||
toFix===expr.right -> addTypecastOrCastedValueModification(modifications, expr.right, commonDt, expr)
|
||||
else -> throw FatalAstException("confused binary expression side")
|
||||
}
|
||||
return modifications
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
@ -95,12 +115,11 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
if(valuetype in IterableDatatypes && targettype==DataType.UWORD)
|
||||
// special case, don't typecast STR/arrays to UWORD, we support those assignments "directly"
|
||||
return noModifications
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
assignment.value,
|
||||
TypecastExpression(assignment.value, targettype, true, assignment.value.position),
|
||||
assignment))
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
addTypecastOrCastedValueModification(modifications, assignment.value, targettype, assignment)
|
||||
return modifications
|
||||
} else {
|
||||
fun castLiteral(cvalue2: NumericLiteralValue): List<IAstModification.ReplaceNode> {
|
||||
fun castLiteral(cvalue2: NumericLiteral): List<IAstModification.ReplaceNode> {
|
||||
val cast = cvalue2.cast(targettype)
|
||||
return if(cast.isValid)
|
||||
listOf(IAstModification.ReplaceNode(assignment.value, cast.valueOrZero(), assignment))
|
||||
@ -135,7 +154,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
return afterFunctionCallArgs(functionCallStatement)
|
||||
}
|
||||
|
||||
override fun after(functionCallExpr: FunctionCallExpr, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
|
||||
return afterFunctionCallArgs(functionCallExpr)
|
||||
}
|
||||
|
||||
@ -154,25 +173,22 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
if (argtype isAssignableTo requiredType) {
|
||||
// don't need a cast for pass-by-reference types that are assigned to UWORD
|
||||
if(requiredType!=DataType.UWORD || argtype !in PassByReferenceDatatypes)
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[index],
|
||||
TypecastExpression(pair.second, requiredType, true, pair.second.position),
|
||||
call as Node)
|
||||
addTypecastOrCastedValueModification(modifications, pair.second, requiredType, call as Node)
|
||||
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
|
||||
// We allow STR/ARRAY values in place of UWORD parameters.
|
||||
// Take their address instead, UNLESS it's a str parameter in the containing subroutine
|
||||
val identifier = pair.second as? IdentifierReference
|
||||
if(identifier?.isSubroutineParameter(program)==false) {
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[index],
|
||||
identifier,
|
||||
AddressOf(identifier, pair.second.position),
|
||||
call as Node)
|
||||
}
|
||||
} else if(pair.second is NumericLiteralValue) {
|
||||
val cast = (pair.second as NumericLiteralValue).cast(requiredType)
|
||||
} else if(pair.second is NumericLiteral) {
|
||||
val cast = (pair.second as NumericLiteral).cast(requiredType)
|
||||
if(cast.isValid)
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[index],
|
||||
pair.second,
|
||||
cast.valueOrZero(),
|
||||
call as Node)
|
||||
}
|
||||
@ -189,10 +205,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
if (pair.first.possibleDatatypes.all { argtype != it }) {
|
||||
for (possibleType in pair.first.possibleDatatypes) {
|
||||
if (argtype isAssignableTo possibleType) {
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[index],
|
||||
TypecastExpression(pair.second, possibleType, true, pair.second.position),
|
||||
call as Node)
|
||||
addTypecastOrCastedValueModification(modifications, pair.second, possibleType, call as Node)
|
||||
break
|
||||
}
|
||||
else if(DataType.UWORD in pair.first.possibleDatatypes && argtype in PassByReferenceDatatypes) {
|
||||
@ -226,29 +239,36 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
else
|
||||
errors.err("integer implicitly converted to float but floating point is not enabled via options", typecast.position)
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
|
||||
// make sure the memory address is an uword
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
val dt = memread.addressExpression.inferType(program)
|
||||
if(dt.isKnown && dt.getOr(DataType.UWORD)!=DataType.UWORD) {
|
||||
val typecast = (memread.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)?.valueOrZero()
|
||||
?: TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
|
||||
return listOf(IAstModification.ReplaceNode(memread.addressExpression, typecast, memread))
|
||||
val castedValue = (memread.addressExpression as? NumericLiteral)?.cast(DataType.UWORD)?.valueOrZero()
|
||||
if(castedValue!=null)
|
||||
modifications += IAstModification.ReplaceNode(memread.addressExpression, castedValue, memread)
|
||||
else
|
||||
addTypecastOrCastedValueModification(modifications, memread.addressExpression, DataType.UWORD, memread)
|
||||
}
|
||||
return noModifications
|
||||
return modifications
|
||||
}
|
||||
|
||||
override fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> {
|
||||
// make sure the memory address is an uword
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
val dt = memwrite.addressExpression.inferType(program)
|
||||
if(dt.isKnown && dt.getOr(DataType.UWORD)!=DataType.UWORD) {
|
||||
val typecast = (memwrite.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)?.valueOrZero()
|
||||
?: TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
|
||||
return listOf(IAstModification.ReplaceNode(memwrite.addressExpression, typecast, memwrite))
|
||||
val castedValue = (memwrite.addressExpression as? NumericLiteral)?.cast(DataType.UWORD)?.valueOrZero()
|
||||
if(castedValue!=null)
|
||||
modifications += IAstModification.ReplaceNode(memwrite.addressExpression, castedValue, memwrite)
|
||||
else
|
||||
addTypecastOrCastedValueModification(modifications, memwrite.addressExpression, DataType.UWORD, memwrite)
|
||||
}
|
||||
return noModifications
|
||||
return modifications
|
||||
}
|
||||
|
||||
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
|
||||
@ -260,18 +280,37 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
val subReturnType = subroutine.returntypes.first()
|
||||
if (returnValue.inferType(program) istype subReturnType)
|
||||
return noModifications
|
||||
if (returnValue is NumericLiteralValue) {
|
||||
if (returnValue is NumericLiteral) {
|
||||
val cast = returnValue.cast(subroutine.returntypes.single())
|
||||
if(cast.isValid)
|
||||
returnStmt.value = cast.valueOrZero()
|
||||
} else {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
returnValue,
|
||||
TypecastExpression(returnValue, subReturnType, true, returnValue.position),
|
||||
returnStmt))
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
addTypecastOrCastedValueModification(modifications, returnValue, subReturnType, returnStmt)
|
||||
return modifications
|
||||
}
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun addTypecastOrCastedValueModification(
|
||||
modifications: MutableList<IAstModification>,
|
||||
expressionToCast: Expression,
|
||||
requiredType: DataType,
|
||||
parent: Node
|
||||
) {
|
||||
val sourceDt = expressionToCast.inferType(program).getOr(DataType.UNDEFINED)
|
||||
if(sourceDt == requiredType)
|
||||
return
|
||||
if(expressionToCast is NumericLiteral && expressionToCast.type!=DataType.FLOAT) { // refuse to automatically truncate floats
|
||||
val castedValue = expressionToCast.cast(requiredType)
|
||||
if (castedValue.isValid) {
|
||||
modifications += IAstModification.ReplaceNode(expressionToCast, castedValue.valueOrZero(), parent)
|
||||
return
|
||||
}
|
||||
}
|
||||
val cast = TypecastExpression(expressionToCast, requiredType, true, expressionToCast.position)
|
||||
modifications += IAstModification.ReplaceNode(expressionToCast, cast, parent)
|
||||
}
|
||||
}
|
||||
|
191
compiler/src/prog8/compiler/astprocessing/VariablesAndConsts.kt
Normal file
191
compiler/src/prog8/compiler/astprocessing/VariablesAndConsts.kt
Normal file
@ -0,0 +1,191 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.statements.Block
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.compilerinterface.IVariablesAndConsts
|
||||
|
||||
|
||||
internal class VariableExtractor: IAstVisitor {
|
||||
private val allBlockVars = mutableMapOf<Block, MutableSet<VarDecl>>()
|
||||
private val allBlockConsts = mutableMapOf<Block, MutableSet<VarDecl>>()
|
||||
private val allBlockMemoryvars = mutableMapOf<Block, MutableSet<VarDecl>>()
|
||||
private val allSubroutineVars = mutableMapOf<Subroutine, MutableSet<VarDecl>>()
|
||||
private val allSubroutineConsts = mutableMapOf<Subroutine, MutableSet<VarDecl>>()
|
||||
private val allSubroutineMemoryvars = mutableMapOf<Subroutine, MutableSet<VarDecl>>()
|
||||
|
||||
fun extractVars(program: Program): IVariablesAndConsts {
|
||||
this.visit(program)
|
||||
return VariablesAndConsts(
|
||||
allBlockVars, allBlockConsts, allBlockMemoryvars,
|
||||
allSubroutineVars, allSubroutineConsts, allSubroutineMemoryvars)
|
||||
}
|
||||
|
||||
override fun visit(decl: VarDecl) {
|
||||
val scope=decl.definingScope
|
||||
when (decl.type) {
|
||||
VarDeclType.VAR -> {
|
||||
when (scope) {
|
||||
is Block -> {
|
||||
val decls = allBlockVars[scope] ?: mutableSetOf()
|
||||
decls.add(decl)
|
||||
allBlockVars[scope] = decls
|
||||
}
|
||||
is Subroutine -> {
|
||||
val decls = allSubroutineVars[scope] ?: mutableSetOf()
|
||||
decls.add(decl)
|
||||
allSubroutineVars[scope] = decls
|
||||
}
|
||||
else -> {
|
||||
throw FatalAstException("var can only occur in subroutine or block scope")
|
||||
}
|
||||
}
|
||||
}
|
||||
VarDeclType.CONST -> {
|
||||
when(scope) {
|
||||
is Block -> {
|
||||
val decls = allBlockConsts[scope] ?: mutableSetOf()
|
||||
decls.add(decl)
|
||||
allBlockConsts[scope] = decls
|
||||
}
|
||||
is Subroutine -> {
|
||||
val decls = allSubroutineConsts[scope] ?: mutableSetOf()
|
||||
decls.add(decl)
|
||||
allSubroutineConsts[scope] = decls
|
||||
}
|
||||
else -> {
|
||||
throw FatalAstException("var can only occur in subroutine or block scope")
|
||||
}
|
||||
}
|
||||
}
|
||||
VarDeclType.MEMORY -> {
|
||||
when(scope) {
|
||||
is Block -> {
|
||||
val decls = allBlockMemoryvars[scope] ?: mutableSetOf()
|
||||
decls.add(decl)
|
||||
allBlockMemoryvars[scope] = decls
|
||||
}
|
||||
is Subroutine -> {
|
||||
val decls = allSubroutineMemoryvars[scope] ?: mutableSetOf()
|
||||
decls.add(decl)
|
||||
allSubroutineMemoryvars[scope] = decls
|
||||
}
|
||||
else -> {
|
||||
throw FatalAstException("var can only occur in subroutine or block scope")
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
throw FatalAstException("invalid var type")
|
||||
}
|
||||
}
|
||||
super.visit(decl)
|
||||
}
|
||||
}
|
||||
|
||||
internal class VariablesAndConsts (
|
||||
astBlockVars: Map<Block, Set<VarDecl>>,
|
||||
astBlockConsts: Map<Block, Set<VarDecl>>,
|
||||
astBlockMemvars: Map<Block, Set<VarDecl>>,
|
||||
astSubroutineVars: Map<Subroutine, Set<VarDecl>>,
|
||||
astSubroutineConsts: Map<Subroutine, Set<VarDecl>>,
|
||||
astSubroutineMemvars: Map<Subroutine, Set<VarDecl>>
|
||||
) : IVariablesAndConsts
|
||||
{
|
||||
override val blockVars: Map<Block, Set<IVariablesAndConsts.StaticVariable>>
|
||||
override val blockConsts: Map<Block, Set<IVariablesAndConsts.ConstantNumberSymbol>>
|
||||
override val blockMemvars: Map<Block, Set<IVariablesAndConsts.MemoryMappedVariable>>
|
||||
override val subroutineVars: Map<Subroutine, Set<IVariablesAndConsts.StaticVariable>>
|
||||
override val subroutineConsts: Map<Subroutine, Set<IVariablesAndConsts.ConstantNumberSymbol>>
|
||||
override val subroutineMemvars: Map<Subroutine, Set<IVariablesAndConsts.MemoryMappedVariable>>
|
||||
|
||||
private val bv = astBlockVars.keys.associateWith { mutableSetOf<IVariablesAndConsts.StaticVariable>() }.toMutableMap()
|
||||
private val bc = astBlockConsts.keys.associateWith { mutableSetOf<IVariablesAndConsts.ConstantNumberSymbol>() }
|
||||
private val bmv = astBlockMemvars.keys.associateWith { mutableSetOf<IVariablesAndConsts.MemoryMappedVariable>() }
|
||||
private val sv = astSubroutineVars.keys.associateWith { mutableSetOf<IVariablesAndConsts.StaticVariable>() }
|
||||
private val sc = astSubroutineConsts.keys.associateWith { mutableSetOf<IVariablesAndConsts.ConstantNumberSymbol>() }
|
||||
private val smv = astSubroutineMemvars.keys.associateWith { mutableSetOf<IVariablesAndConsts.MemoryMappedVariable>() }
|
||||
|
||||
init {
|
||||
astBlockVars.forEach { (block, decls) ->
|
||||
val vars = bv.getValue(block)
|
||||
vars.addAll(decls.map { toStatic(it) })
|
||||
}
|
||||
astBlockConsts.forEach { (block, decls) ->
|
||||
bc.getValue(block).addAll(
|
||||
decls.map {
|
||||
IVariablesAndConsts.ConstantNumberSymbol(
|
||||
it.datatype,
|
||||
it.scopedName,
|
||||
(it.value as NumericLiteral).number,
|
||||
it.position
|
||||
)
|
||||
})
|
||||
}
|
||||
astBlockMemvars.forEach { (block, decls) ->
|
||||
val vars = bmv.getValue(block)
|
||||
for(decl in decls) {
|
||||
// make sure the 'stubs' for the scratch variables in zeropage are not included as normal variables
|
||||
if(!decl.name.startsWith("P8ZP_SCRATCH_")) {
|
||||
vars.add(
|
||||
IVariablesAndConsts.MemoryMappedVariable(
|
||||
decl.datatype,
|
||||
decl.scopedName,
|
||||
(decl.value as NumericLiteral).number.toUInt(),
|
||||
decl.position
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
astSubroutineVars.forEach { (sub, decls) ->
|
||||
val vars = sv.getValue(sub)
|
||||
vars.addAll(decls.map { toStatic(it) })
|
||||
}
|
||||
astSubroutineConsts.forEach { (sub, decls) ->
|
||||
sc.getValue(sub).addAll(
|
||||
decls.map {
|
||||
IVariablesAndConsts.ConstantNumberSymbol(
|
||||
it.datatype,
|
||||
it.scopedName,
|
||||
(it.value as NumericLiteral).number,
|
||||
it.position
|
||||
)
|
||||
})
|
||||
}
|
||||
astSubroutineMemvars.forEach { (sub, decls) ->
|
||||
smv.getValue(sub).addAll(
|
||||
decls.map {
|
||||
IVariablesAndConsts.MemoryMappedVariable(
|
||||
it.datatype,
|
||||
it.scopedName,
|
||||
(it.value as NumericLiteral).number.toUInt(),
|
||||
it.position
|
||||
)
|
||||
})
|
||||
}
|
||||
blockVars = bv
|
||||
blockConsts = bc
|
||||
blockMemvars = bmv
|
||||
subroutineVars = sv
|
||||
subroutineConsts = sc
|
||||
subroutineMemvars = smv
|
||||
}
|
||||
|
||||
private fun toStatic(decl: VarDecl) =
|
||||
IVariablesAndConsts.StaticVariable(decl.datatype, decl.scopedName, decl.definingScope, decl.value, decl.arraysize?.constIndex(), decl.zeropage, decl.position)
|
||||
|
||||
override fun addIfUnknown(definingBlock: Block, variable: VarDecl) {
|
||||
var blockvars = bv[definingBlock]
|
||||
if(blockvars==null) {
|
||||
blockvars = mutableSetOf()
|
||||
bv[definingBlock] = blockvars
|
||||
}
|
||||
blockvars.add(toStatic(variable))
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.IStatementContainer
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
@ -7,17 +8,16 @@ import prog8.ast.base.ArrayDatatypes
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.statements.AnonymousScope
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compilerinterface.CompilationOptions
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
|
||||
|
||||
internal class VariousCleanups(val program: Program, val errors: IErrorReporter): AstWalker() {
|
||||
|
||||
override fun before(nop: Nop, parent: Node): Iterable<IAstModification> {
|
||||
return listOf(IAstModification.Remove(nop, parent as IStatementContainer))
|
||||
}
|
||||
internal class VariousCleanups(val program: Program, val errors: IErrorReporter, val options: CompilationOptions): AstWalker() {
|
||||
|
||||
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
return if(parent is IStatementContainer)
|
||||
@ -38,8 +38,8 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter)
|
||||
}
|
||||
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(typecast.expression is NumericLiteralValue) {
|
||||
val value = (typecast.expression as NumericLiteralValue).cast(typecast.type)
|
||||
if(typecast.expression is NumericLiteral) {
|
||||
val value = (typecast.expression as NumericLiteral).cast(typecast.type)
|
||||
if(value.isValid)
|
||||
return listOf(IAstModification.ReplaceNode(typecast, value.valueOrZero(), parent))
|
||||
}
|
||||
@ -62,7 +62,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter)
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
val nextAssign = assignment.nextSibling() as? Assignment
|
||||
if(nextAssign!=null && nextAssign.target.isSameAs(assignment.target, program)) {
|
||||
if(nextAssign.value isSameAs assignment.value)
|
||||
if(nextAssign.value isSameAs assignment.value && assignment.value !is IFunctionCall) // don't remove function calls even when they're duplicates
|
||||
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
|
||||
}
|
||||
|
||||
@ -98,7 +98,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter)
|
||||
val leftBinExpr = expr.left as? BinaryExpression
|
||||
val rightBinExpr = expr.right as? BinaryExpression
|
||||
if(leftBinExpr!=null && leftBinExpr.operator=="==" && rightBinExpr!=null && rightBinExpr.operator=="==") {
|
||||
if(leftBinExpr.right is NumericLiteralValue && rightBinExpr.right is NumericLiteralValue) {
|
||||
if(leftBinExpr.right is NumericLiteral && rightBinExpr.right is NumericLiteral) {
|
||||
if(leftBinExpr.left isSameAs rightBinExpr.left)
|
||||
errors.warn("consider using 'in' or 'when' to test for multiple values", expr.position)
|
||||
}
|
||||
@ -130,7 +130,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter)
|
||||
|
||||
override fun after(containment: ContainmentCheck, parent: Node): Iterable<IAstModification> {
|
||||
// replace trivial containment checks with just false or a single comparison
|
||||
fun replaceWithEquals(value: NumericLiteralValue): Iterable<IAstModification> {
|
||||
fun replaceWithEquals(value: NumericLiteral): Iterable<IAstModification> {
|
||||
errors.warn("containment could be written as just a single comparison", containment.position)
|
||||
val equals = BinaryExpression(containment.element, "==", value, containment.position)
|
||||
return listOf(IAstModification.ReplaceNode(containment, equals, parent))
|
||||
@ -138,7 +138,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter)
|
||||
|
||||
fun replaceWithFalse(): Iterable<IAstModification> {
|
||||
errors.warn("condition is always false", containment.position)
|
||||
return listOf(IAstModification.ReplaceNode(containment, NumericLiteralValue.fromBoolean(false, containment.position), parent))
|
||||
return listOf(IAstModification.ReplaceNode(containment, NumericLiteral.fromBoolean(false, containment.position), parent))
|
||||
}
|
||||
|
||||
fun checkArray(array: Array<Expression>): Iterable<IAstModification> {
|
||||
@ -152,51 +152,55 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
fun checkString(stringVal: StringLiteralValue): Iterable<IAstModification> {
|
||||
fun checkString(stringVal: StringLiteral): Iterable<IAstModification> {
|
||||
if(stringVal.value.isEmpty())
|
||||
return replaceWithFalse()
|
||||
if(stringVal.value.length==1) {
|
||||
val string = program.encoding.encodeString(stringVal.value, stringVal.altEncoding)
|
||||
return replaceWithEquals(NumericLiteralValue(DataType.UBYTE, string[0].toDouble(), stringVal.position))
|
||||
val string = program.encoding.encodeString(stringVal.value, stringVal.encoding)
|
||||
return replaceWithEquals(NumericLiteral(DataType.UBYTE, string[0].toDouble(), stringVal.position))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
when(containment.iterable) {
|
||||
is ArrayLiteralValue -> {
|
||||
val array = (containment.iterable as ArrayLiteralValue).value
|
||||
is ArrayLiteral -> {
|
||||
val array = (containment.iterable as ArrayLiteral).value
|
||||
return checkArray(array)
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val variable = (containment.iterable as IdentifierReference).targetVarDecl(program)!!
|
||||
when(variable.datatype) {
|
||||
DataType.STR -> {
|
||||
val stringVal = (variable.value as StringLiteralValue)
|
||||
val stringVal = (variable.value as StringLiteral)
|
||||
return checkString(stringVal)
|
||||
}
|
||||
in ArrayDatatypes -> {
|
||||
val array = (variable.value as ArrayLiteralValue).value
|
||||
val array = (variable.value as ArrayLiteral).value
|
||||
return checkArray(array)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
is RangeExpr -> {
|
||||
val constValues = (containment.iterable as RangeExpr).toConstantIntegerRange()
|
||||
is RangeExpression -> {
|
||||
val constValues = (containment.iterable as RangeExpression).toConstantIntegerRange()
|
||||
if(constValues!=null) {
|
||||
if (constValues.isEmpty())
|
||||
return replaceWithFalse()
|
||||
if (constValues.count()==1)
|
||||
return replaceWithEquals(NumericLiteralValue.optimalNumeric(constValues.first, containment.position))
|
||||
return replaceWithEquals(NumericLiteral.optimalNumeric(constValues.first, containment.position))
|
||||
}
|
||||
}
|
||||
is StringLiteralValue -> {
|
||||
val stringVal = containment.iterable as StringLiteralValue
|
||||
is StringLiteral -> {
|
||||
val stringVal = containment.iterable as StringLiteral
|
||||
return checkString(stringVal)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
return replaceCallByGosub(functionCallStatement, parent, program, options)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.FunctionCallExpr
|
||||
import prog8.ast.expressions.FunctionCallExpression
|
||||
import prog8.ast.expressions.TypecastExpression
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
@ -13,7 +13,7 @@ import prog8.compilerinterface.InternalCompilerException
|
||||
|
||||
internal class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
||||
|
||||
override fun visit(functionCallExpr: FunctionCallExpr) {
|
||||
override fun visit(functionCallExpr: FunctionCallExpression) {
|
||||
val error = checkTypes(functionCallExpr as IFunctionCall, program)
|
||||
if(error!=null)
|
||||
throw InternalCompilerException(error)
|
||||
|
@ -2,13 +2,6 @@ package prog8tests
|
||||
|
||||
import com.github.michaelbull.result.getErrorOrElse
|
||||
import com.github.michaelbull.result.getOrElse
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.internedStringsModuleName
|
||||
import prog8.compiler.ModuleImporter
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
import prog8.parser.ParseError
|
||||
import prog8.parser.SourceCode
|
||||
import kotlin.io.path.*
|
||||
import io.kotest.assertions.fail
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.assertions.withClue
|
||||
@ -16,11 +9,14 @@ import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.collections.shouldBeIn
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.internedStringsModuleName
|
||||
import prog8.compiler.ModuleImporter
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
import prog8.parser.ParseError
|
||||
import prog8.parser.SourceCode
|
||||
import prog8tests.helpers.*
|
||||
import prog8tests.helpers.DummyFunctions
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import prog8tests.helpers.DummyStringEncoder
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import kotlin.io.path.*
|
||||
|
||||
|
||||
class TestModuleImporter: FunSpec({
|
||||
@ -71,7 +67,7 @@ class TestModuleImporter: FunSpec({
|
||||
val searchIn = Path(".", "$srcPathRel").invariantSeparatorsPathString
|
||||
val importer = makeImporter(null, searchIn)
|
||||
|
||||
shouldThrow<AccessDeniedException> { importer.importModule(srcPathRel) }
|
||||
shouldThrow<FileSystemException> { importer.importModule(srcPathRel) }
|
||||
.let {
|
||||
withClue(".file should be normalized") {
|
||||
"${it.file}" shouldBe "${it.file.normalize()}"
|
||||
@ -82,7 +78,7 @@ class TestModuleImporter: FunSpec({
|
||||
}
|
||||
program.modules.size shouldBe 1
|
||||
|
||||
shouldThrow<AccessDeniedException> { importer.importModule(srcPathAbs) }
|
||||
shouldThrow<FileSystemException> { importer.importModule(srcPathAbs) }
|
||||
.let {
|
||||
withClue(".file should be normalized") {
|
||||
"${it.file}" shouldBe "${it.file.normalize()}"
|
||||
|
@ -1,14 +1,6 @@
|
||||
package prog8tests
|
||||
|
||||
import io.kotest.core.config.AbstractProjectConfig
|
||||
import io.kotest.core.listeners.Listener
|
||||
import io.kotest.core.listeners.TestListener
|
||||
import io.kotest.core.spec.Spec
|
||||
import io.kotest.extensions.system.NoSystemErrListener
|
||||
import io.kotest.extensions.system.NoSystemOutListener
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.PrintStream
|
||||
import kotlin.math.max
|
||||
|
||||
object ProjectConfig : AbstractProjectConfig() {
|
||||
override val parallelism = 2 // max(2, Runtime.getRuntime().availableProcessors() / 2)
|
||||
|
@ -5,6 +5,7 @@ import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import prog8.codegen.target.C64Target
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.assertFailure
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import prog8tests.helpers.compileText
|
||||
|
||||
@ -24,10 +25,76 @@ class TestAstChecks: FunSpec({
|
||||
}
|
||||
"""
|
||||
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
|
||||
compileText(C64Target, true, text, writeAssembly = true, errors=errors).assertSuccess()
|
||||
compileText(C64Target(), true, text, writeAssembly = true, errors=errors).assertSuccess()
|
||||
errors.errors.size shouldBe 0
|
||||
errors.warnings.size shouldBe 2
|
||||
errors.warnings[0] shouldContain "converted to float"
|
||||
errors.warnings[1] shouldContain "converted to float"
|
||||
}
|
||||
|
||||
test("can't assign label or subroutine without using address-of") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
|
||||
label:
|
||||
uword @shared addr
|
||||
addr = label
|
||||
addr = thing
|
||||
addr = &label
|
||||
addr = &thing
|
||||
}
|
||||
|
||||
sub thing() {
|
||||
}
|
||||
}
|
||||
"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(C64Target(), true, text, writeAssembly = true, errors=errors).assertFailure()
|
||||
errors.errors.size shouldBe 2
|
||||
errors.warnings.size shouldBe 0
|
||||
errors.errors[0] shouldContain ":7:28) assignment value is invalid"
|
||||
errors.errors[1] shouldContain ":8:28) assignment value is invalid"
|
||||
}
|
||||
|
||||
test("can't do str or array expression without using address-of") {
|
||||
val text = """
|
||||
%import textio
|
||||
main {
|
||||
sub start() {
|
||||
ubyte[] array = [1,2,3,4]
|
||||
str s1 = "test"
|
||||
ubyte ff = 1
|
||||
txt.print(s1+ff)
|
||||
txt.print(array+ff)
|
||||
txt.print_uwhex(s1+ff, true)
|
||||
txt.print_uwhex(array+ff, true)
|
||||
}
|
||||
}
|
||||
"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(C64Target(), false, text, writeAssembly = false, errors=errors).assertFailure()
|
||||
errors.errors.filter { it.contains("missing &") }.size shouldBe 4
|
||||
}
|
||||
|
||||
test("str or array expression with address-of") {
|
||||
val text = """
|
||||
%import textio
|
||||
main {
|
||||
sub start() {
|
||||
ubyte[] array = [1,2,3,4]
|
||||
str s1 = "test"
|
||||
ubyte ff = 1
|
||||
txt.print(&s1+ff)
|
||||
txt.print(&array+ff)
|
||||
txt.print_uwhex(&s1+ff, true)
|
||||
txt.print_uwhex(&array+ff, true)
|
||||
; also good:
|
||||
ff = (s1 == "derp")
|
||||
ff = (s1 != "derp")
|
||||
}
|
||||
}
|
||||
"""
|
||||
compileText(C64Target(), false, text, writeAssembly = false).assertSuccess()
|
||||
}
|
||||
})
|
||||
|
@ -2,15 +2,18 @@ package prog8tests
|
||||
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.maps.shouldContainKey
|
||||
import io.kotest.matchers.ints.shouldBeGreaterThanOrEqual
|
||||
import io.kotest.matchers.maps.shouldNotContainKey
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.statements.Block
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.codegen.target.C64Target
|
||||
import prog8.compilerinterface.CallGraph
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import prog8tests.helpers.compileText
|
||||
import prog8.parser.Prog8Parser.parseModule
|
||||
import prog8.parser.SourceCode
|
||||
import prog8tests.helpers.*
|
||||
|
||||
class TestCallgraph: FunSpec({
|
||||
test("testGraphForEmptySubs") {
|
||||
@ -23,7 +26,7 @@ class TestCallgraph: FunSpec({
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, false, sourcecode).assertSuccess()
|
||||
val result = compileText(C64Target(), false, sourcecode).assertSuccess()
|
||||
val graph = CallGraph(result.program)
|
||||
|
||||
graph.imports.size shouldBe 1
|
||||
@ -52,7 +55,7 @@ class TestCallgraph: FunSpec({
|
||||
}
|
||||
}
|
||||
|
||||
test("testGraphForEmptyButReferencedSub") {
|
||||
test("reference to empty sub") {
|
||||
val sourcecode = """
|
||||
%import string
|
||||
main {
|
||||
@ -64,7 +67,7 @@ class TestCallgraph: FunSpec({
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, false, sourcecode).assertSuccess()
|
||||
val result = compileText(C64Target(), false, sourcecode).assertSuccess()
|
||||
val graph = CallGraph(result.program)
|
||||
|
||||
graph.imports.size shouldBe 1
|
||||
@ -82,17 +85,144 @@ class TestCallgraph: FunSpec({
|
||||
val startSub = mainBlock.statements.filterIsInstance<Subroutine>().single{it.name=="start"}
|
||||
val emptySub = mainBlock.statements.filterIsInstance<Subroutine>().single{it.name=="empty"}
|
||||
|
||||
withClue("start 'calls' (references) empty") {
|
||||
graph.calls shouldContainKey startSub
|
||||
}
|
||||
graph.calls shouldNotContainKey startSub
|
||||
graph.calledBy shouldNotContainKey emptySub
|
||||
withClue("empty doesn't call anything") {
|
||||
graph.calls shouldNotContainKey emptySub
|
||||
}
|
||||
withClue("empty gets 'called'") {
|
||||
graph.calledBy shouldContainKey emptySub
|
||||
}
|
||||
withClue( "start doesn't get called (except as entrypoint ofc.)") {
|
||||
graph.calledBy shouldNotContainKey startSub
|
||||
}
|
||||
}
|
||||
|
||||
test("allIdentifiers separates for different positions of the IdentifierReferences") {
|
||||
val sourcecode = """
|
||||
main {
|
||||
sub start() {
|
||||
uword x1 = &empty
|
||||
uword x2 = &empty
|
||||
empty()
|
||||
}
|
||||
sub empty() {
|
||||
%asm {{
|
||||
nop
|
||||
}}
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target(), false, sourcecode).assertSuccess()
|
||||
val graph = CallGraph(result.program)
|
||||
graph.allIdentifiers.size shouldBeGreaterThanOrEqual 9
|
||||
val empties = graph.allIdentifiers.keys.filter { it.nameInSource==listOf("empty") }
|
||||
empties.size shouldBe 3
|
||||
empties[0].position.line shouldBe 4
|
||||
empties[1].position.line shouldBe 5
|
||||
empties[2].position.line shouldBe 6
|
||||
}
|
||||
|
||||
test("checking block and subroutine names usage in assembly code") {
|
||||
val source = """
|
||||
main {
|
||||
sub start() {
|
||||
%asm {{
|
||||
lda #<blockname
|
||||
lda #<blockname.subroutine
|
||||
correctlabel:
|
||||
nop
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
blockname {
|
||||
sub subroutine() {
|
||||
@(1000) = 0
|
||||
}
|
||||
|
||||
sub correctlabel() {
|
||||
@(1000) = 0
|
||||
}
|
||||
}
|
||||
|
||||
; all block and subroutines below should NOT be found in asm because they're only substrings of the names in there
|
||||
locknam {
|
||||
sub rout() {
|
||||
@(1000) = 0
|
||||
}
|
||||
|
||||
sub orrectlab() {
|
||||
@(1000) = 0
|
||||
}
|
||||
}"""
|
||||
val module = parseModule(SourceCode.Text(source))
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
program.addModule(module)
|
||||
val callgraph = CallGraph(program)
|
||||
val blockMain = program.allBlocks.single { it.name=="main" }
|
||||
val blockBlockname = program.allBlocks.single { it.name=="blockname" }
|
||||
val blockLocknam = program.allBlocks.single { it.name=="locknam" }
|
||||
val subStart = blockMain.statements.filterIsInstance<Subroutine>().single { it.name == "start" }
|
||||
val subSubroutine = blockBlockname.statements.filterIsInstance<Subroutine>().single { it.name == "subroutine" }
|
||||
val subCorrectlabel = blockBlockname.statements.filterIsInstance<Subroutine>().single { it.name == "correctlabel" }
|
||||
val subRout = blockLocknam.statements.filterIsInstance<Subroutine>().single { it.name == "rout" }
|
||||
val subOrrectlab = blockLocknam.statements.filterIsInstance<Subroutine>().single { it.name == "orrectlab" }
|
||||
callgraph.unused(blockMain) shouldBe false
|
||||
callgraph.unused(blockBlockname) shouldBe false
|
||||
callgraph.unused(blockLocknam) shouldBe true
|
||||
callgraph.unused(subStart) shouldBe false
|
||||
callgraph.unused(subSubroutine) shouldBe false
|
||||
callgraph.unused(subCorrectlabel) shouldBe false
|
||||
callgraph.unused(subRout) shouldBe true
|
||||
callgraph.unused(subOrrectlab) shouldBe true
|
||||
}
|
||||
|
||||
test("recursion detection") {
|
||||
val source="""
|
||||
main {
|
||||
sub start() {
|
||||
recurse1()
|
||||
}
|
||||
sub recurse1() {
|
||||
recurse2()
|
||||
}
|
||||
sub recurse2() {
|
||||
start()
|
||||
}
|
||||
}"""
|
||||
val module = parseModule(SourceCode.Text(source))
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
program.addModule(module)
|
||||
val callgraph = CallGraph(program)
|
||||
val errors = ErrorReporterForTests()
|
||||
callgraph.checkRecursiveCalls(errors)
|
||||
errors.errors.size shouldBe 0
|
||||
errors.warnings.size shouldBe 4
|
||||
errors.warnings[0] shouldContain "contains recursive subroutine calls"
|
||||
errors.warnings[1] shouldContain "start at"
|
||||
errors.warnings[2] shouldContain "recurse1 at"
|
||||
errors.warnings[3] shouldContain "recurse2 at"
|
||||
}
|
||||
|
||||
test("no recursion warning if reference isn't a call") {
|
||||
val source="""
|
||||
main {
|
||||
sub start() {
|
||||
recurse1()
|
||||
}
|
||||
sub recurse1() {
|
||||
recurse2()
|
||||
}
|
||||
sub recurse2() {
|
||||
uword @shared address = &start
|
||||
}
|
||||
}"""
|
||||
val module = parseModule(SourceCode.Text(source))
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
program.addModule(module)
|
||||
val callgraph = CallGraph(program)
|
||||
val errors = ErrorReporterForTests()
|
||||
callgraph.checkRecursiveCalls(errors)
|
||||
errors.errors.size shouldBe 0
|
||||
errors.warnings.size shouldBe 0
|
||||
}
|
||||
})
|
||||
|
@ -9,9 +9,9 @@ import prog8.ast.IFunctionCall
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.codegen.target.Cx16Target
|
||||
import prog8.compilerinterface.Encoding
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import prog8tests.helpers.compileText
|
||||
|
||||
@ -24,7 +24,7 @@ import prog8tests.helpers.compileText
|
||||
class TestCompilerOnCharLit: FunSpec({
|
||||
|
||||
test("testCharLitAsRomsubArg") {
|
||||
val platform = Cx16Target
|
||||
val platform = Cx16Target()
|
||||
val result = compileText(platform, false, """
|
||||
main {
|
||||
romsub ${"$"}FFD2 = chrout(ubyte ch @ A)
|
||||
@ -39,15 +39,15 @@ class TestCompilerOnCharLit: FunSpec({
|
||||
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]
|
||||
|
||||
withClue("char literal should have been replaced by ubyte literal") {
|
||||
funCall.args[0] shouldBe instanceOf<NumericLiteralValue>()
|
||||
funCall.args[0] shouldBe instanceOf<NumericLiteral>()
|
||||
}
|
||||
val arg = funCall.args[0] as NumericLiteralValue
|
||||
val arg = funCall.args[0] as NumericLiteral
|
||||
arg.type shouldBe DataType.UBYTE
|
||||
arg.number shouldBe platform.encodeString("\n", false)[0].toDouble()
|
||||
arg.number shouldBe platform.encodeString("\n", Encoding.PETSCII)[0].toDouble()
|
||||
}
|
||||
|
||||
test("testCharVarAsRomsubArg") {
|
||||
val platform = Cx16Target
|
||||
val platform = Cx16Target()
|
||||
val result = compileText(platform, false, """
|
||||
main {
|
||||
romsub ${"$"}FFD2 = chrout(ubyte ch @ A)
|
||||
@ -76,18 +76,18 @@ class TestCompilerOnCharLit: FunSpec({
|
||||
withClue("initializer value should have been moved to separate assignment"){
|
||||
decl.value shouldBe null
|
||||
}
|
||||
val assignInitialValue = decl.nextSibling() as Assignment
|
||||
val assignInitialValue = decl.findInitializer(program)!!
|
||||
assignInitialValue.target.identifier!!.nameInSource shouldBe listOf("ch")
|
||||
withClue("char literal should have been replaced by ubyte literal") {
|
||||
assignInitialValue.value shouldBe instanceOf<NumericLiteralValue>()
|
||||
assignInitialValue.value shouldBe instanceOf<NumericLiteral>()
|
||||
}
|
||||
val initializerValue = assignInitialValue.value as NumericLiteralValue
|
||||
val initializerValue = assignInitialValue.value as NumericLiteral
|
||||
initializerValue.type shouldBe DataType.UBYTE
|
||||
initializerValue.number shouldBe platform.encodeString("\n", false)[0].toDouble()
|
||||
initializerValue.number shouldBe platform.encodeString("\n", Encoding.PETSCII)[0].toDouble()
|
||||
}
|
||||
|
||||
test("testCharConstAsRomsubArg") {
|
||||
val platform = Cx16Target
|
||||
val platform = Cx16Target()
|
||||
val result = compileText(platform, false, """
|
||||
main {
|
||||
romsub ${"$"}FFD2 = chrout(ubyte ch @ A)
|
||||
@ -108,10 +108,10 @@ class TestCompilerOnCharLit: FunSpec({
|
||||
val decl = arg.targetVarDecl(program)!!
|
||||
decl.type shouldBe VarDeclType.CONST
|
||||
decl.datatype shouldBe DataType.UBYTE
|
||||
(decl.value as NumericLiteralValue).number shouldBe platform.encodeString("\n", false)[0]
|
||||
(decl.value as NumericLiteral).number shouldBe platform.encodeString("\n", Encoding.PETSCII)[0]
|
||||
}
|
||||
is NumericLiteralValue -> {
|
||||
arg.number shouldBe platform.encodeString("\n", false)[0].toDouble()
|
||||
is NumericLiteral -> {
|
||||
arg.number shouldBe platform.encodeString("\n", Encoding.PETSCII)[0].toDouble()
|
||||
}
|
||||
else -> fail("invalid arg type") // funCall.args[0] shouldBe instanceOf<IdentifierReference>() // make test fail
|
||||
}
|
||||
|
@ -1,14 +1,13 @@
|
||||
package prog8tests
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import prog8.codegen.target.C64Target
|
||||
import prog8.codegen.target.Cx16Target
|
||||
import prog8.compiler.CompilationResult
|
||||
import prog8.compiler.CompilerArguments
|
||||
import prog8.compiler.compileProgram
|
||||
import prog8.codegen.target.C64Target
|
||||
import prog8.codegen.target.Cx16Target
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
import prog8tests.helpers.*
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.absolute
|
||||
import kotlin.io.path.exists
|
||||
@ -32,6 +31,7 @@ private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilat
|
||||
slowCodegenWarnings = false,
|
||||
quietAssembler = true,
|
||||
asmListfile = false,
|
||||
experimentalCodegen = false,
|
||||
compilationTarget = target.name,
|
||||
outputDir = outputDir
|
||||
)
|
||||
@ -40,10 +40,10 @@ private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilat
|
||||
|
||||
private fun prepareTestFiles(source: String, optimize: Boolean, target: ICompilationTarget): Pair<String, Path> {
|
||||
val searchIn = mutableListOf(examplesDir)
|
||||
if (target == Cx16Target) {
|
||||
if (target is Cx16Target) {
|
||||
searchIn.add(0, assumeDirectory(examplesDir, "cx16"))
|
||||
}
|
||||
val filepath = searchIn
|
||||
val filepath = searchIn.asSequence()
|
||||
.map { it.resolve("$source.p8") }
|
||||
.map { it.normalize().absolute() }
|
||||
.map { workingDir.relativize(it) }
|
||||
@ -72,9 +72,10 @@ class TestCompilerOnExamplesC64: FunSpec({
|
||||
|
||||
onlyC64.forEach {
|
||||
val (source, optimize) = it
|
||||
val (displayName, filepath) = prepareTestFiles(source, optimize, C64Target)
|
||||
val target = C64Target()
|
||||
val (displayName, filepath) = prepareTestFiles(source, optimize, target)
|
||||
test(displayName) {
|
||||
compileTheThing(filepath, optimize, C64Target).assertSuccess()
|
||||
compileTheThing(filepath, optimize, target).assertSuccess()
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -103,9 +104,10 @@ class TestCompilerOnExamplesCx16: FunSpec({
|
||||
|
||||
onlyCx16.forEach {
|
||||
val (source, optimize) = it
|
||||
val (displayName, filepath) = prepareTestFiles(source, optimize, Cx16Target)
|
||||
val target = Cx16Target()
|
||||
val (displayName, filepath) = prepareTestFiles(source, optimize, target)
|
||||
test(displayName) {
|
||||
compileTheThing(filepath, optimize, Cx16Target).assertSuccess()
|
||||
compileTheThing(filepath, optimize, target).assertSuccess()
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -141,13 +143,15 @@ class TestCompilerOnExamplesBothC64andCx16: FunSpec({
|
||||
|
||||
bothCx16AndC64.forEach {
|
||||
val (source, optimize) = it
|
||||
val (displayNameC64, filepathC64) = prepareTestFiles(source, optimize, C64Target)
|
||||
val (displayNameCx16, filepathCx16) = prepareTestFiles(source, optimize, Cx16Target)
|
||||
val c64target = C64Target()
|
||||
val cx16target = Cx16Target()
|
||||
val (displayNameC64, filepathC64) = prepareTestFiles(source, optimize, c64target)
|
||||
val (displayNameCx16, filepathCx16) = prepareTestFiles(source, optimize, cx16target)
|
||||
test(displayNameC64) {
|
||||
compileTheThing(filepathC64, optimize, C64Target).assertSuccess()
|
||||
compileTheThing(filepathC64, optimize, c64target).assertSuccess()
|
||||
}
|
||||
test(displayNameCx16) {
|
||||
compileTheThing(filepathCx16, optimize, Cx16Target).assertSuccess()
|
||||
compileTheThing(filepathCx16, optimize, cx16target).assertSuccess()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -6,7 +6,7 @@ import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import prog8.ast.expressions.AddressOf
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.StringLiteralValue
|
||||
import prog8.ast.expressions.StringLiteral
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
import prog8.ast.statements.Label
|
||||
import prog8.codegen.target.Cx16Target
|
||||
@ -27,7 +27,7 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
|
||||
val filepath = assumeReadableFile(fixturesDir, "importFromSameFolder.p8")
|
||||
assumeReadableFile(fixturesDir, "foo_bar.p8")
|
||||
|
||||
val platform = Cx16Target
|
||||
val platform = Cx16Target()
|
||||
val result = compileFile(platform, optimize = false, fixturesDir, filepath.name)
|
||||
.assertSuccess()
|
||||
|
||||
@ -36,7 +36,7 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
|
||||
val strLits = startSub.statements
|
||||
.filterIsInstance<FunctionCallStatement>()
|
||||
.map { it.args[0] as IdentifierReference }
|
||||
.map { it.targetVarDecl(program)!!.value as StringLiteralValue }
|
||||
.map { it.targetVarDecl(program)!!.value as StringLiteral }
|
||||
|
||||
strLits[0].value shouldBe "main.bar"
|
||||
strLits[1].value shouldBe "foo.bar"
|
||||
@ -50,7 +50,7 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
|
||||
val filepath = assumeReadableFile(fixturesDir, "asmIncludeFromSameFolder.p8")
|
||||
assumeReadableFile(fixturesDir, "foo_bar.asm")
|
||||
|
||||
val platform = Cx16Target
|
||||
val platform = Cx16Target()
|
||||
val result = compileFile(platform, optimize = false, fixturesDir, filepath.name)
|
||||
.assertSuccess()
|
||||
|
||||
@ -60,7 +60,7 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
|
||||
.filterIsInstance<FunctionCallStatement>()
|
||||
.map { it.args[0] }
|
||||
|
||||
val str0 = (args[0] as IdentifierReference).targetVarDecl(program)!!.value as StringLiteralValue
|
||||
val str0 = (args[0] as IdentifierReference).targetVarDecl(program)!!.value as StringLiteral
|
||||
str0.value shouldBe "main.bar"
|
||||
str0.definingScope.name shouldBe "main"
|
||||
|
||||
@ -76,7 +76,7 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
|
||||
val p8Path = assumeReadableFile(fixturesDir, "asmBinaryNonExisting.p8")
|
||||
assumeNotExists(fixturesDir, "i_do_not_exist.bin")
|
||||
|
||||
compileFile(Cx16Target, false, p8Path.parent, p8Path.name, outputDir)
|
||||
compileFile(Cx16Target(), false, p8Path.parent, p8Path.name, outputDir)
|
||||
.assertFailure()
|
||||
}
|
||||
|
||||
@ -84,7 +84,7 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
|
||||
val p8Path = assumeReadableFile(fixturesDir, "asmBinaryNonReadable.p8")
|
||||
assumeDirectory(fixturesDir, "subFolder")
|
||||
|
||||
compileFile(Cx16Target, false, p8Path.parent, p8Path.name, outputDir)
|
||||
compileFile(Cx16Target(), false, p8Path.parent, p8Path.name, outputDir)
|
||||
.assertFailure()
|
||||
}
|
||||
|
||||
@ -94,7 +94,7 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
|
||||
)
|
||||
|
||||
tests.forEach {
|
||||
val (where, p8Str, binStr) = it
|
||||
val (where, p8Str, _) = it
|
||||
test("%asmbinary from ${where}folder") {
|
||||
val p8Path = assumeReadableFile(fixturesDir, p8Str)
|
||||
// val binPath = assumeReadableFile(fixturesDir, binStr)
|
||||
@ -104,7 +104,7 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
|
||||
outputDir.normalize().toAbsolutePath() shouldNotBe workingDir.normalize().toAbsolutePath()
|
||||
}
|
||||
|
||||
compileFile(Cx16Target, false, p8Path.parent, p8Path.name, outputDir)
|
||||
compileFile(Cx16Target(), false, p8Path.parent, p8Path.name, outputDir)
|
||||
.assertSuccess(
|
||||
"argument to assembler directive .binary " +
|
||||
"should be relative to the generated .asm file (in output dir), " +
|
||||
|
@ -7,16 +7,16 @@ import io.kotest.matchers.string.shouldContain
|
||||
import io.kotest.matchers.types.instanceOf
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.expressions.ArrayLiteral
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.expressions.RangeExpression
|
||||
import prog8.ast.statements.ForLoop
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.codegen.target.C64Target
|
||||
import prog8.codegen.target.Cx16Target
|
||||
import prog8.compilerinterface.Encoding
|
||||
import prog8tests.helpers.*
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.assertFailure
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import prog8tests.helpers.compileText
|
||||
|
||||
|
||||
/**
|
||||
@ -27,11 +27,11 @@ import prog8tests.helpers.compileText
|
||||
class TestCompilerOnRanges: FunSpec({
|
||||
|
||||
test("testUByteArrayInitializerWithRange_char_to_char") {
|
||||
val platform = Cx16Target
|
||||
val platform = Cx16Target()
|
||||
val result = compileText(platform, false, """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte[] cs = @'a' to 'z' ; values are computed at compile time
|
||||
ubyte[] cs = sc:'a' to 'z' ; values are computed at compile time
|
||||
cs[0] = 23 ; keep optimizer from removing it
|
||||
}
|
||||
}
|
||||
@ -41,11 +41,11 @@ class TestCompilerOnRanges: FunSpec({
|
||||
val startSub = program.entrypoint
|
||||
val decl = startSub
|
||||
.statements.filterIsInstance<VarDecl>()[0]
|
||||
val rhsValues = (decl.value as ArrayLiteralValue)
|
||||
val rhsValues = (decl.value as ArrayLiteral)
|
||||
.value // Array<Expression>
|
||||
.map { (it as NumericLiteralValue).number.toInt() }
|
||||
val expectedStart = platform.encodeString("a", true)[0].toInt()
|
||||
val expectedEnd = platform.encodeString("z", false)[0].toInt()
|
||||
.map { (it as NumericLiteral).number.toInt() }
|
||||
val expectedStart = platform.encodeString("a", Encoding.SCREENCODES)[0].toInt()
|
||||
val expectedEnd = platform.encodeString("z", Encoding.PETSCII)[0].toInt()
|
||||
val expectedStr = "$expectedStart .. $expectedEnd"
|
||||
|
||||
val actualStr = "${rhsValues.first()} .. ${rhsValues.last()}"
|
||||
@ -58,9 +58,9 @@ class TestCompilerOnRanges: FunSpec({
|
||||
}
|
||||
|
||||
test("testFloatArrayInitializerWithRange_char_to_char") {
|
||||
val platform = C64Target
|
||||
val platform = C64Target()
|
||||
val result = compileText(platform, optimize = false, """
|
||||
%option enable_floats
|
||||
%import floats
|
||||
main {
|
||||
sub start() {
|
||||
float[] cs = 'a' to 'z' ; values are computed at compile time
|
||||
@ -73,11 +73,11 @@ class TestCompilerOnRanges: FunSpec({
|
||||
val startSub = program.entrypoint
|
||||
val decl = startSub
|
||||
.statements.filterIsInstance<VarDecl>()[0]
|
||||
val rhsValues = (decl.value as ArrayLiteralValue)
|
||||
val rhsValues = (decl.value as ArrayLiteral)
|
||||
.value // Array<Expression>
|
||||
.map { (it as NumericLiteralValue).number.toInt() }
|
||||
val expectedStart = platform.encodeString("a", false)[0].toInt()
|
||||
val expectedEnd = platform.encodeString("z", false)[0].toInt()
|
||||
.map { (it as NumericLiteral).number.toInt() }
|
||||
val expectedStart = platform.encodeString("a", Encoding.PETSCII)[0].toInt()
|
||||
val expectedEnd = platform.encodeString("z", Encoding.PETSCII)[0].toInt()
|
||||
val expectedStr = "$expectedStart .. $expectedEnd"
|
||||
|
||||
val actualStr = "${rhsValues.first()} .. ${rhsValues.last()}"
|
||||
@ -92,9 +92,9 @@ class TestCompilerOnRanges: FunSpec({
|
||||
context("floatArrayInitializerWithRange") {
|
||||
val combos = cartesianProduct(
|
||||
listOf("", "42", "41"), // sizeInDecl
|
||||
listOf("%option enable_floats", ""), // optEnableFloats
|
||||
listOf(Cx16Target, C64Target), // platform
|
||||
listOf(false, true) // optimize
|
||||
listOf("%import floats", ""), // optEnableFloats
|
||||
listOf(Cx16Target(), C64Target()), // platform
|
||||
listOf(false, true) // optimize
|
||||
)
|
||||
|
||||
combos.forEach {
|
||||
@ -128,12 +128,12 @@ class TestCompilerOnRanges: FunSpec({
|
||||
}
|
||||
|
||||
test("testForLoopWithRange_char_to_char") {
|
||||
val platform = Cx16Target
|
||||
val platform = Cx16Target()
|
||||
val result = compileText(platform, optimize = true, """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte i
|
||||
for i in @'a' to 'f' {
|
||||
for i in sc:'a' to 'f' {
|
||||
i += i ; keep optimizer from removing it
|
||||
}
|
||||
}
|
||||
@ -145,10 +145,10 @@ class TestCompilerOnRanges: FunSpec({
|
||||
val iterable = startSub
|
||||
.statements.filterIsInstance<ForLoop>()
|
||||
.map { it.iterable }[0]
|
||||
val rangeExpr = iterable as RangeExpr
|
||||
val rangeExpr = iterable as RangeExpression
|
||||
|
||||
val expectedStart = platform.encodeString("a", true)[0].toInt()
|
||||
val expectedEnd = platform.encodeString("f", false)[0].toInt()
|
||||
val expectedStart = platform.encodeString("a", Encoding.SCREENCODES)[0].toInt()
|
||||
val expectedEnd = platform.encodeString("f", Encoding.PETSCII)[0].toInt()
|
||||
val expectedStr = "$expectedStart .. $expectedEnd"
|
||||
|
||||
val intProgression = rangeExpr.toConstantIntegerRange()
|
||||
@ -162,7 +162,7 @@ class TestCompilerOnRanges: FunSpec({
|
||||
}
|
||||
|
||||
test("testForLoopWithRange_bool_to_bool") {
|
||||
val platform = Cx16Target
|
||||
val platform = Cx16Target()
|
||||
val result = compileText(platform, optimize = true, """
|
||||
main {
|
||||
sub start() {
|
||||
@ -179,7 +179,7 @@ class TestCompilerOnRanges: FunSpec({
|
||||
val rangeExpr = startSub
|
||||
.statements.filterIsInstance<ForLoop>()
|
||||
.map { it.iterable }
|
||||
.filterIsInstance<RangeExpr>()[0]
|
||||
.filterIsInstance<RangeExpression>()[0]
|
||||
|
||||
rangeExpr.size() shouldBe 2
|
||||
val intProgression = rangeExpr.toConstantIntegerRange()
|
||||
@ -188,7 +188,7 @@ class TestCompilerOnRanges: FunSpec({
|
||||
}
|
||||
|
||||
test("testForLoopWithRange_ubyte_to_ubyte") {
|
||||
val platform = Cx16Target
|
||||
val platform = Cx16Target()
|
||||
val result = compileText(platform, optimize = true, """
|
||||
main {
|
||||
sub start() {
|
||||
@ -205,7 +205,7 @@ class TestCompilerOnRanges: FunSpec({
|
||||
val rangeExpr = startSub
|
||||
.statements.filterIsInstance<ForLoop>()
|
||||
.map { it.iterable }
|
||||
.filterIsInstance<RangeExpr>()[0]
|
||||
.filterIsInstance<RangeExpression>()[0]
|
||||
|
||||
rangeExpr.size() shouldBe 9
|
||||
val intProgression = rangeExpr.toConstantIntegerRange()
|
||||
@ -215,7 +215,7 @@ class TestCompilerOnRanges: FunSpec({
|
||||
|
||||
test("testForLoopWithRange_str_downto_str") {
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(Cx16Target, true, """
|
||||
compileText(Cx16Target(), true, """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte i
|
||||
@ -231,7 +231,7 @@ class TestCompilerOnRanges: FunSpec({
|
||||
}
|
||||
|
||||
test("testForLoopWithIterable_str") {
|
||||
val result = compileText(Cx16Target, false, """
|
||||
val result = compileText(Cx16Target(), false, """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte i
|
||||
@ -253,17 +253,17 @@ class TestCompilerOnRanges: FunSpec({
|
||||
}
|
||||
|
||||
test("testRangeExprNumericSize") {
|
||||
val expr = RangeExpr(
|
||||
NumericLiteralValue.optimalInteger(10, Position.DUMMY),
|
||||
NumericLiteralValue.optimalInteger(20, Position.DUMMY),
|
||||
NumericLiteralValue.optimalInteger(2, Position.DUMMY),
|
||||
val expr = RangeExpression(
|
||||
NumericLiteral.optimalInteger(10, Position.DUMMY),
|
||||
NumericLiteral.optimalInteger(20, Position.DUMMY),
|
||||
NumericLiteral.optimalInteger(2, Position.DUMMY),
|
||||
Position.DUMMY)
|
||||
expr.size() shouldBe 6
|
||||
expr.toConstantIntegerRange()
|
||||
}
|
||||
|
||||
test("range with negative step should be constvalue") {
|
||||
val result = compileText(C64Target, false, """
|
||||
val result = compileText(C64Target(), false, """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte[] array = 100 to 50 step -2
|
||||
@ -275,15 +275,15 @@ class TestCompilerOnRanges: FunSpec({
|
||||
""").assertSuccess()
|
||||
val statements = result.program.entrypoint.statements
|
||||
val array = (statements[0] as VarDecl).value
|
||||
array shouldBe instanceOf<ArrayLiteralValue>()
|
||||
(array as ArrayLiteralValue).value.size shouldBe 26
|
||||
array shouldBe instanceOf<ArrayLiteral>()
|
||||
(array as ArrayLiteral).value.size shouldBe 26
|
||||
val forloop = (statements.dropLast(1).last() as ForLoop)
|
||||
forloop.iterable shouldBe instanceOf<RangeExpr>()
|
||||
(forloop.iterable as RangeExpr).step shouldBe NumericLiteralValue(DataType.UBYTE, -2.0, Position.DUMMY)
|
||||
forloop.iterable shouldBe instanceOf<RangeExpression>()
|
||||
(forloop.iterable as RangeExpression).step shouldBe NumericLiteral(DataType.UBYTE, -2.0, Position.DUMMY)
|
||||
}
|
||||
|
||||
test("range with start/end variables should be ok") {
|
||||
val result = compileText(C64Target, false, """
|
||||
val result = compileText(C64Target(), false, """
|
||||
main {
|
||||
sub start() {
|
||||
byte from = 100
|
||||
@ -296,13 +296,13 @@ class TestCompilerOnRanges: FunSpec({
|
||||
""").assertSuccess()
|
||||
val statements = result.program.entrypoint.statements
|
||||
val forloop = (statements.dropLast(1).last() as ForLoop)
|
||||
forloop.iterable shouldBe instanceOf<RangeExpr>()
|
||||
(forloop.iterable as RangeExpr).step shouldBe NumericLiteralValue(DataType.UBYTE, -2.0, Position.DUMMY)
|
||||
forloop.iterable shouldBe instanceOf<RangeExpression>()
|
||||
(forloop.iterable as RangeExpression).step shouldBe NumericLiteral(DataType.UBYTE, -2.0, Position.DUMMY)
|
||||
}
|
||||
|
||||
|
||||
test("for statement on all possible iterable expressions") {
|
||||
compileText(C64Target, false, """
|
||||
compileText(C64Target(), false, """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte xx
|
||||
@ -343,7 +343,7 @@ class TestCompilerOnRanges: FunSpec({
|
||||
}
|
||||
|
||||
test("if containment check on all possible iterable expressions") {
|
||||
compileText(C64Target, false, """
|
||||
compileText(C64Target(), false, """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte xx
|
||||
@ -404,7 +404,7 @@ class TestCompilerOnRanges: FunSpec({
|
||||
}
|
||||
|
||||
test("containment check in expressions") {
|
||||
compileText(C64Target, false, """
|
||||
compileText(C64Target(), false, """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte xx
|
||||
|
@ -1,12 +1,11 @@
|
||||
package prog8tests
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import prog8.codegen.target.Cx16Target
|
||||
import prog8.compiler.CompilationResult
|
||||
import prog8.compiler.CompilerArguments
|
||||
import prog8.compiler.compileProgram
|
||||
import prog8.codegen.target.Cx16Target
|
||||
import prog8tests.helpers.*
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.absolute
|
||||
import kotlin.io.path.createTempFile
|
||||
@ -46,7 +45,8 @@ class TestCompilerOptionSourcedirs: FunSpec({
|
||||
slowCodegenWarnings = false,
|
||||
quietAssembler = true,
|
||||
asmListfile = false,
|
||||
compilationTarget = Cx16Target.name,
|
||||
experimentalCodegen = false,
|
||||
compilationTarget = Cx16Target.NAME,
|
||||
sourceDirs,
|
||||
outputDir
|
||||
)
|
||||
|
@ -5,9 +5,9 @@ import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.string.shouldStartWith
|
||||
import prog8.ast.internedStringsModuleName
|
||||
import prog8.codegen.target.C64Target
|
||||
import prog8.compiler.determineCompilationOptions
|
||||
import prog8.compiler.parseImports
|
||||
import prog8.codegen.target.C64Target
|
||||
import prog8.compilerinterface.ZeropageType
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.assertSuccess
|
||||
@ -18,7 +18,7 @@ import prog8tests.helpers.outputDir
|
||||
class TestImportedModulesOrderAndOptions: FunSpec({
|
||||
|
||||
test("testImportedModuleOrderAndMainModuleCorrect") {
|
||||
val result = compileText(C64Target, false, """
|
||||
val result = compileText(C64Target(), false, """
|
||||
%import textio
|
||||
%import floats
|
||||
|
||||
@ -36,7 +36,7 @@ main {
|
||||
}
|
||||
withClue("module order in parse tree") {
|
||||
moduleNames.drop(1) shouldBe listOf(
|
||||
"prog8_interned_strings",
|
||||
internedStringsModuleName,
|
||||
"textio",
|
||||
"syslib",
|
||||
"conv",
|
||||
@ -49,7 +49,7 @@ main {
|
||||
}
|
||||
|
||||
test("testCompilationOptionsCorrectFromMain") {
|
||||
val result = compileText(C64Target, false, """
|
||||
val result = compileText(C64Target(), false, """
|
||||
%import textio
|
||||
%import floats
|
||||
%zeropage dontuse
|
||||
@ -62,7 +62,7 @@ main {
|
||||
}
|
||||
""").assertSuccess()
|
||||
result.program.toplevelModule.name shouldStartWith "on_the_fly_test"
|
||||
val options = determineCompilationOptions(result.program, C64Target)
|
||||
val options = determineCompilationOptions(result.program, C64Target())
|
||||
options.floats shouldBe true
|
||||
options.zeropage shouldBe ZeropageType.DONTUSE
|
||||
options.noSysInit shouldBe true
|
||||
@ -85,7 +85,7 @@ main {
|
||||
val filenameBase = "on_the_fly_test_" + sourceText.hashCode().toUInt().toString(16)
|
||||
val filepath = outputDir.resolve("$filenameBase.p8")
|
||||
filepath.toFile().writeText(sourceText)
|
||||
val (program, options, importedfiles) = parseImports(filepath, errors, C64Target, emptyList())
|
||||
val (program, options, importedfiles) = parseImports(filepath, errors, C64Target(), emptyList())
|
||||
|
||||
program.toplevelModule.name shouldBe filenameBase
|
||||
withClue("all imports other than the test source must have been internal resources library files") {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user