mirror of
https://github.com/irmen/prog8.git
synced 2025-06-18 08:23:37 +00:00
Compare commits
163 Commits
Author | SHA1 | Date | |
---|---|---|---|
b68f141568 | |||
b5d1e8653d | |||
f6d4c90dea | |||
b5b24636ae | |||
9dedbbf47c | |||
c493c3e5c6 | |||
61d4ca1d24 | |||
2cf9af4a6e | |||
bdcd10512f | |||
fec8db6a75 | |||
b400010426 | |||
28109a39ac | |||
651f0ec445 | |||
e61d3df380 | |||
15710207b2 | |||
adfddddac6 | |||
e46982f652 | |||
900c2aea23 | |||
42f8e98cab | |||
bed0e33b4f | |||
8d6542905d | |||
39798a1a4f | |||
befe4b8e9f | |||
772e48105e | |||
9afe451b8d | |||
89d469e77e | |||
59a43889a5 | |||
7caa0daffc | |||
5e854c2cf8 | |||
9edc92ec29 | |||
1d178080a3 | |||
aa94300bdd | |||
2d768c3f28 | |||
b79af624ae | |||
38208a7c9e | |||
8eff51904e | |||
c717f4573d | |||
984d251a6d | |||
8c3b43f3ed | |||
0f1485f30b | |||
eb94c678bd | |||
50d792a121 | |||
f0d4654917 | |||
4ce93b5d9d | |||
fb0d7a1908 | |||
bb7b063757 | |||
c495f54bbb | |||
1cc1f2d91d | |||
d837cc11f9 | |||
cbb7083307 | |||
d4a17dfad1 | |||
59f8b91e25 | |||
80113f9208 | |||
27f987f0ae | |||
3ae2597261 | |||
248e7b808c | |||
a983a896f2 | |||
68df1730f5 | |||
d62ab93b24 | |||
47297f7e31 | |||
b64d611e02 | |||
9fb9bcfebd | |||
dff9c5f53e | |||
d4a77321d2 | |||
2665618fa6 | |||
b5c5560af8 | |||
065587525e | |||
58e5d5c071 | |||
b44e76db57 | |||
2ce6bc5946 | |||
fe5b225732 | |||
d499e40a4b | |||
62a66d89c6 | |||
e1b26ae287 | |||
1c151f4a3f | |||
8917926996 | |||
b54a9b9831 | |||
f08906dba1 | |||
a6bba824d3 | |||
fd84152a2b | |||
3466106119 | |||
c79b587eea | |||
4862fb7db1 | |||
2136db0e61 | |||
2f0c0f6fcd | |||
7ddc01f883 | |||
126c2162f1 | |||
094c8ab94c | |||
efe2723874 | |||
bccfeb2fa2 | |||
d498d5445c | |||
5095d090cc | |||
6544fcdc36 | |||
e834924857 | |||
2c3b8a9819 | |||
309c82fc9e | |||
0f91ce6441 | |||
f29ec3b4e1 | |||
cc1fc869cf | |||
0431d3cddc | |||
a1cd202cd2 | |||
b842493cf0 | |||
4718f09cb7 | |||
e9c357a885 | |||
fb00ff74d1 | |||
b740b079db | |||
6394841041 | |||
3f4050c647 | |||
82f01d84c2 | |||
299ea72d70 | |||
50aa286d3a | |||
6f7322150f | |||
cc9965cc96 | |||
ae90a957c6 | |||
8cec032e7d | |||
3732ab1e62 | |||
fba149ee28 | |||
4661cba974 | |||
025be8cb7c | |||
3aea32551b | |||
8e8c112ff0 | |||
b0dda08e74 | |||
2c25df122a | |||
7cb5702b37 | |||
b7502c7eaa | |||
fed020825a | |||
1c411897df | |||
f94e241fb2 | |||
757cbfd1ba | |||
3de80319db | |||
f9617d777a | |||
9961a404ae | |||
776c844d02 | |||
03782a37a2 | |||
173663380b | |||
c6fdd65c63 | |||
d9546f9dc7 | |||
2a6b0f5db7 | |||
b4e1b42cec | |||
a8898a5993 | |||
e03c68b632 | |||
a0074de12b | |||
411bedcc46 | |||
07d8caf884 | |||
c0e83ef8df | |||
4dbf4b2005 | |||
61af72b906 | |||
17be722e2b | |||
16d7927d2f | |||
55a7a5d9d5 | |||
78d7849197 | |||
d5b12fb01d | |||
31f4e378aa | |||
8a26b7b248 | |||
87c28cfdbc | |||
1f5420010d | |||
a089c48378 | |||
3e5deda46c | |||
7500c6efd0 | |||
717b5f3b07 | |||
9f6fa60bf1 | |||
1e9586f635 | |||
44f9d5e69e |
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,4 +1,5 @@
|
|||||||
.idea/workspace.xml
|
.idea/workspace.xml
|
||||||
|
.idea/discord.xml
|
||||||
/build/
|
/build/
|
||||||
/dist/
|
/dist/
|
||||||
/output/
|
/output/
|
||||||
@ -24,8 +25,6 @@ __pycache__/
|
|||||||
parser.out
|
parser.out
|
||||||
parsetab.py
|
parsetab.py
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
compiler/src/prog8_kotlin.jar
|
|
||||||
compiler/src/compiled_java
|
|
||||||
.attach_pid*
|
.attach_pid*
|
||||||
|
|
||||||
.gradle
|
.gradle
|
||||||
|
2
.idea/.gitignore
generated
vendored
Normal file
2
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
10
.idea/inspectionProfiles/Project_Default.xml
generated
10
.idea/inspectionProfiles/Project_Default.xml
generated
@ -1,6 +1,16 @@
|
|||||||
<component name="InspectionProjectProfileManager">
|
<component name="InspectionProjectProfileManager">
|
||||||
<profile version="1.0">
|
<profile version="1.0">
|
||||||
<option name="myName" value="Project Default" />
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||||
|
<Languages>
|
||||||
|
<language minSize="100" isEnabled="false" name="JavaScript" />
|
||||||
|
<language isEnabled="false" name="Groovy" />
|
||||||
|
<language isEnabled="false" name="Style Sheets" />
|
||||||
|
<language minSize="70" name="Kotlin" />
|
||||||
|
<language isEnabled="false" name="TypeScript" />
|
||||||
|
<language isEnabled="false" name="ActionScript" />
|
||||||
|
</Languages>
|
||||||
|
</inspection_tool>
|
||||||
<inspection_tool class="SpellCheckingInspection" enabled="true" level="TYPO" enabled_by_default="true">
|
<inspection_tool class="SpellCheckingInspection" enabled="true" level="TYPO" enabled_by_default="true">
|
||||||
<option name="processCode" value="false" />
|
<option name="processCode" value="false" />
|
||||||
<option name="processLiterals" value="true" />
|
<option name="processLiterals" value="true" />
|
||||||
|
9
.idea/libraries/kotlinx_cli_jvm_0_1_0_dev_5.xml
generated
Normal file
9
.idea/libraries/kotlinx_cli_jvm_0_1_0_dev_5.xml
generated
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="kotlinx-cli-jvm-0.1.0-dev-5">
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$PROJECT_DIR$/compiler/lib/kotlinx-cli-jvm-0.1.0-dev-5.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</component>
|
1
.idea/modules.xml
generated
1
.idea/modules.xml
generated
@ -2,6 +2,7 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectModuleManager">
|
<component name="ProjectModuleManager">
|
||||||
<modules>
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/OldCodeGen/OldCodeGen.iml" filepath="$PROJECT_DIR$/OldCodeGen/OldCodeGen.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
|
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
|
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
|
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
|
||||||
|
2
.idea/vcs.xml
generated
2
.idea/vcs.xml
generated
@ -3,4 +3,4 @@
|
|||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
language: java
|
language: java
|
||||||
|
sudo: false
|
||||||
# jdk: openjdk8
|
# jdk: openjdk8
|
||||||
# dist: xenial
|
# dist: xenial
|
||||||
# sudo: false
|
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- chmod +x gradlew
|
- chmod +x gradlew
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- ./gradlew test
|
- gradle test
|
||||||
|
|
||||||
|
10
OldCodeGen/OldCodeGen.iml
Normal file
10
OldCodeGen/OldCodeGen.iml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?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$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||||
|
</component>
|
||||||
|
</module>
|
@ -1,4 +1,6 @@
|
|||||||
package prog8.compiler.target.c64
|
package oldcodegen
|
||||||
|
|
||||||
|
/** OLD STACK-VM CODE GEN -- NO LONGER USED **/
|
||||||
|
|
||||||
// note: to put stuff on the stack, we use Absolute,X addressing mode which is 3 bytes / 4 cycles
|
// note: to put stuff on the stack, we use Absolute,X addressing mode which is 3 bytes / 4 cycles
|
||||||
// possible space optimization is to use zeropage (indirect),Y which is 2 bytes, but 5 cycles
|
// possible space optimization is to use zeropage (indirect),Y which is 2 bytes, but 5 cycles
|
||||||
@ -6,11 +8,17 @@ package prog8.compiler.target.c64
|
|||||||
import prog8.ast.antlr.escape
|
import prog8.ast.antlr.escape
|
||||||
import prog8.ast.base.DataType
|
import prog8.ast.base.DataType
|
||||||
import prog8.ast.base.initvarsSubName
|
import prog8.ast.base.initvarsSubName
|
||||||
import prog8.vm.RuntimeValue
|
import prog8.ast.statements.ZeropageWish
|
||||||
import prog8.compiler.*
|
import prog8.compiler.*
|
||||||
import prog8.compiler.intermediate.*
|
import prog8.compiler.intermediate.Instruction
|
||||||
|
import prog8.compiler.intermediate.IntermediateProgram
|
||||||
|
import prog8.compiler.intermediate.LabelInstr
|
||||||
|
import prog8.compiler.intermediate.Opcode
|
||||||
|
import prog8.compiler.target.c64.AssemblyProgram
|
||||||
|
import prog8.compiler.target.c64.MachineDefinition
|
||||||
|
import prog8.compiler.target.c64.Petscii
|
||||||
|
import prog8.vm.RuntimeValue
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.*
|
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
|
||||||
@ -44,8 +52,7 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
|
|||||||
// Convert invalid label names (such as "<anon-1>") to something that's allowed.
|
// Convert invalid label names (such as "<anon-1>") to something that's allowed.
|
||||||
val newblocks = mutableListOf<IntermediateProgram.ProgramBlock>()
|
val newblocks = mutableListOf<IntermediateProgram.ProgramBlock>()
|
||||||
for(block in program.blocks) {
|
for(block in program.blocks) {
|
||||||
val newvars = block.variables.map { symname(it.key, block) to it.value }.toMap().toMutableMap()
|
val newvars = block.variables.map { IntermediateProgram.Variable(symname(it.scopedname, block), it.value, it.params) }.toMutableList()
|
||||||
val newvarsZeropaged = block.variablesMarkedForZeropage.map{symname(it, block)}.toMutableSet()
|
|
||||||
val newlabels = block.labels.map { symname(it.key, block) to it.value}.toMap().toMutableMap()
|
val newlabels = block.labels.map { symname(it.key, block) to it.value}.toMap().toMutableMap()
|
||||||
val newinstructions = block.instructions.asSequence().map {
|
val newinstructions = block.instructions.asSequence().map {
|
||||||
when {
|
when {
|
||||||
@ -66,8 +73,6 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
|
|||||||
newMempointers,
|
newMempointers,
|
||||||
newlabels,
|
newlabels,
|
||||||
force_output = block.force_output)
|
force_output = block.force_output)
|
||||||
newblock.variablesMarkedForZeropage.clear()
|
|
||||||
newblock.variablesMarkedForZeropage.addAll(newvarsZeropaged)
|
|
||||||
newblocks.add(newblock)
|
newblocks.add(newblock)
|
||||||
}
|
}
|
||||||
program.blocks.clear()
|
program.blocks.clear()
|
||||||
@ -146,7 +151,7 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
|
|||||||
return name.replace("-", "")
|
return name.replace("-", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun makeFloatFill(flt: Mflpt5): String {
|
private fun makeFloatFill(flt: MachineDefinition.Mflpt5): String {
|
||||||
val b0 = "$"+flt.b0.toString(16).padStart(2, '0')
|
val b0 = "$"+flt.b0.toString(16).padStart(2, '0')
|
||||||
val b1 = "$"+flt.b1.toString(16).padStart(2, '0')
|
val b1 = "$"+flt.b1.toString(16).padStart(2, '0')
|
||||||
val b2 = "$"+flt.b2.toString(16).padStart(2, '0')
|
val b2 = "$"+flt.b2.toString(16).padStart(2, '0')
|
||||||
@ -164,7 +169,8 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
|
|||||||
out("\n.cpu '6502'\n.enc 'none'\n")
|
out("\n.cpu '6502'\n.enc 'none'\n")
|
||||||
|
|
||||||
if(program.loadAddress==0) // fix load address
|
if(program.loadAddress==0) // fix load address
|
||||||
program.loadAddress = if(options.launcher==LauncherType.BASIC) BASIC_LOAD_ADDRESS else RAW_LOAD_ADDRESS
|
program.loadAddress = if(options.launcher==LauncherType.BASIC)
|
||||||
|
MachineDefinition.BASIC_LOAD_ADDRESS else MachineDefinition.RAW_LOAD_ADDRESS
|
||||||
|
|
||||||
when {
|
when {
|
||||||
options.launcher == LauncherType.BASIC -> {
|
options.launcher == LauncherType.BASIC -> {
|
||||||
@ -220,14 +226,14 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
|
|||||||
|
|
||||||
// the global list of all floating point constants for the whole program
|
// the global list of all floating point constants for the whole program
|
||||||
for(flt in globalFloatConsts) {
|
for(flt in globalFloatConsts) {
|
||||||
val floatFill = makeFloatFill(Mflpt5.fromNumber(flt.key))
|
val floatFill = makeFloatFill(MachineDefinition.Mflpt5.fromNumber(flt.key))
|
||||||
out("${flt.value}\t.byte $floatFill ; float ${flt.key}")
|
out("${flt.value}\t.byte $floatFill ; float ${flt.key}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun block2asm(blk: IntermediateProgram.ProgramBlock) {
|
private fun block2asm(blk: IntermediateProgram.ProgramBlock) {
|
||||||
block = blk
|
block = blk
|
||||||
out("\n; ---- block: '${block.name}' ----")
|
out("\n\n; ---- block: '${block.name}' ----")
|
||||||
if(!blk.force_output)
|
if(!blk.force_output)
|
||||||
out("${block.name}\t.proc\n")
|
out("${block.name}\t.proc\n")
|
||||||
if(block.address!=null) {
|
if(block.address!=null) {
|
||||||
@ -237,16 +243,17 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
|
|||||||
|
|
||||||
// deal with zeropage variables
|
// deal with zeropage variables
|
||||||
for(variable in blk.variables) {
|
for(variable in blk.variables) {
|
||||||
val sym = symname(blk.name+"."+variable.key, null)
|
val sym = symname(blk.name+"."+variable.scopedname, null)
|
||||||
val zpVar = program.allocatedZeropageVariables[sym]
|
val zpVar = program.allocatedZeropageVariables[sym]
|
||||||
if(zpVar==null) {
|
if(zpVar==null) {
|
||||||
// This var is not on the ZP yet. Attempt to move it there (if it's not a float, those take up too much space)
|
// This var is not on the ZP yet. Attempt to move it there (if it's not a float, those take up too much space)
|
||||||
if(variable.value.type in zeropage.allowedDatatypes && variable.value.type != DataType.FLOAT) {
|
if(variable.params.zp != ZeropageWish.NOT_IN_ZEROPAGE &&
|
||||||
|
variable.value.type in zeropage.allowedDatatypes
|
||||||
|
&& variable.value.type != DataType.FLOAT) {
|
||||||
try {
|
try {
|
||||||
val address = zeropage.allocate(sym, variable.value.type, null)
|
val address = zeropage.allocate(sym, variable.value.type, null)
|
||||||
out("${variable.key} = $address\t; auto zp ${variable.value.type}")
|
out("${variable.scopedname} = $address\t; auto zp ${variable.value.type}")
|
||||||
// make sure we add the var to the set of zpvars for this block
|
// make sure we add the var to the set of zpvars for this block
|
||||||
blk.variablesMarkedForZeropage.add(variable.key)
|
|
||||||
program.allocatedZeropageVariables[sym] = Pair(address, variable.value.type)
|
program.allocatedZeropageVariables[sym] = Pair(address, variable.value.type)
|
||||||
} catch (x: ZeropageDepletedError) {
|
} catch (x: ZeropageDepletedError) {
|
||||||
// leave it as it is.
|
// leave it as it is.
|
||||||
@ -255,7 +262,7 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// it was already allocated on the zp
|
// it was already allocated on the zp
|
||||||
out("${variable.key} = ${zpVar.first}\t; zp ${zpVar.second}")
|
out("${variable.scopedname} = ${zpVar.first}\t; zp ${zpVar.second}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,78 +296,98 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun vardecls2asm(block: IntermediateProgram.ProgramBlock) {
|
private fun vardecls2asm(block: IntermediateProgram.ProgramBlock) {
|
||||||
// these are the non-zeropage variables
|
val uniqueNames = block.variables.map { it.scopedname }.toSet()
|
||||||
val sortedVars = block.variables.filter{it.key !in block.variablesMarkedForZeropage}.toList().sortedBy { it.second.type }
|
if (uniqueNames.size != block.variables.size)
|
||||||
for (v in sortedVars) {
|
throw AssemblyError("not all variables have unique names")
|
||||||
when (v.second.type) {
|
|
||||||
DataType.UBYTE -> out("${v.first}\t.byte 0")
|
// these are the non-zeropage variables.
|
||||||
DataType.BYTE -> out("${v.first}\t.char 0")
|
// first get all the flattened struct members, they MUST remain in order
|
||||||
DataType.UWORD -> out("${v.first}\t.word 0")
|
out("; flattened struct members")
|
||||||
DataType.WORD -> out("${v.first}\t.sint 0")
|
val (structMembers, normalVars) = block.variables.partition { it.params.memberOfStruct!=null }
|
||||||
DataType.FLOAT -> out("${v.first}\t.byte 0,0,0,0,0 ; float")
|
structMembers.forEach { vardecl2asm(it.scopedname, it.value, it.params) }
|
||||||
DataType.STR, DataType.STR_S -> {
|
|
||||||
val rawStr = heap.get(v.second.heapId!!).str!!
|
// sort the other variables by type
|
||||||
val bytes = encodeStr(rawStr, v.second.type).map { "$" + it.toString(16).padStart(2, '0') }
|
out("; other variables sorted by type")
|
||||||
out("${v.first}\t; ${v.second.type} \"${escape(rawStr).replace("\u0000", "<NULL>")}\"")
|
val sortedVars = normalVars.sortedBy { it.value.type }
|
||||||
for (chunk in bytes.chunked(16))
|
for (variable in sortedVars) {
|
||||||
out(" .byte " + chunk.joinToString())
|
val sym = symname(block.name + "." + variable.scopedname, null)
|
||||||
}
|
if(sym in program.allocatedZeropageVariables)
|
||||||
DataType.ARRAY_UB -> {
|
continue // skip the ones that already belong in the zero page
|
||||||
// unsigned integer byte arraysize
|
vardecl2asm(variable.scopedname, variable.value, variable.params)
|
||||||
val data = makeArrayFillDataUnsigned(v.second)
|
|
||||||
if (data.size <= 16)
|
|
||||||
out("${v.first}\t.byte ${data.joinToString()}")
|
|
||||||
else {
|
|
||||||
out(v.first)
|
|
||||||
for (chunk in data.chunked(16))
|
|
||||||
out(" .byte " + chunk.joinToString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.ARRAY_B -> {
|
|
||||||
// signed integer byte arraysize
|
|
||||||
val data = makeArrayFillDataSigned(v.second)
|
|
||||||
if (data.size <= 16)
|
|
||||||
out("${v.first}\t.char ${data.joinToString()}")
|
|
||||||
else {
|
|
||||||
out(v.first)
|
|
||||||
for (chunk in data.chunked(16))
|
|
||||||
out(" .char " + chunk.joinToString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.ARRAY_UW -> {
|
|
||||||
// unsigned word arraysize
|
|
||||||
val data = makeArrayFillDataUnsigned(v.second)
|
|
||||||
if (data.size <= 16)
|
|
||||||
out("${v.first}\t.word ${data.joinToString()}")
|
|
||||||
else {
|
|
||||||
out(v.first)
|
|
||||||
for (chunk in data.chunked(16))
|
|
||||||
out(" .word " + chunk.joinToString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.ARRAY_W -> {
|
|
||||||
// signed word arraysize
|
|
||||||
val data = makeArrayFillDataSigned(v.second)
|
|
||||||
if (data.size <= 16)
|
|
||||||
out("${v.first}\t.sint ${data.joinToString()}")
|
|
||||||
else {
|
|
||||||
out(v.first)
|
|
||||||
for (chunk in data.chunked(16))
|
|
||||||
out(" .sint " + chunk.joinToString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.ARRAY_F -> {
|
|
||||||
// float arraysize
|
|
||||||
val array = heap.get(v.second.heapId!!).doubleArray!!
|
|
||||||
val floatFills = array.map { makeFloatFill(Mflpt5.fromNumber(it)) }
|
|
||||||
out(v.first)
|
|
||||||
for(f in array.zip(floatFills))
|
|
||||||
out(" .byte ${f.second} ; float ${f.first}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun vardecl2asm(varname: String, value: RuntimeValue, parameters: IntermediateProgram.VariableParameters) {
|
||||||
|
when (value.type) {
|
||||||
|
DataType.UBYTE -> out("$varname\t.byte 0")
|
||||||
|
DataType.BYTE -> out("$varname\t.char 0")
|
||||||
|
DataType.UWORD -> out("$varname\t.word 0")
|
||||||
|
DataType.WORD -> out("$varname\t.sint 0")
|
||||||
|
DataType.FLOAT -> out("$varname\t.byte 0,0,0,0,0 ; float")
|
||||||
|
DataType.STR, DataType.STR_S -> {
|
||||||
|
val rawStr = heap.get(value.heapId!!).str!!
|
||||||
|
val bytes = encodeStr(rawStr, value.type).map { "$" + it.toString(16).padStart(2, '0') }
|
||||||
|
out("$varname\t; ${value.type} \"${escape(rawStr).replace("\u0000", "<NULL>")}\"")
|
||||||
|
for (chunk in bytes.chunked(16))
|
||||||
|
out(" .byte " + chunk.joinToString())
|
||||||
|
}
|
||||||
|
DataType.ARRAY_UB -> {
|
||||||
|
// unsigned integer byte arraysize
|
||||||
|
val data = makeArrayFillDataUnsigned(value)
|
||||||
|
if (data.size <= 16)
|
||||||
|
out("$varname\t.byte ${data.joinToString()}")
|
||||||
|
else {
|
||||||
|
out(varname)
|
||||||
|
for (chunk in data.chunked(16))
|
||||||
|
out(" .byte " + chunk.joinToString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.ARRAY_B -> {
|
||||||
|
// signed integer byte arraysize
|
||||||
|
val data = makeArrayFillDataSigned(value)
|
||||||
|
if (data.size <= 16)
|
||||||
|
out("$varname\t.char ${data.joinToString()}")
|
||||||
|
else {
|
||||||
|
out(varname)
|
||||||
|
for (chunk in data.chunked(16))
|
||||||
|
out(" .char " + chunk.joinToString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.ARRAY_UW -> {
|
||||||
|
// unsigned word arraysize
|
||||||
|
val data = makeArrayFillDataUnsigned(value)
|
||||||
|
if (data.size <= 16)
|
||||||
|
out("$varname\t.word ${data.joinToString()}")
|
||||||
|
else {
|
||||||
|
out(varname)
|
||||||
|
for (chunk in data.chunked(16))
|
||||||
|
out(" .word " + chunk.joinToString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.ARRAY_W -> {
|
||||||
|
// signed word arraysize
|
||||||
|
val data = makeArrayFillDataSigned(value)
|
||||||
|
if (data.size <= 16)
|
||||||
|
out("$varname\t.sint ${data.joinToString()}")
|
||||||
|
else {
|
||||||
|
out(varname)
|
||||||
|
for (chunk in data.chunked(16))
|
||||||
|
out(" .sint " + chunk.joinToString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.ARRAY_F -> {
|
||||||
|
// float arraysize
|
||||||
|
val array = heap.get(value.heapId!!).doubleArray!!
|
||||||
|
val floatFills = array.map { makeFloatFill(MachineDefinition.Mflpt5.fromNumber(it)) }
|
||||||
|
out(varname)
|
||||||
|
for (f in array.zip(floatFills))
|
||||||
|
out(" .byte ${f.second} ; float ${f.first}")
|
||||||
|
}
|
||||||
|
DataType.STRUCT -> throw AssemblyError("vars of type STRUCT should have been removed because flattened")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun encodeStr(str: String, dt: DataType): List<Short> {
|
private fun encodeStr(str: String, dt: DataType): List<Short> {
|
||||||
return when(dt) {
|
return when(dt) {
|
||||||
DataType.STR -> {
|
DataType.STR -> {
|
||||||
@ -510,7 +537,7 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
|
|||||||
}
|
}
|
||||||
|
|
||||||
// add any matching patterns from the big list
|
// add any matching patterns from the big list
|
||||||
for(pattern in patterns) {
|
for(pattern in Patterns.patterns) {
|
||||||
if(pattern.sequence.size > segment.size || (pattern.altSequence!=null && pattern.altSequence.size > segment.size))
|
if(pattern.sequence.size > segment.size || (pattern.altSequence!=null && pattern.altSequence.size > segment.size))
|
||||||
continue // don't accept patterns that don't fit
|
continue // don't accept patterns that don't fit
|
||||||
val opcodesList = opcodes.subList(0, pattern.sequence.size)
|
val opcodesList = opcodes.subList(0, pattern.sequence.size)
|
||||||
@ -537,31 +564,31 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
|
|||||||
Opcode.SHL_BYTE -> AsmFragment(" asl $variable+$index", 8)
|
Opcode.SHL_BYTE -> AsmFragment(" asl $variable+$index", 8)
|
||||||
Opcode.SHR_UBYTE -> AsmFragment(" lsr $variable+$index", 8)
|
Opcode.SHR_UBYTE -> AsmFragment(" lsr $variable+$index", 8)
|
||||||
Opcode.SHR_SBYTE -> AsmFragment(" lda $variable+$index | asl a | ror $variable+$index")
|
Opcode.SHR_SBYTE -> AsmFragment(" lda $variable+$index | asl a | ror $variable+$index")
|
||||||
Opcode.SHL_WORD -> AsmFragment(" asl $variable+${index*2+1} | rol $variable+${index*2}", 8)
|
Opcode.SHL_WORD -> AsmFragment(" asl $variable+${index * 2 + 1} | rol $variable+${index * 2}", 8)
|
||||||
Opcode.SHR_UWORD -> AsmFragment(" lsr $variable+${index*2+1} | ror $variable+${index*2}", 8)
|
Opcode.SHR_UWORD -> AsmFragment(" lsr $variable+${index * 2 + 1} | ror $variable+${index * 2}", 8)
|
||||||
Opcode.SHR_SWORD -> AsmFragment(" lda $variable+${index*2+1} | asl a | ror $variable+${index*2+1} | ror $variable+${index*2}", 8)
|
Opcode.SHR_SWORD -> AsmFragment(" lda $variable+${index * 2 + 1} | asl a | ror $variable+${index * 2 + 1} | ror $variable+${index * 2}", 8)
|
||||||
Opcode.ROL_BYTE -> AsmFragment(" rol $variable+$index", 8)
|
Opcode.ROL_BYTE -> AsmFragment(" rol $variable+$index", 8)
|
||||||
Opcode.ROR_BYTE -> AsmFragment(" ror $variable+$index", 8)
|
Opcode.ROR_BYTE -> AsmFragment(" ror $variable+$index", 8)
|
||||||
Opcode.ROL_WORD -> AsmFragment(" rol $variable+${index*2+1} | rol $variable+${index*2}", 8)
|
Opcode.ROL_WORD -> AsmFragment(" rol $variable+${index * 2 + 1} | rol $variable+${index * 2}", 8)
|
||||||
Opcode.ROR_WORD -> AsmFragment(" ror $variable+${index*2+1} | ror $variable+${index*2}", 8)
|
Opcode.ROR_WORD -> AsmFragment(" ror $variable+${index * 2 + 1} | ror $variable+${index * 2}", 8)
|
||||||
Opcode.ROL2_BYTE -> AsmFragment(" lda $variable+$index | cmp #\$80 | rol $variable+$index", 8)
|
Opcode.ROL2_BYTE -> AsmFragment(" lda $variable+$index | cmp #\$80 | rol $variable+$index", 8)
|
||||||
Opcode.ROR2_BYTE -> AsmFragment(" lda $variable+$index | lsr a | bcc + | ora #\$80 |+ | sta $variable+$index", 10)
|
Opcode.ROR2_BYTE -> AsmFragment(" lda $variable+$index | lsr a | bcc + | ora #\$80 |+ | sta $variable+$index", 10)
|
||||||
Opcode.ROL2_WORD -> AsmFragment(" asl $variable+${index*2+1} | rol $variable+${index*2} | bcc + | inc $variable+${index*2+1} |+",20)
|
Opcode.ROL2_WORD -> AsmFragment(" asl $variable+${index * 2 + 1} | rol $variable+${index * 2} | bcc + | inc $variable+${index * 2 + 1} |+", 20)
|
||||||
Opcode.ROR2_WORD -> AsmFragment(" lsr $variable+${index*2+1} | ror $variable+${index*2} | bcc + | lda $variable+${index*2+1} | ora #\$80 | sta $variable+${index*2+1} |+", 30)
|
Opcode.ROR2_WORD -> AsmFragment(" lsr $variable+${index * 2 + 1} | ror $variable+${index * 2} | bcc + | lda $variable+${index * 2 + 1} | ora #\$80 | sta $variable+${index * 2 + 1} |+", 30)
|
||||||
Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UB -> AsmFragment(" inc $variable+$index", 2)
|
Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UB -> AsmFragment(" inc $variable+$index", 2)
|
||||||
Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UB -> AsmFragment(" dec $variable+$index", 5)
|
Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UB -> AsmFragment(" dec $variable+$index", 5)
|
||||||
Opcode.INC_INDEXED_VAR_W, Opcode.INC_INDEXED_VAR_UW -> AsmFragment(" inc $variable+${index*2} | bne + | inc $variable+${index*2+1} |+")
|
Opcode.INC_INDEXED_VAR_W, Opcode.INC_INDEXED_VAR_UW -> AsmFragment(" inc $variable+${index * 2} | bne + | inc $variable+${index * 2 + 1} |+")
|
||||||
Opcode.DEC_INDEXED_VAR_W, Opcode.DEC_INDEXED_VAR_UW -> AsmFragment(" lda $variable+${index*2} | bne + | dec $variable+${index*2+1} |+ | dec $variable+${index*2}")
|
Opcode.DEC_INDEXED_VAR_W, Opcode.DEC_INDEXED_VAR_UW -> AsmFragment(" lda $variable+${index * 2} | bne + | dec $variable+${index * 2 + 1} |+ | dec $variable+${index * 2}")
|
||||||
Opcode.INC_INDEXED_VAR_FLOAT -> AsmFragment(
|
Opcode.INC_INDEXED_VAR_FLOAT -> AsmFragment(
|
||||||
"""
|
"""
|
||||||
lda #<($variable+${index*Mflpt5.MemorySize})
|
lda #<($variable+${index * MachineDefinition.Mflpt5.MemorySize})
|
||||||
ldy #>($variable+${index*Mflpt5.MemorySize})
|
ldy #>($variable+${index * MachineDefinition.Mflpt5.MemorySize})
|
||||||
jsr c64flt.inc_var_f
|
jsr c64flt.inc_var_f
|
||||||
""")
|
""")
|
||||||
Opcode.DEC_INDEXED_VAR_FLOAT -> AsmFragment(
|
Opcode.DEC_INDEXED_VAR_FLOAT -> AsmFragment(
|
||||||
"""
|
"""
|
||||||
lda #<($variable+${index*Mflpt5.MemorySize})
|
lda #<($variable+${index * MachineDefinition.Mflpt5.MemorySize})
|
||||||
ldy #>($variable+${index*Mflpt5.MemorySize})
|
ldy #>($variable+${index * MachineDefinition.Mflpt5.MemorySize})
|
||||||
jsr c64flt.dec_var_f
|
jsr c64flt.dec_var_f
|
||||||
""")
|
""")
|
||||||
|
|
||||||
@ -571,8 +598,8 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
|
|||||||
|
|
||||||
private fun sameIndexedVarOperation(variable: String, indexVar: String, ins: Instruction): AsmFragment? {
|
private fun sameIndexedVarOperation(variable: String, indexVar: String, ins: Instruction): AsmFragment? {
|
||||||
// an in place operation that consists of a push-value / op / push-index-var / pop-into-indexed-var
|
// an in place operation that consists of a push-value / op / push-index-var / pop-into-indexed-var
|
||||||
val saveX = " stx ${C64Zeropage.SCRATCH_B1} |"
|
val saveX = " stx ${MachineDefinition.C64Zeropage.SCRATCH_B1} |"
|
||||||
val restoreX = " | ldx ${C64Zeropage.SCRATCH_B1}"
|
val restoreX = " | ldx ${MachineDefinition.C64Zeropage.SCRATCH_B1}"
|
||||||
val loadXWord: String
|
val loadXWord: String
|
||||||
val loadX: String
|
val loadX: String
|
||||||
|
|
2342
OldCodeGen/src/oldcodegen/AsmPatterns.kt
Normal file
2342
OldCodeGen/src/oldcodegen/AsmPatterns.kt
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,19 @@
|
|||||||
package prog8.compiler.target.c64
|
package oldcodegen
|
||||||
|
|
||||||
|
/** OLD STACK-VM CODE GEN -- NO LONGER USED **/
|
||||||
|
|
||||||
|
|
||||||
import prog8.compiler.CompilerException
|
import prog8.compiler.CompilerException
|
||||||
import prog8.compiler.intermediate.Instruction
|
import prog8.compiler.intermediate.Instruction
|
||||||
import prog8.compiler.intermediate.IntermediateProgram
|
import prog8.compiler.intermediate.IntermediateProgram
|
||||||
import prog8.compiler.intermediate.LabelInstr
|
import prog8.compiler.intermediate.LabelInstr
|
||||||
import prog8.compiler.intermediate.Opcode
|
import prog8.compiler.intermediate.Opcode
|
||||||
|
import prog8.compiler.target.c64.MachineDefinition.C64Zeropage
|
||||||
|
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_HEX
|
||||||
|
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_PLUS1_HEX
|
||||||
|
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX
|
||||||
|
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS1_HEX
|
||||||
|
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS2_HEX
|
||||||
import prog8.compiler.toHex
|
import prog8.compiler.toHex
|
||||||
import prog8.vm.stackvm.Syscall
|
import prog8.vm.stackvm.Syscall
|
||||||
import prog8.vm.stackvm.syscallsForStackVm
|
import prog8.vm.stackvm.syscallsForStackVm
|
||||||
@ -66,23 +75,23 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
|||||||
Opcode.DISCARD_WORD -> " inx"
|
Opcode.DISCARD_WORD -> " inx"
|
||||||
Opcode.DISCARD_FLOAT -> " inx | inx | inx"
|
Opcode.DISCARD_FLOAT -> " inx | inx | inx"
|
||||||
Opcode.DUP_B -> {
|
Opcode.DUP_B -> {
|
||||||
" lda ${(ESTACK_LO+1).toHex()},x | sta ${ESTACK_LO.toHex()},x | dex | ;DUP_B "
|
" lda $ESTACK_LO_PLUS1_HEX,x | sta $ESTACK_LO_HEX,x | dex | ;DUP_B "
|
||||||
}
|
}
|
||||||
Opcode.DUP_W -> {
|
Opcode.DUP_W -> {
|
||||||
" lda ${(ESTACK_LO+1).toHex()},x | sta ${ESTACK_LO.toHex()},x | lda ${(ESTACK_HI+1).toHex()},x | sta ${ESTACK_HI.toHex()},x | dex "
|
" lda $ESTACK_LO_PLUS1_HEX,x | sta $ESTACK_LO_HEX,x | lda $ESTACK_HI_PLUS1_HEX,x | sta $ESTACK_HI_HEX,x | dex "
|
||||||
}
|
}
|
||||||
|
|
||||||
Opcode.CMP_B, Opcode.CMP_UB -> {
|
Opcode.CMP_B, Opcode.CMP_UB -> {
|
||||||
" inx | lda ${ESTACK_LO.toHex()},x | cmp #${ins.arg!!.integerValue().toHex()} | ;CMP_B "
|
" inx | lda $ESTACK_LO_HEX,x | cmp #${ins.arg!!.integerValue().toHex()} | ;CMP_B "
|
||||||
}
|
}
|
||||||
|
|
||||||
Opcode.CMP_W, Opcode.CMP_UW -> {
|
Opcode.CMP_W, Opcode.CMP_UW -> {
|
||||||
"""
|
"""
|
||||||
inx
|
inx
|
||||||
lda ${ESTACK_HI.toHex()},x
|
lda $ESTACK_HI_HEX,x
|
||||||
cmp #>${ins.arg!!.integerValue().toHex()}
|
cmp #>${ins.arg!!.integerValue().toHex()}
|
||||||
bne +
|
bne +
|
||||||
lda ${ESTACK_LO.toHex()},x
|
lda $ESTACK_LO_HEX,x
|
||||||
cmp #<${ins.arg.integerValue().toHex()}
|
cmp #<${ins.arg.integerValue().toHex()}
|
||||||
; bne + not necessary?
|
; bne + not necessary?
|
||||||
; lda #0 not necessary?
|
; lda #0 not necessary?
|
||||||
@ -130,11 +139,11 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
|||||||
}
|
}
|
||||||
|
|
||||||
Opcode.PUSH_BYTE -> {
|
Opcode.PUSH_BYTE -> {
|
||||||
" lda #${hexVal(ins)} | sta ${ESTACK_LO.toHex()},x | dex"
|
" lda #${hexVal(ins)} | sta $ESTACK_LO_HEX,x | dex"
|
||||||
}
|
}
|
||||||
Opcode.PUSH_WORD -> {
|
Opcode.PUSH_WORD -> {
|
||||||
val value = hexVal(ins)
|
val value = hexVal(ins)
|
||||||
" lda #<$value | sta ${ESTACK_LO.toHex()},x | lda #>$value | sta ${ESTACK_HI.toHex()},x | dex"
|
" lda #<$value | sta $ESTACK_LO_HEX,x | lda #>$value | sta $ESTACK_HI_HEX,x | dex"
|
||||||
}
|
}
|
||||||
Opcode.PUSH_FLOAT -> {
|
Opcode.PUSH_FLOAT -> {
|
||||||
val floatConst = getFloatConst(ins.arg!!)
|
val floatConst = getFloatConst(ins.arg!!)
|
||||||
@ -143,28 +152,28 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
|||||||
Opcode.PUSH_VAR_BYTE -> {
|
Opcode.PUSH_VAR_BYTE -> {
|
||||||
when(ins.callLabel) {
|
when(ins.callLabel) {
|
||||||
"X" -> throw CompilerException("makes no sense to push X, it's used as a stack pointer itself. You should probably not use the X register (or only in trivial assignments)")
|
"X" -> throw CompilerException("makes no sense to push X, it's used as a stack pointer itself. You should probably not use the X register (or only in trivial assignments)")
|
||||||
"A" -> " sta ${ESTACK_LO.toHex()},x | dex"
|
"A" -> " sta $ESTACK_LO_HEX,x | dex"
|
||||||
"Y" -> " tya | sta ${ESTACK_LO.toHex()},x | dex"
|
"Y" -> " tya | sta $ESTACK_LO_HEX,x | dex"
|
||||||
else -> " lda ${ins.callLabel} | sta ${ESTACK_LO.toHex()},x | dex"
|
else -> " lda ${ins.callLabel} | sta $ESTACK_LO_HEX,x | dex"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode.PUSH_VAR_WORD -> {
|
Opcode.PUSH_VAR_WORD -> {
|
||||||
" lda ${ins.callLabel} | sta ${ESTACK_LO.toHex()},x | lda ${ins.callLabel}+1 | sta ${ESTACK_HI.toHex()},x | dex"
|
" lda ${ins.callLabel} | sta $ESTACK_LO_HEX,x | lda ${ins.callLabel}+1 | sta $ESTACK_HI_HEX,x | dex"
|
||||||
}
|
}
|
||||||
Opcode.PUSH_VAR_FLOAT -> " lda #<${ins.callLabel} | ldy #>${ins.callLabel}| jsr c64flt.push_float"
|
Opcode.PUSH_VAR_FLOAT -> " lda #<${ins.callLabel} | ldy #>${ins.callLabel}| jsr c64flt.push_float"
|
||||||
Opcode.PUSH_MEM_B, Opcode.PUSH_MEM_UB -> {
|
Opcode.PUSH_MEM_B, Opcode.PUSH_MEM_UB -> {
|
||||||
"""
|
"""
|
||||||
lda ${hexVal(ins)}
|
lda ${hexVal(ins)}
|
||||||
sta ${ESTACK_LO.toHex()},x
|
sta $ESTACK_LO_HEX,x
|
||||||
dex
|
dex
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
Opcode.PUSH_MEM_W, Opcode.PUSH_MEM_UW -> {
|
Opcode.PUSH_MEM_W, Opcode.PUSH_MEM_UW -> {
|
||||||
"""
|
"""
|
||||||
lda ${hexVal(ins)}
|
lda ${hexVal(ins)}
|
||||||
sta ${ESTACK_LO.toHex()},x
|
sta $ESTACK_LO_HEX,x
|
||||||
lda ${hexValPlusOne(ins)}
|
lda ${hexValPlusOne(ins)}
|
||||||
sta ${ESTACK_HI.toHex()},x
|
sta $ESTACK_HI_HEX,x
|
||||||
dex
|
dex
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
@ -173,43 +182,43 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
|||||||
}
|
}
|
||||||
Opcode.PUSH_MEMREAD -> {
|
Opcode.PUSH_MEMREAD -> {
|
||||||
"""
|
"""
|
||||||
lda ${(ESTACK_LO+1).toHex()},x
|
lda $ESTACK_LO_PLUS1_HEX,x
|
||||||
sta (+) +1
|
sta (+) +1
|
||||||
lda ${(ESTACK_HI+1).toHex()},x
|
lda $ESTACK_HI_PLUS1_HEX,x
|
||||||
sta (+) +2
|
sta (+) +2
|
||||||
+ lda 65535 ; modified
|
+ lda 65535 ; modified
|
||||||
sta ${(ESTACK_LO+1).toHex()},x
|
sta $ESTACK_LO_PLUS1_HEX,x
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
||||||
Opcode.PUSH_REGAY_WORD -> {
|
Opcode.PUSH_REGAY_WORD -> {
|
||||||
" sta ${ESTACK_LO.toHex()},x | tya | sta ${ESTACK_HI.toHex()},x | dex "
|
" sta $ESTACK_LO_HEX,x | tya | sta $ESTACK_HI_HEX,x | dex "
|
||||||
}
|
}
|
||||||
Opcode.PUSH_ADDR_HEAPVAR -> {
|
Opcode.PUSH_ADDR_HEAPVAR -> {
|
||||||
" lda #<${ins.callLabel} | sta ${ESTACK_LO.toHex()},x | lda #>${ins.callLabel} | sta ${ESTACK_HI.toHex()},x | dex"
|
" lda #<${ins.callLabel} | sta $ESTACK_LO_HEX,x | lda #>${ins.callLabel} | sta $ESTACK_HI_HEX,x | dex"
|
||||||
}
|
}
|
||||||
Opcode.POP_REGAX_WORD -> throw AssemblyError("cannot load X register from stack because it's used as the stack pointer itself")
|
Opcode.POP_REGAX_WORD -> throw AssemblyError("cannot load X register from stack because it's used as the stack pointer itself")
|
||||||
Opcode.POP_REGXY_WORD -> throw AssemblyError("cannot load X register from stack because it's used as the stack pointer itself")
|
Opcode.POP_REGXY_WORD -> throw AssemblyError("cannot load X register from stack because it's used as the stack pointer itself")
|
||||||
Opcode.POP_REGAY_WORD -> {
|
Opcode.POP_REGAY_WORD -> {
|
||||||
" inx | lda ${ESTACK_LO.toHex()},x | ldy ${ESTACK_HI.toHex()},x "
|
" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x "
|
||||||
}
|
}
|
||||||
|
|
||||||
Opcode.READ_INDEXED_VAR_BYTE -> {
|
Opcode.READ_INDEXED_VAR_BYTE -> {
|
||||||
"""
|
"""
|
||||||
ldy ${(ESTACK_LO+1).toHex()},x
|
ldy $ESTACK_LO_PLUS1_HEX,x
|
||||||
lda ${ins.callLabel},y
|
lda ${ins.callLabel},y
|
||||||
sta ${(ESTACK_LO+1).toHex()},x
|
sta $ESTACK_LO_PLUS1_HEX,x
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
Opcode.READ_INDEXED_VAR_WORD -> {
|
Opcode.READ_INDEXED_VAR_WORD -> {
|
||||||
"""
|
"""
|
||||||
lda ${(ESTACK_LO+1).toHex()},x
|
lda $ESTACK_LO_PLUS1_HEX,x
|
||||||
asl a
|
asl a
|
||||||
tay
|
tay
|
||||||
lda ${ins.callLabel},y
|
lda ${ins.callLabel},y
|
||||||
sta ${(ESTACK_LO+1).toHex()},x
|
sta $ESTACK_LO_PLUS1_HEX,x
|
||||||
lda ${ins.callLabel}+1,y
|
lda ${ins.callLabel}+1,y
|
||||||
sta ${(ESTACK_HI+1).toHex()},x
|
sta $ESTACK_HI_PLUS1_HEX,x
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
Opcode.READ_INDEXED_VAR_FLOAT -> {
|
Opcode.READ_INDEXED_VAR_FLOAT -> {
|
||||||
@ -222,22 +231,22 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
|||||||
Opcode.WRITE_INDEXED_VAR_BYTE -> {
|
Opcode.WRITE_INDEXED_VAR_BYTE -> {
|
||||||
"""
|
"""
|
||||||
inx
|
inx
|
||||||
ldy ${ESTACK_LO.toHex()},x
|
ldy $ESTACK_LO_HEX,x
|
||||||
inx
|
inx
|
||||||
lda ${ESTACK_LO.toHex()},x
|
lda $ESTACK_LO_HEX,x
|
||||||
sta ${ins.callLabel},y
|
sta ${ins.callLabel},y
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
Opcode.WRITE_INDEXED_VAR_WORD -> {
|
Opcode.WRITE_INDEXED_VAR_WORD -> {
|
||||||
"""
|
"""
|
||||||
inx
|
inx
|
||||||
lda ${ESTACK_LO.toHex()},x
|
lda $ESTACK_LO_HEX,x
|
||||||
asl a
|
asl a
|
||||||
tay
|
tay
|
||||||
inx
|
inx
|
||||||
lda ${ESTACK_LO.toHex()},x
|
lda $ESTACK_LO_HEX,x
|
||||||
sta ${ins.callLabel},y
|
sta ${ins.callLabel},y
|
||||||
lda ${ESTACK_HI.toHex()},x
|
lda $ESTACK_HI_HEX,x
|
||||||
sta ${ins.callLabel}+1,y
|
sta ${ins.callLabel}+1,y
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
@ -251,16 +260,16 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
|||||||
Opcode.POP_MEM_BYTE -> {
|
Opcode.POP_MEM_BYTE -> {
|
||||||
"""
|
"""
|
||||||
inx
|
inx
|
||||||
lda ${ESTACK_LO.toHex()},x
|
lda $ESTACK_LO_HEX,x
|
||||||
sta ${hexVal(ins)}
|
sta ${hexVal(ins)}
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
Opcode.POP_MEM_WORD -> {
|
Opcode.POP_MEM_WORD -> {
|
||||||
"""
|
"""
|
||||||
inx
|
inx
|
||||||
lda ${ESTACK_LO.toHex()},x
|
lda $ESTACK_LO_HEX,x
|
||||||
sta ${hexVal(ins)}
|
sta ${hexVal(ins)}
|
||||||
lda ${ESTACK_HI.toHex()},x
|
lda $ESTACK_HI_HEX,x
|
||||||
sta ${hexValPlusOne(ins)}
|
sta ${hexValPlusOne(ins)}
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
@ -270,12 +279,12 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
|||||||
Opcode.POP_MEMWRITE -> {
|
Opcode.POP_MEMWRITE -> {
|
||||||
"""
|
"""
|
||||||
inx
|
inx
|
||||||
lda ${ESTACK_LO.toHex()},x
|
lda $ESTACK_LO_HEX,x
|
||||||
sta (+) +1
|
sta (+) +1
|
||||||
lda ${ESTACK_HI.toHex()},x
|
lda $ESTACK_HI_HEX,x
|
||||||
sta (+) +2
|
sta (+) +2
|
||||||
inx
|
inx
|
||||||
lda ${ESTACK_LO.toHex()},x
|
lda $ESTACK_LO_HEX,x
|
||||||
+ sta 65535 ; modified
|
+ sta 65535 ; modified
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
@ -283,13 +292,13 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
|||||||
Opcode.POP_VAR_BYTE -> {
|
Opcode.POP_VAR_BYTE -> {
|
||||||
when (ins.callLabel) {
|
when (ins.callLabel) {
|
||||||
"X" -> throw CompilerException("makes no sense to pop X, it's used as a stack pointer itself")
|
"X" -> throw CompilerException("makes no sense to pop X, it's used as a stack pointer itself")
|
||||||
"A" -> " inx | lda ${ESTACK_LO.toHex()},x"
|
"A" -> " inx | lda $ESTACK_LO_HEX,x"
|
||||||
"Y" -> " inx | ldy ${ESTACK_LO.toHex()},x"
|
"Y" -> " inx | ldy $ESTACK_LO_HEX,x"
|
||||||
else -> " inx | lda ${ESTACK_LO.toHex()},x | sta ${ins.callLabel}"
|
else -> " inx | lda $ESTACK_LO_HEX,x | sta ${ins.callLabel}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode.POP_VAR_WORD -> {
|
Opcode.POP_VAR_WORD -> {
|
||||||
" inx | lda ${ESTACK_LO.toHex()},x | ldy ${ESTACK_HI.toHex()},x | sta ${ins.callLabel} | sty ${ins.callLabel}+1"
|
" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x | sta ${ins.callLabel} | sty ${ins.callLabel}+1"
|
||||||
}
|
}
|
||||||
Opcode.POP_VAR_FLOAT -> {
|
Opcode.POP_VAR_FLOAT -> {
|
||||||
" lda #<${ins.callLabel} | ldy #>${ins.callLabel} | jsr c64flt.pop_float"
|
" lda #<${ins.callLabel} | ldy #>${ins.callLabel} | jsr c64flt.pop_float"
|
||||||
@ -316,9 +325,9 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
|||||||
Opcode.POP_INC_MEMORY -> {
|
Opcode.POP_INC_MEMORY -> {
|
||||||
"""
|
"""
|
||||||
inx
|
inx
|
||||||
lda ${ESTACK_LO.toHex()},x
|
lda $ESTACK_LO_HEX,x
|
||||||
sta (+) +1
|
sta (+) +1
|
||||||
lda ${ESTACK_HI.toHex()},x
|
lda $ESTACK_HI_HEX,x
|
||||||
sta (+) +2
|
sta (+) +2
|
||||||
+ inc 65535 ; modified
|
+ inc 65535 ; modified
|
||||||
"""
|
"""
|
||||||
@ -326,9 +335,9 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
|||||||
Opcode.POP_DEC_MEMORY -> {
|
Opcode.POP_DEC_MEMORY -> {
|
||||||
"""
|
"""
|
||||||
inx
|
inx
|
||||||
lda ${ESTACK_LO.toHex()},x
|
lda $ESTACK_LO_HEX,x
|
||||||
sta (+) +1
|
sta (+) +1
|
||||||
lda ${ESTACK_HI.toHex()},x
|
lda $ESTACK_HI_HEX,x
|
||||||
sta (+) +2
|
sta (+) +2
|
||||||
+ dec 65535 ; modified
|
+ dec 65535 ; modified
|
||||||
"""
|
"""
|
||||||
@ -353,8 +362,8 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
|||||||
}
|
}
|
||||||
Opcode.INC_MEMORY -> " inc ${hexVal(ins)}"
|
Opcode.INC_MEMORY -> " inc ${hexVal(ins)}"
|
||||||
Opcode.DEC_MEMORY -> " dec ${hexVal(ins)}"
|
Opcode.DEC_MEMORY -> " dec ${hexVal(ins)}"
|
||||||
Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UB -> " inx | txa | pha | lda ${ESTACK_LO.toHex()},x | tax | inc ${ins.callLabel},x | pla | tax"
|
Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UB -> " inx | txa | pha | lda $ESTACK_LO_HEX,x | tax | inc ${ins.callLabel},x | pla | tax"
|
||||||
Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UB -> " inx | txa | pha | lda ${ESTACK_LO.toHex()},x | tax | dec ${ins.callLabel},x | pla | tax"
|
Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UB -> " inx | txa | pha | lda $ESTACK_LO_HEX,x | tax | dec ${ins.callLabel},x | pla | tax"
|
||||||
|
|
||||||
Opcode.NEG_B -> " jsr prog8_lib.neg_b"
|
Opcode.NEG_B -> " jsr prog8_lib.neg_b"
|
||||||
Opcode.NEG_W -> " jsr prog8_lib.neg_w"
|
Opcode.NEG_W -> " jsr prog8_lib.neg_w"
|
||||||
@ -365,9 +374,9 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
|||||||
Opcode.POW_F -> " jsr c64flt.pow_f"
|
Opcode.POW_F -> " jsr c64flt.pow_f"
|
||||||
Opcode.INV_BYTE -> {
|
Opcode.INV_BYTE -> {
|
||||||
"""
|
"""
|
||||||
lda ${(ESTACK_LO + 1).toHex()},x
|
lda $ESTACK_LO_PLUS1_HEX,x
|
||||||
eor #255
|
eor #255
|
||||||
sta ${(ESTACK_LO + 1).toHex()},x
|
sta $ESTACK_LO_PLUS1_HEX,x
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
Opcode.INV_WORD -> " jsr prog8_lib.inv_word"
|
Opcode.INV_WORD -> " jsr prog8_lib.inv_word"
|
||||||
@ -409,7 +418,7 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
|||||||
val label = ins.callLabel ?: hexVal(ins)
|
val label = ins.callLabel ?: hexVal(ins)
|
||||||
"""
|
"""
|
||||||
inx
|
inx
|
||||||
lda ${(ESTACK_LO).toHex()},x
|
lda $ESTACK_LO_HEX,x
|
||||||
beq $label
|
beq $label
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
@ -417,9 +426,9 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
|||||||
val label = ins.callLabel ?: hexVal(ins)
|
val label = ins.callLabel ?: hexVal(ins)
|
||||||
"""
|
"""
|
||||||
inx
|
inx
|
||||||
lda ${(ESTACK_LO).toHex()},x
|
lda $ESTACK_LO_HEX,x
|
||||||
beq $label
|
beq $label
|
||||||
lda ${(ESTACK_HI).toHex()},x
|
lda $ESTACK_HI_HEX,x
|
||||||
beq $label
|
beq $label
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
@ -427,7 +436,7 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
|||||||
val label = ins.callLabel ?: hexVal(ins)
|
val label = ins.callLabel ?: hexVal(ins)
|
||||||
"""
|
"""
|
||||||
inx
|
inx
|
||||||
lda ${(ESTACK_LO).toHex()},x
|
lda $ESTACK_LO_HEX,x
|
||||||
bne $label
|
bne $label
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
@ -435,9 +444,9 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
|||||||
val label = ins.callLabel ?: hexVal(ins)
|
val label = ins.callLabel ?: hexVal(ins)
|
||||||
"""
|
"""
|
||||||
inx
|
inx
|
||||||
lda ${(ESTACK_LO).toHex()},x
|
lda $ESTACK_LO_HEX,x
|
||||||
bne $label
|
bne $label
|
||||||
lda ${(ESTACK_HI).toHex()},x
|
lda $ESTACK_HI_HEX,x
|
||||||
bne $label
|
bne $label
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
@ -453,31 +462,31 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
|||||||
Opcode.CAST_B_TO_F -> " jsr c64flt.stack_b2float"
|
Opcode.CAST_B_TO_F -> " jsr c64flt.stack_b2float"
|
||||||
Opcode.CAST_UW_TO_F -> " jsr c64flt.stack_uw2float"
|
Opcode.CAST_UW_TO_F -> " jsr c64flt.stack_uw2float"
|
||||||
Opcode.CAST_W_TO_F -> " jsr c64flt.stack_w2float"
|
Opcode.CAST_W_TO_F -> " jsr c64flt.stack_w2float"
|
||||||
Opcode.CAST_F_TO_UB -> " jsr c64flt.stack_float2uw"
|
Opcode.CAST_F_TO_UB -> " jsr c64flt.stack_float2ub"
|
||||||
Opcode.CAST_F_TO_B -> " jsr c64flt.stack_float2w"
|
Opcode.CAST_F_TO_B -> " jsr c64flt.stack_float2b"
|
||||||
Opcode.CAST_F_TO_UW -> " jsr c64flt.stack_float2uw"
|
Opcode.CAST_F_TO_UW -> " jsr c64flt.stack_float2uw"
|
||||||
Opcode.CAST_F_TO_W -> " jsr c64flt.stack_float2w"
|
Opcode.CAST_F_TO_W -> " jsr c64flt.stack_float2w"
|
||||||
Opcode.CAST_UB_TO_UW, Opcode.CAST_UB_TO_W -> " lda #0 | sta ${(ESTACK_HI+1).toHex()},x" // clear the msb
|
Opcode.CAST_UB_TO_UW, Opcode.CAST_UB_TO_W -> " lda #0 | sta $ESTACK_HI_PLUS1_HEX,x" // clear the msb
|
||||||
Opcode.CAST_B_TO_UW, Opcode.CAST_B_TO_W -> " lda ${(ESTACK_LO+1).toHex()},x | ${signExtendA("${(ESTACK_HI+1).toHex()},x")}" // sign extend the lsb
|
Opcode.CAST_B_TO_UW, Opcode.CAST_B_TO_W -> " lda $ESTACK_LO_PLUS1_HEX,x | ${signExtendA("$ESTACK_HI_PLUS1_HEX,x")}" // sign extend the lsb
|
||||||
Opcode.MSB -> " lda ${(ESTACK_HI+1).toHex()},x | sta ${(ESTACK_LO+1).toHex()},x"
|
Opcode.MSB -> " lda $ESTACK_HI_PLUS1_HEX,x | sta $ESTACK_LO_PLUS1_HEX,x"
|
||||||
Opcode.MKWORD -> " inx | lda ${ESTACK_LO.toHex()},x | sta ${(ESTACK_HI+1).toHex()},x "
|
Opcode.MKWORD -> " inx | lda $ESTACK_LO_HEX,x | sta $ESTACK_HI_PLUS1_HEX,x "
|
||||||
|
|
||||||
Opcode.ADD_UB, Opcode.ADD_B -> { // TODO inline better (pattern with more opcodes)
|
Opcode.ADD_UB, Opcode.ADD_B -> { // TODO inline better (pattern with more opcodes)
|
||||||
"""
|
"""
|
||||||
lda ${(ESTACK_LO + 2).toHex()},x
|
lda $ESTACK_LO_PLUS2_HEX,x
|
||||||
clc
|
clc
|
||||||
adc ${(ESTACK_LO + 1).toHex()},x
|
adc $ESTACK_LO_PLUS1_HEX,x
|
||||||
inx
|
inx
|
||||||
sta ${(ESTACK_LO + 1).toHex()},x
|
sta $ESTACK_LO_PLUS1_HEX,x
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
Opcode.SUB_UB, Opcode.SUB_B -> { // TODO inline better (pattern with more opcodes)
|
Opcode.SUB_UB, Opcode.SUB_B -> { // TODO inline better (pattern with more opcodes)
|
||||||
"""
|
"""
|
||||||
lda ${(ESTACK_LO + 2).toHex()},x
|
lda $ESTACK_LO_PLUS2_HEX,x
|
||||||
sec
|
sec
|
||||||
sbc ${(ESTACK_LO + 1).toHex()},x
|
sbc $ESTACK_LO_PLUS1_HEX,x
|
||||||
inx
|
inx
|
||||||
sta ${(ESTACK_LO + 1).toHex()},x
|
sta $ESTACK_LO_PLUS1_HEX,x
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
Opcode.ADD_W, Opcode.ADD_UW -> " jsr prog8_lib.add_w"
|
Opcode.ADD_W, Opcode.ADD_UW -> " jsr prog8_lib.add_w"
|
||||||
@ -507,27 +516,27 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
|||||||
Opcode.BITOR_WORD -> " jsr prog8_lib.bitor_w"
|
Opcode.BITOR_WORD -> " jsr prog8_lib.bitor_w"
|
||||||
Opcode.BITXOR_WORD -> " jsr prog8_lib.bitxor_w"
|
Opcode.BITXOR_WORD -> " jsr prog8_lib.bitxor_w"
|
||||||
|
|
||||||
Opcode.REMAINDER_UB -> " jsr prog8_lib.remainder_ub"
|
Opcode.REMAINDER_UB -> " jsr prog8_lib.remainder_ub"
|
||||||
Opcode.REMAINDER_UW -> " jsr prog8_lib.remainder_uw"
|
Opcode.REMAINDER_UW -> " jsr prog8_lib.remainder_uw"
|
||||||
|
|
||||||
Opcode.GREATER_B -> " jsr prog8_lib.greater_b"
|
Opcode.GREATER_B -> " jsr prog8_lib.greater_b"
|
||||||
Opcode.GREATER_UB -> " jsr prog8_lib.greater_ub"
|
Opcode.GREATER_UB -> " jsr prog8_lib.greater_ub"
|
||||||
Opcode.GREATER_W -> " jsr prog8_lib.greater_w"
|
Opcode.GREATER_W -> " jsr prog8_lib.greater_w"
|
||||||
Opcode.GREATER_UW -> " jsr prog8_lib.greater_uw"
|
Opcode.GREATER_UW -> " jsr prog8_lib.greater_uw"
|
||||||
Opcode.GREATER_F -> " jsr c64flt.greater_f"
|
Opcode.GREATER_F -> " jsr c64flt.greater_f"
|
||||||
|
|
||||||
Opcode.GREATEREQ_B -> " jsr prog8_lib.greatereq_b"
|
Opcode.GREATEREQ_B -> " jsr prog8_lib.greatereq_b"
|
||||||
Opcode.GREATEREQ_UB -> " jsr prog8_lib.greatereq_ub"
|
Opcode.GREATEREQ_UB -> " jsr prog8_lib.greatereq_ub"
|
||||||
Opcode.GREATEREQ_W -> " jsr prog8_lib.greatereq_w"
|
Opcode.GREATEREQ_W -> " jsr prog8_lib.greatereq_w"
|
||||||
Opcode.GREATEREQ_UW -> " jsr prog8_lib.greatereq_uw"
|
Opcode.GREATEREQ_UW -> " jsr prog8_lib.greatereq_uw"
|
||||||
Opcode.GREATEREQ_F -> " jsr c64flt.greatereq_f"
|
Opcode.GREATEREQ_F -> " jsr c64flt.greatereq_f"
|
||||||
|
|
||||||
Opcode.EQUAL_BYTE -> " jsr prog8_lib.equal_b"
|
Opcode.EQUAL_BYTE -> " jsr prog8_lib.equal_b"
|
||||||
Opcode.EQUAL_WORD -> " jsr prog8_lib.equal_w"
|
Opcode.EQUAL_WORD -> " jsr prog8_lib.equal_w"
|
||||||
Opcode.EQUAL_F -> " jsr c64flt.equal_f"
|
Opcode.EQUAL_F -> " jsr c64flt.equal_f"
|
||||||
Opcode.NOTEQUAL_BYTE -> " jsr prog8_lib.notequal_b"
|
Opcode.NOTEQUAL_BYTE -> " jsr prog8_lib.notequal_b"
|
||||||
Opcode.NOTEQUAL_WORD -> " jsr prog8_lib.notequal_w"
|
Opcode.NOTEQUAL_WORD -> " jsr prog8_lib.notequal_w"
|
||||||
Opcode.NOTEQUAL_F -> " jsr c64flt.notequal_f"
|
Opcode.NOTEQUAL_F -> " jsr c64flt.notequal_f"
|
||||||
|
|
||||||
Opcode.LESS_UB -> " jsr prog8_lib.less_ub"
|
Opcode.LESS_UB -> " jsr prog8_lib.less_ub"
|
||||||
Opcode.LESS_B -> " jsr prog8_lib.less_b"
|
Opcode.LESS_B -> " jsr prog8_lib.less_b"
|
||||||
@ -541,12 +550,12 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
|||||||
Opcode.LESSEQ_W -> " jsr prog8_lib.lesseq_w"
|
Opcode.LESSEQ_W -> " jsr prog8_lib.lesseq_w"
|
||||||
Opcode.LESSEQ_F -> " jsr c64flt.lesseq_f"
|
Opcode.LESSEQ_F -> " jsr c64flt.lesseq_f"
|
||||||
|
|
||||||
Opcode.SHIFTEDL_BYTE -> " asl ${(ESTACK_LO+1).toHex()},x"
|
Opcode.SHIFTEDL_BYTE -> " asl $ESTACK_LO_PLUS1_HEX,x"
|
||||||
Opcode.SHIFTEDL_WORD -> " asl ${(ESTACK_LO+1).toHex()},x | rol ${(ESTACK_HI+1).toHex()},x"
|
Opcode.SHIFTEDL_WORD -> " asl $ESTACK_LO_PLUS1_HEX,x | rol $ESTACK_HI_PLUS1_HEX,x"
|
||||||
Opcode.SHIFTEDR_SBYTE -> " lda ${(ESTACK_LO+1).toHex()},x | asl a | ror ${(ESTACK_LO+1).toHex()},x"
|
Opcode.SHIFTEDR_SBYTE -> " lda $ESTACK_LO_PLUS1_HEX,x | asl a | ror $ESTACK_LO_PLUS1_HEX,x"
|
||||||
Opcode.SHIFTEDR_UBYTE -> " lsr ${(ESTACK_LO+1).toHex()},x"
|
Opcode.SHIFTEDR_UBYTE -> " lsr $ESTACK_LO_PLUS1_HEX,x"
|
||||||
Opcode.SHIFTEDR_SWORD -> " lda ${(ESTACK_HI+1).toHex()},x | asl a | ror ${(ESTACK_HI+1).toHex()},x | ror ${(ESTACK_LO+1).toHex()},x"
|
Opcode.SHIFTEDR_SWORD -> " lda $ESTACK_HI_PLUS1_HEX,x | asl a | ror $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x"
|
||||||
Opcode.SHIFTEDR_UWORD -> " lsr ${(ESTACK_HI+1).toHex()},x | ror ${(ESTACK_LO+1).toHex()},x"
|
Opcode.SHIFTEDR_UWORD -> " lsr $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x"
|
||||||
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
34
README.md
34
README.md
@ -14,36 +14,40 @@ as used in many home computers from that era. It is a medium to low level progra
|
|||||||
which aims to provide many conveniences over raw assembly code (even when using a macro assembler):
|
which aims to provide many conveniences over raw assembly code (even when using a macro assembler):
|
||||||
|
|
||||||
- reduction of source code length
|
- reduction of source code length
|
||||||
- easier program understanding (because it's higher level, and way more compact)
|
|
||||||
- modularity, symbol scoping, subroutines
|
- modularity, symbol scoping, subroutines
|
||||||
- subroutines have enforced input- and output parameter definitions
|
- various data types other than just bytes (16-bit words, floats, strings)
|
||||||
- various data types other than just bytes (16-bit words, floats, strings, 16-bit register pairs)
|
- automatic variable allocations, automatic string and array variables and string sharing
|
||||||
- automatic variable allocations, automatic string variables and string sharing
|
- subroutines with a input- and output parameter signature
|
||||||
- constant folding in expressions (compile-time evaluation)
|
- constant folding in expressions
|
||||||
- conditional branches
|
- conditional branches
|
||||||
- when statement to provide a 'jump table' alternative to if/elseif chains
|
- 'when' statement to provide a concise jump table alternative to if/elseif chains
|
||||||
- automatic type conversions
|
- structs to group together sets of variables and manipulate them at once
|
||||||
- floating point operations (uses the C64 Basic ROM routines for this)
|
- floating point operations (requires the C64 Basic ROM routines for this)
|
||||||
- abstracting away low level aspects such as ZeroPage handling, program startup, explicit memory addresses
|
- abstracting away low level aspects such as ZeroPage handling, program startup, explicit memory addresses
|
||||||
- various code optimizations (code structure, logical and numerical expressions, unused code removal...)
|
- various code optimizations (code structure, logical and numerical expressions, unused code removal...)
|
||||||
|
- inline assembly allows you to have full control when every cycle or byte matters
|
||||||
|
- many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``, ``sort`` and ``reverse``
|
||||||
|
|
||||||
Rapid edit-compile-run-debug cycle:
|
Rapid edit-compile-run-debug cycle:
|
||||||
|
|
||||||
- use modern PC to work on
|
- use modern PC to work on
|
||||||
- quick compilation times (less than 1 second)
|
- quick compilation times (seconds)
|
||||||
- option to automatically run the program in the Vice emulator
|
- option to automatically run the program in the Vice emulator
|
||||||
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them
|
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them
|
||||||
- source code labels automatically loaded in Vice emulator so it can show them in disassembly
|
- source code labels automatically loaded in Vice emulator so it can show them in disassembly
|
||||||
|
- virtual machine that can execute compiled code directy on the host system,
|
||||||
|
without having to actually convert it to assembly to run on a real 6502
|
||||||
|
|
||||||
It is mainly targeted at the Commodore-64 machine at this time.
|
It is mainly targeted at the Commodore-64 machine at this time.
|
||||||
Contributions to add support for other 8-bit (or other?!) machines are welcome.
|
Contributions to add support for other 8-bit (or other?!) machines are welcome.
|
||||||
|
|
||||||
Documentation is online at https://prog8.readthedocs.io/
|
Documentation/manual
|
||||||
|
--------------------
|
||||||
|
See https://prog8.readthedocs.io/
|
||||||
|
|
||||||
|
|
||||||
Required tools:
|
Required tools
|
||||||
---------------
|
--------------
|
||||||
|
|
||||||
[64tass](https://sourceforge.net/projects/tass64/) - cross assembler. Install this on your shell path.
|
[64tass](https://sourceforge.net/projects/tass64/) - cross assembler. Install this on your shell path.
|
||||||
A recent .exe version of this tool for Windows can be obtained from my [clone](https://github.com/irmen/64tass/releases) of this project.
|
A recent .exe version of this tool for Windows can be obtained from my [clone](https://github.com/irmen/64tass/releases) of this project.
|
||||||
@ -65,7 +69,7 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
|||||||
%import c64utils
|
%import c64utils
|
||||||
%zeropage basicsafe
|
%zeropage basicsafe
|
||||||
|
|
||||||
~ main {
|
main {
|
||||||
|
|
||||||
ubyte[256] sieve
|
ubyte[256] sieve
|
||||||
ubyte candidate_prime = 2
|
ubyte candidate_prime = 2
|
||||||
|
@ -15,15 +15,16 @@ plugins {
|
|||||||
apply plugin: "kotlin"
|
apply plugin: "kotlin"
|
||||||
apply plugin: "java"
|
apply plugin: "java"
|
||||||
|
|
||||||
|
targetCompatibility = 1.8
|
||||||
|
sourceCompatibility = 1.8
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
jcenter()
|
jcenter()
|
||||||
|
maven { url "https://dl.bintray.com/orangy/maven/" }
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceCompatibility = 1.8
|
|
||||||
|
|
||||||
def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
|
def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@ -37,18 +38,24 @@ dependencies {
|
|||||||
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5:$kotlinVersion"
|
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5:$kotlinVersion"
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2'
|
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2'
|
||||||
testImplementation 'org.hamcrest:hamcrest-junit:2.0.0.0'
|
testImplementation 'org.hamcrest:hamcrest-junit:2.0.0.0'
|
||||||
|
|
||||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.2'
|
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.2'
|
||||||
|
compile 'org.jetbrains.kotlinx:kotlinx-cli-jvm:0.1.0-dev-5'
|
||||||
}
|
}
|
||||||
|
|
||||||
compileKotlin {
|
compileKotlin {
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "1.8"
|
jvmTarget = "1.8"
|
||||||
verbose = true
|
// verbose = true
|
||||||
// freeCompilerArgs += "-XXLanguage:+NewInference"
|
// freeCompilerArgs += "-XXLanguage:+NewInference"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compileTestKotlin {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "1.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main {
|
main {
|
||||||
java {
|
java {
|
||||||
@ -76,20 +83,6 @@ artifacts {
|
|||||||
archives shadowJar
|
archives shadowJar
|
||||||
}
|
}
|
||||||
|
|
||||||
task p8vmScript(type: CreateStartScripts) {
|
|
||||||
mainClassName = "prog8.vm.stackvm.MainKt"
|
|
||||||
applicationName = "p8vm"
|
|
||||||
outputDir = new File(project.buildDir, 'scripts')
|
|
||||||
classpath = jar.outputs.files + project.configurations.runtime
|
|
||||||
}
|
|
||||||
|
|
||||||
applicationDistribution.into("bin") {
|
|
||||||
from(p8vmScript)
|
|
||||||
fileMode = 0755
|
|
||||||
}
|
|
||||||
|
|
||||||
// To create a fat-jar use the 'create_compiler_jar' script for now
|
|
||||||
// @todo investigate https://imperceptiblethoughts.com/shadow/introduction/
|
|
||||||
|
|
||||||
shadowJar {
|
shadowJar {
|
||||||
baseName = 'prog8compiler'
|
baseName = 'prog8compiler'
|
||||||
|
@ -8,11 +8,12 @@
|
|||||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
|
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||||
<orderEntry type="library" name="antlr-runtime-4.7.2" level="project" />
|
<orderEntry type="library" name="antlr-runtime-4.7.2" level="project" />
|
||||||
<orderEntry type="module" module-name="parser" />
|
<orderEntry type="module" module-name="parser" />
|
||||||
<orderEntry type="library" name="unittest-libs" level="project" />
|
<orderEntry type="library" name="unittest-libs" level="project" />
|
||||||
|
<orderEntry type="library" name="kotlinx-cli-jvm-0.1.0-dev-5" level="project" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
BIN
compiler/lib/kotlinx-cli-jvm-0.1.0-dev-5.jar
Normal file
BIN
compiler/lib/kotlinx-cli-jvm-0.1.0-dev-5.jar
Normal file
Binary file not shown.
@ -7,7 +7,7 @@
|
|||||||
%option enable_floats
|
%option enable_floats
|
||||||
|
|
||||||
|
|
||||||
~ c64flt {
|
c64flt {
|
||||||
; ---- this block contains C-64 floating point related functions ----
|
; ---- this block contains C-64 floating point related functions ----
|
||||||
|
|
||||||
const float PI = 3.141592653589793
|
const float PI = 3.141592653589793
|
||||||
@ -52,7 +52,7 @@ asmsub MOVMF (uword mflpt @ XY) clobbers(A,Y) = $bbd4 ; store fac1 to memory
|
|||||||
|
|
||||||
; fac1-> signed word in Y/A (might throw ILLEGAL QUANTITY)
|
; fac1-> signed word in Y/A (might throw ILLEGAL QUANTITY)
|
||||||
; (tip: use c64flt.FTOSWRDAY to get A/Y output; lo/hi switched to normal little endian order)
|
; (tip: use c64flt.FTOSWRDAY to get A/Y output; lo/hi switched to normal little endian order)
|
||||||
asmsub FTOSWORDYA () clobbers(X) -> ubyte @ Y, ubyte @ A = $b1aa
|
asmsub FTOSWORDYA () clobbers(X) -> ubyte @ Y, ubyte @ A = $b1aa ; note: calls AYINT.
|
||||||
|
|
||||||
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
|
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
|
||||||
; (tip: use c64flt.GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
|
; (tip: use c64flt.GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
|
||||||
@ -196,8 +196,8 @@ sub print_f (float value) {
|
|||||||
; ---- prints the floating point value (without a newline) using basic rom routines.
|
; ---- prints the floating point value (without a newline) using basic rom routines.
|
||||||
%asm {{
|
%asm {{
|
||||||
stx c64.SCRATCH_ZPREGX
|
stx c64.SCRATCH_ZPREGX
|
||||||
lda #<print_f_value
|
lda #<value
|
||||||
ldy #>print_f_value
|
ldy #>value
|
||||||
jsr MOVFM ; load float into fac1
|
jsr MOVFM ; load float into fac1
|
||||||
jsr FOUT ; fac1 to string in A/Y
|
jsr FOUT ; fac1 to string in A/Y
|
||||||
jsr c64.STROUT ; print string in A/Y
|
jsr c64.STROUT ; print string in A/Y
|
||||||
@ -310,7 +310,7 @@ stack_uw2float .proc
|
|||||||
jmp push_fac1_as_result
|
jmp push_fac1_as_result
|
||||||
.pend
|
.pend
|
||||||
|
|
||||||
stack_float2w .proc
|
stack_float2w .proc ; also used for float2b
|
||||||
jsr pop_float_fac1
|
jsr pop_float_fac1
|
||||||
stx c64.SCRATCH_ZPREGX
|
stx c64.SCRATCH_ZPREGX
|
||||||
jsr AYINT
|
jsr AYINT
|
||||||
@ -323,7 +323,7 @@ stack_float2w .proc
|
|||||||
rts
|
rts
|
||||||
.pend
|
.pend
|
||||||
|
|
||||||
stack_float2uw .proc
|
stack_float2uw .proc ; also used for float2ub
|
||||||
jsr pop_float_fac1
|
jsr pop_float_fac1
|
||||||
stx c64.SCRATCH_ZPREGX
|
stx c64.SCRATCH_ZPREGX
|
||||||
jsr GETADR
|
jsr GETADR
|
||||||
@ -954,6 +954,16 @@ func_sum_f .proc
|
|||||||
bne -
|
bne -
|
||||||
+ jmp push_fac1_as_result
|
+ jmp push_fac1_as_result
|
||||||
.pend
|
.pend
|
||||||
|
|
||||||
|
sign_f .proc
|
||||||
|
jsr pop_float_fac1
|
||||||
|
jsr SIGN
|
||||||
|
sta c64.ESTACK_LO,x
|
||||||
|
dex
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
}}
|
}}
|
||||||
|
|
||||||
} ; ------ end of block c64flt
|
} ; ------ end of block c64flt
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
; indent format: TABS, size=8
|
; indent format: TABS, size=8
|
||||||
|
|
||||||
|
|
||||||
~ c64 {
|
c64 {
|
||||||
const uword ESTACK_LO = $ce00 ; evaluation stack (lsb)
|
const uword ESTACK_LO = $ce00 ; evaluation stack (lsb)
|
||||||
const uword ESTACK_HI = $cf00 ; evaluation stack (msb)
|
const uword ESTACK_HI = $cf00 ; evaluation stack (msb)
|
||||||
&ubyte SCRATCH_ZPB1 = $02 ; scratch byte 1 in ZP
|
&ubyte SCRATCH_ZPB1 = $02 ; scratch byte 1 in ZP
|
||||||
@ -21,7 +21,7 @@
|
|||||||
&ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
|
&ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
|
||||||
&ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
|
&ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
|
||||||
&ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
|
&ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
|
||||||
|
|
||||||
&ubyte COLOR = $0286 ; cursor color
|
&ubyte COLOR = $0286 ; cursor color
|
||||||
&ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address)
|
&ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address)
|
||||||
&uword CINV = $0314 ; IRQ vector
|
&uword CINV = $0314 ; IRQ vector
|
||||||
@ -202,12 +202,12 @@ asmsub CINT () clobbers(A,X,Y) = $FF81 ; (alias: SCINIT) initialize scree
|
|||||||
asmsub IOINIT () clobbers(A, X) = $FF84 ; initialize I/O devices (CIA, SID, IRQ)
|
asmsub IOINIT () clobbers(A, X) = $FF84 ; initialize I/O devices (CIA, SID, IRQ)
|
||||||
asmsub RAMTAS () clobbers(A,X,Y) = $FF87 ; initialize RAM, tape buffer, screen
|
asmsub RAMTAS () clobbers(A,X,Y) = $FF87 ; initialize RAM, tape buffer, screen
|
||||||
asmsub RESTOR () clobbers(A,X,Y) = $FF8A ; restore default I/O vectors
|
asmsub RESTOR () clobbers(A,X,Y) = $FF8A ; restore default I/O vectors
|
||||||
asmsub VECTOR (ubyte dir @ Pc, uword userptr @ XY) clobbers(A,Y) = $FF8D ; read/set I/O vector table
|
asmsub VECTOR (uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) = $FF8D ; read/set I/O vector table
|
||||||
asmsub SETMSG (ubyte value @ A) = $FF90 ; set Kernal message control flag
|
asmsub SETMSG (ubyte value @ A) = $FF90 ; set Kernal message control flag
|
||||||
asmsub SECOND (ubyte address @ A) clobbers(A) = $FF93 ; (alias: LSTNSA) send secondary address after LISTEN
|
asmsub SECOND (ubyte address @ A) clobbers(A) = $FF93 ; (alias: LSTNSA) send secondary address after LISTEN
|
||||||
asmsub TKSA (ubyte address @ A) clobbers(A) = $FF96 ; (alias: TALKSA) send secondary address after TALK
|
asmsub TKSA (ubyte address @ A) clobbers(A) = $FF96 ; (alias: TALKSA) send secondary address after TALK
|
||||||
asmsub MEMTOP (ubyte dir @ Pc, uword address @ XY) -> uword @ XY = $FF99 ; read/set top of memory pointer
|
asmsub MEMTOP (uword address @ XY, ubyte dir @ Pc) -> uword @ XY = $FF99 ; read/set top of memory pointer
|
||||||
asmsub MEMBOT (ubyte dir @ Pc, uword address @ XY) -> uword @ XY = $FF9C ; read/set bottom of memory pointer
|
asmsub MEMBOT (uword address @ XY, ubyte dir @ Pc) -> uword @ XY = $FF9C ; read/set bottom of memory pointer
|
||||||
asmsub SCNKEY () clobbers(A,X,Y) = $FF9F ; scan the keyboard
|
asmsub SCNKEY () clobbers(A,X,Y) = $FF9F ; scan the keyboard
|
||||||
asmsub SETTMO (ubyte timeout @ A) = $FFA2 ; set time-out flag for IEEE bus
|
asmsub SETTMO (ubyte timeout @ A) = $FFA2 ; set time-out flag for IEEE bus
|
||||||
asmsub ACPTR () -> ubyte @ A = $FFA5 ; (alias: IECIN) input byte from serial bus
|
asmsub ACPTR () -> ubyte @ A = $FFA5 ; (alias: IECIN) input byte from serial bus
|
||||||
@ -235,7 +235,7 @@ asmsub GETIN () clobbers(X,Y) -> ubyte @ A = $FFE4 ; (via 810 ($32A)) get a
|
|||||||
asmsub CLALL () clobbers(A,X) = $FFE7 ; (via 812 ($32C)) close all files
|
asmsub CLALL () clobbers(A,X) = $FFE7 ; (via 812 ($32C)) close all files
|
||||||
asmsub UDTIM () clobbers(A,X) = $FFEA ; update the software clock
|
asmsub UDTIM () clobbers(A,X) = $FFEA ; update the software clock
|
||||||
asmsub SCREEN () -> ubyte @ X, ubyte @ Y = $FFED ; read number of screen rows and columns
|
asmsub SCREEN () -> ubyte @ X, ubyte @ Y = $FFED ; read number of screen rows and columns
|
||||||
asmsub PLOT (ubyte dir @ Pc, ubyte col @ Y, ubyte row @ X) -> ubyte @ X, ubyte @ Y = $FFF0 ; read/set position of cursor on screen. Use c64scr.plot for a 'safe' wrapper that preserves X.
|
asmsub PLOT (ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y = $FFF0 ; read/set position of cursor on screen. Use c64scr.plot for a 'safe' wrapper that preserves X.
|
||||||
asmsub IOBASE () -> uword @ XY = $FFF3 ; read base address of I/O devices
|
asmsub IOBASE () -> uword @ XY = $FFF3 ; read base address of I/O devices
|
||||||
|
|
||||||
; ---- end of C64 kernal routines ----
|
; ---- end of C64 kernal routines ----
|
||||||
|
@ -9,34 +9,205 @@
|
|||||||
%import c64lib
|
%import c64lib
|
||||||
|
|
||||||
|
|
||||||
~ c64utils {
|
c64utils {
|
||||||
|
|
||||||
const uword ESTACK_LO = $ce00
|
const uword ESTACK_LO = $ce00
|
||||||
const uword ESTACK_HI = $cf00
|
const uword ESTACK_HI = $cf00
|
||||||
|
|
||||||
|
|
||||||
; ----- utility functions ----
|
; ----- number conversions to decimal strings
|
||||||
|
|
||||||
asmsub ubyte2decimal (ubyte value @ A) -> ubyte @ Y, ubyte @ X, ubyte @ A {
|
asmsub ubyte2decimal (ubyte value @ A) -> ubyte @ Y, ubyte @ A, ubyte @ X {
|
||||||
; ---- A to decimal string in Y/X/A (100s in Y, 10s in X, 1s in A)
|
; ---- A to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
|
||||||
%asm {{
|
%asm {{
|
||||||
ldy #$2f
|
ldy #uword2decimal.ASCII_0_OFFSET
|
||||||
ldx #$3a
|
bne uword2decimal.hex_try200
|
||||||
sec
|
rts
|
||||||
- iny
|
|
||||||
sbc #100
|
|
||||||
bcs -
|
|
||||||
- dex
|
|
||||||
adc #10
|
|
||||||
bmi -
|
|
||||||
adc #$2f
|
|
||||||
rts
|
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
asmsub byte2decimal (ubyte value @ A) -> ubyte @ Y, ubyte @ X, ubyte @ A {
|
asmsub uword2decimal (uword value @ AY) -> ubyte @Y, ubyte @A, ubyte @X {
|
||||||
; ---- A (signed byte) to decimal string in Y/X/A (100s in Y, 10s in X, 1s in A)
|
; ---- convert 16 bit uword in A/Y to decimal
|
||||||
; note: the '-' is not part of the conversion here if it's a negative number
|
; output in uword2decimal.decTenThousands, decThousands, decHundreds, decTens, decOnes
|
||||||
|
; (these are terminated by a zero byte so they can be easily printed)
|
||||||
|
; also returns Y = 100's, A = 10's, X = 1's
|
||||||
|
|
||||||
|
%asm {{
|
||||||
|
|
||||||
|
;Convert 16 bit Hex to Decimal (0-65535) Rev 2
|
||||||
|
;By Omegamatrix Further optimizations by tepples
|
||||||
|
; routine from http://forums.nesdev.com/viewtopic.php?f=2&t=11341&start=15
|
||||||
|
|
||||||
|
;HexToDec99
|
||||||
|
; start in A
|
||||||
|
; end with A = 10's, decOnes (also in X)
|
||||||
|
|
||||||
|
;HexToDec255
|
||||||
|
; start in A
|
||||||
|
; end with Y = 100's, A = 10's, decOnes (also in X)
|
||||||
|
|
||||||
|
;HexToDec999
|
||||||
|
; start with A = high byte, Y = low byte
|
||||||
|
; end with Y = 100's, A = 10's, decOnes (also in X)
|
||||||
|
; requires 1 extra temp register on top of decOnes, could combine
|
||||||
|
; these two if HexToDec65535 was eliminated...
|
||||||
|
|
||||||
|
;HexToDec65535
|
||||||
|
; start with A/Y (low/high) as 16 bit value
|
||||||
|
; end with decTenThousand, decThousand, Y = 100's, A = 10's, decOnes (also in X)
|
||||||
|
; (irmen: I store Y and A in decHundreds and decTens too, so all of it can be easily printed)
|
||||||
|
|
||||||
|
|
||||||
|
ASCII_0_OFFSET = $30
|
||||||
|
temp = c64.SCRATCH_ZPB1 ; byte in zeropage
|
||||||
|
hexHigh = c64.SCRATCH_ZPWORD1 ; byte in zeropage
|
||||||
|
hexLow = c64.SCRATCH_ZPWORD1+1 ; byte in zeropage
|
||||||
|
|
||||||
|
|
||||||
|
HexToDec65535; SUBROUTINE
|
||||||
|
sty hexHigh ;3 @9
|
||||||
|
sta hexLow ;3 @12
|
||||||
|
tya
|
||||||
|
tax ;2 @14
|
||||||
|
lsr a ;2 @16
|
||||||
|
lsr a ;2 @18 integer divide 1024 (result 0-63)
|
||||||
|
|
||||||
|
cpx #$A7 ;2 @20 account for overflow of multiplying 24 from 43,000 ($A7F8) onward,
|
||||||
|
adc #1 ;2 @22 we can just round it to $A700, and the divide by 1024 is fine...
|
||||||
|
|
||||||
|
;at this point we have a number 1-65 that we have to times by 24,
|
||||||
|
;add to original sum, and Mod 1024 to get a remainder 0-999
|
||||||
|
|
||||||
|
|
||||||
|
sta temp ;3 @25
|
||||||
|
asl a ;2 @27
|
||||||
|
adc temp ;3 @30 x3
|
||||||
|
tay ;2 @32
|
||||||
|
lsr a ;2 @34
|
||||||
|
lsr a ;2 @36
|
||||||
|
lsr a ;2 @38
|
||||||
|
lsr a ;2 @40
|
||||||
|
lsr a ;2 @42
|
||||||
|
tax ;2 @44
|
||||||
|
tya ;2 @46
|
||||||
|
asl a ;2 @48
|
||||||
|
asl a ;2 @50
|
||||||
|
asl a ;2 @52
|
||||||
|
clc ;2 @54
|
||||||
|
adc hexLow ;3 @57
|
||||||
|
sta hexLow ;3 @60
|
||||||
|
txa ;2 @62
|
||||||
|
adc hexHigh ;3 @65
|
||||||
|
sta hexHigh ;3 @68
|
||||||
|
ror a ;2 @70
|
||||||
|
lsr a ;2 @72
|
||||||
|
tay ;2 @74 integer divide 1,000 (result 0-65)
|
||||||
|
|
||||||
|
lsr a ;2 @76 split the 1,000 and 10,000 digit
|
||||||
|
tax ;2 @78
|
||||||
|
lda ShiftedBcdTab,x ;4 @82
|
||||||
|
tax ;2 @84
|
||||||
|
rol a ;2 @86
|
||||||
|
and #$0F ;2 @88
|
||||||
|
ora #ASCII_0_OFFSET
|
||||||
|
sta decThousands ;3 @91
|
||||||
|
txa ;2 @93
|
||||||
|
lsr a ;2 @95
|
||||||
|
lsr a ;2 @97
|
||||||
|
lsr a ;2 @99
|
||||||
|
ora #ASCII_0_OFFSET
|
||||||
|
sta decTenThousands ;3 @102
|
||||||
|
|
||||||
|
lda hexLow ;3 @105
|
||||||
|
cpy temp ;3 @108
|
||||||
|
bmi _doSubtract ;2³ @110/111
|
||||||
|
beq _useZero ;2³ @112/113
|
||||||
|
adc #23 + 24 ;2 @114
|
||||||
|
_doSubtract
|
||||||
|
sbc #23 ;2 @116
|
||||||
|
sta hexLow ;3 @119
|
||||||
|
_useZero
|
||||||
|
lda hexHigh ;3 @122
|
||||||
|
sbc #0 ;2 @124
|
||||||
|
|
||||||
|
Start100s
|
||||||
|
and #$03 ;2 @126
|
||||||
|
tax ;2 @128 0,1,2,3
|
||||||
|
cmp #2 ;2 @130
|
||||||
|
rol a ;2 @132 0,2,5,7
|
||||||
|
ora #ASCII_0_OFFSET
|
||||||
|
tay ;2 @134 Y = Hundreds digit
|
||||||
|
|
||||||
|
lda hexLow ;3 @137
|
||||||
|
adc Mod100Tab,x ;4 @141 adding remainder of 256, 512, and 256+512 (all mod 100)
|
||||||
|
bcs hex_doSub200 ;2³ @143/144
|
||||||
|
|
||||||
|
hex_try200
|
||||||
|
cmp #200 ;2 @145
|
||||||
|
bcc hex_try100 ;2³ @147/148
|
||||||
|
hex_doSub200
|
||||||
|
iny ;2 @149
|
||||||
|
iny ;2 @151
|
||||||
|
sbc #200 ;2 @153
|
||||||
|
hex_try100
|
||||||
|
cmp #100 ;2 @155
|
||||||
|
bcc HexToDec99 ;2³ @157/158
|
||||||
|
iny ;2 @159
|
||||||
|
sbc #100 ;2 @161
|
||||||
|
|
||||||
|
HexToDec99; SUBROUTINE
|
||||||
|
lsr a ;2 @163
|
||||||
|
tax ;2 @165
|
||||||
|
lda ShiftedBcdTab,x ;4 @169
|
||||||
|
tax ;2 @171
|
||||||
|
rol a ;2 @173
|
||||||
|
and #$0F ;2 @175
|
||||||
|
ora #ASCII_0_OFFSET
|
||||||
|
sta decOnes ;3 @178
|
||||||
|
txa ;2 @180
|
||||||
|
lsr a ;2 @182
|
||||||
|
lsr a ;2 @184
|
||||||
|
lsr a ;2 @186
|
||||||
|
ora #ASCII_0_OFFSET
|
||||||
|
|
||||||
|
; irmen: load X with ones, and store Y and A too, for easy printing afterwards
|
||||||
|
sty decHundreds
|
||||||
|
sta decTens
|
||||||
|
ldx decOnes
|
||||||
|
rts ;6 @192 Y=hundreds, A = tens digit, X=ones digit
|
||||||
|
|
||||||
|
|
||||||
|
HexToDec999; SUBROUTINE
|
||||||
|
sty hexLow ;3 @9
|
||||||
|
jmp Start100s ;3 @12
|
||||||
|
|
||||||
|
Mod100Tab
|
||||||
|
.byte 0,56,12,56+12
|
||||||
|
|
||||||
|
ShiftedBcdTab
|
||||||
|
.byte $00,$01,$02,$03,$04,$08,$09,$0A,$0B,$0C
|
||||||
|
.byte $10,$11,$12,$13,$14,$18,$19,$1A,$1B,$1C
|
||||||
|
.byte $20,$21,$22,$23,$24,$28,$29,$2A,$2B,$2C
|
||||||
|
.byte $30,$31,$32,$33,$34,$38,$39,$3A,$3B,$3C
|
||||||
|
.byte $40,$41,$42,$43,$44,$48,$49,$4A,$4B,$4C
|
||||||
|
|
||||||
|
decTenThousands .byte 0
|
||||||
|
decThousands .byte 0
|
||||||
|
decHundreds .byte 0
|
||||||
|
decTens .byte 0
|
||||||
|
decOnes .byte 0
|
||||||
|
.byte 0 ; zero-terminate the decimal output string
|
||||||
|
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
; ----- utility functions ----
|
||||||
|
|
||||||
|
|
||||||
|
asmsub byte2decimal (byte value @ A) -> ubyte @ Y, ubyte @ A, ubyte @ X {
|
||||||
|
; ---- A (signed byte) to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
|
||||||
|
; note: if the number is negative, you have to deal with the '-' yourself!
|
||||||
%asm {{
|
%asm {{
|
||||||
cmp #0
|
cmp #0
|
||||||
bpl +
|
bpl +
|
||||||
@ -48,7 +219,7 @@ asmsub byte2decimal (ubyte value @ A) -> ubyte @ Y, ubyte @ X, ubyte @ A {
|
|||||||
}
|
}
|
||||||
|
|
||||||
asmsub ubyte2hex (ubyte value @ A) -> ubyte @ A, ubyte @ Y {
|
asmsub ubyte2hex (ubyte value @ A) -> ubyte @ A, ubyte @ Y {
|
||||||
; ---- A to hex string in AY (first hex char in A, second hex char in Y)
|
; ---- A to hex petscii string in AY (first hex char in A, second hex char in Y)
|
||||||
%asm {{
|
%asm {{
|
||||||
stx c64.SCRATCH_ZPREGX
|
stx c64.SCRATCH_ZPREGX
|
||||||
pha
|
pha
|
||||||
@ -69,7 +240,6 @@ _hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
asmsub uword2hex (uword value @ AY) clobbers(A,Y) {
|
asmsub uword2hex (uword value @ AY) clobbers(A,Y) {
|
||||||
; ---- convert 16 bit uword in A/Y into 4-character hexadecimal string 'uword2hex.output' (0-terminated)
|
; ---- convert 16 bit uword in A/Y into 4-character hexadecimal string 'uword2hex.output' (0-terminated)
|
||||||
%asm {{
|
%asm {{
|
||||||
@ -87,92 +257,6 @@ output .text "0000", $00 ; 0-terminated output buffer (to make printing ea
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
asmsub uword2bcd (uword value @ AY) clobbers(A,Y) {
|
|
||||||
; Convert an 16 bit binary value to BCD
|
|
||||||
;
|
|
||||||
; This function converts a 16 bit binary value in A/Y into a 24 bit BCD. It
|
|
||||||
; works by transferring one bit a time from the source and adding it
|
|
||||||
; into a BCD value that is being doubled on each iteration. As all the
|
|
||||||
; arithmetic is being done in BCD the result is a binary to decimal
|
|
||||||
; conversion.
|
|
||||||
%asm {{
|
|
||||||
sta c64.SCRATCH_ZPB1
|
|
||||||
sty c64.SCRATCH_ZPREG
|
|
||||||
php
|
|
||||||
pla ; read status register
|
|
||||||
and #%00000100
|
|
||||||
sta _had_irqd
|
|
||||||
sei ; disable interrupts because of bcd math
|
|
||||||
sed ; switch to decimal mode
|
|
||||||
lda #0 ; ensure the result is clear
|
|
||||||
sta bcdbuff+0
|
|
||||||
sta bcdbuff+1
|
|
||||||
sta bcdbuff+2
|
|
||||||
ldy #16 ; the number of source bits
|
|
||||||
|
|
||||||
- asl c64.SCRATCH_ZPB1 ; shift out one bit
|
|
||||||
rol c64.SCRATCH_ZPREG
|
|
||||||
lda bcdbuff+0 ; and add into result
|
|
||||||
adc bcdbuff+0
|
|
||||||
sta bcdbuff+0
|
|
||||||
lda bcdbuff+1 ; propagating any carry
|
|
||||||
adc bcdbuff+1
|
|
||||||
sta bcdbuff+1
|
|
||||||
lda bcdbuff+2 ; ... thru whole result
|
|
||||||
adc bcdbuff+2
|
|
||||||
sta bcdbuff+2
|
|
||||||
dey ; and repeat for next bit
|
|
||||||
bne -
|
|
||||||
cld ; back to binary
|
|
||||||
lda _had_irqd
|
|
||||||
bne +
|
|
||||||
cli ; enable interrupts again (only if they were enabled before)
|
|
||||||
+ rts
|
|
||||||
_had_irqd .byte 0
|
|
||||||
bcdbuff .byte 0,0,0
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
asmsub uword2decimal (uword value @ AY) clobbers(A) -> ubyte @ Y {
|
|
||||||
; ---- convert 16 bit uword in A/Y into 0-terminated decimal string into memory 'uword2decimal.output'
|
|
||||||
; returns length of resulting string in Y
|
|
||||||
%asm {{
|
|
||||||
jsr uword2bcd
|
|
||||||
lda uword2bcd.bcdbuff+2
|
|
||||||
clc
|
|
||||||
adc #'0'
|
|
||||||
sta output
|
|
||||||
ldy #1
|
|
||||||
lda uword2bcd.bcdbuff+1
|
|
||||||
jsr +
|
|
||||||
lda uword2bcd.bcdbuff+0
|
|
||||||
|
|
||||||
+ pha
|
|
||||||
lsr a
|
|
||||||
lsr a
|
|
||||||
lsr a
|
|
||||||
lsr a
|
|
||||||
clc
|
|
||||||
adc #'0'
|
|
||||||
sta output,y
|
|
||||||
iny
|
|
||||||
pla
|
|
||||||
and #$0f
|
|
||||||
adc #'0'
|
|
||||||
sta output,y
|
|
||||||
iny
|
|
||||||
lda #0
|
|
||||||
sta output,y
|
|
||||||
rts
|
|
||||||
|
|
||||||
output .text "00000", $00 ; 0 terminated
|
|
||||||
|
|
||||||
}}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
asmsub str2uword(str string @ AY) -> uword @ AY {
|
asmsub str2uword(str string @ AY) -> uword @ AY {
|
||||||
; -- returns the unsigned word value of the string number argument in AY
|
; -- returns the unsigned word value of the string number argument in AY
|
||||||
; the number may NOT be preceded by a + sign and may NOT contain spaces
|
; the number may NOT be preceded by a + sign and may NOT contain spaces
|
||||||
@ -227,7 +311,6 @@ _result_times_10 ; (W*4 + W)*2
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
asmsub str2word(str string @ AY) -> word @ AY {
|
asmsub str2word(str string @ AY) -> word @ AY {
|
||||||
; -- returns the signed word value of the string number argument in AY
|
; -- returns the signed word value of the string number argument in AY
|
||||||
; the number may be preceded by a + or - sign but may NOT contain spaces
|
; the number may be preceded by a + or - sign but may NOT contain spaces
|
||||||
@ -283,7 +366,6 @@ _negative .byte 0
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
asmsub set_irqvec_excl() clobbers(A) {
|
asmsub set_irqvec_excl() clobbers(A) {
|
||||||
%asm {{
|
%asm {{
|
||||||
sei
|
sei
|
||||||
@ -341,6 +423,7 @@ _irq_handler_init
|
|||||||
dex
|
dex
|
||||||
dex
|
dex
|
||||||
dex
|
dex
|
||||||
|
cld
|
||||||
rts
|
rts
|
||||||
|
|
||||||
_irq_handler_end
|
_irq_handler_end
|
||||||
@ -372,7 +455,6 @@ IRQ_SCRATCH_ZPWORD2 .word 0
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
asmsub restore_irqvec() {
|
asmsub restore_irqvec() {
|
||||||
%asm {{
|
%asm {{
|
||||||
sei
|
sei
|
||||||
@ -389,7 +471,6 @@ asmsub restore_irqvec() {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
asmsub set_rasterirq(uword rasterpos @ AY) clobbers(A) {
|
asmsub set_rasterirq(uword rasterpos @ AY) clobbers(A) {
|
||||||
%asm {{
|
%asm {{
|
||||||
sei
|
sei
|
||||||
@ -454,14 +535,12 @@ _raster_irq_handler
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} ; ------ end of block c64utils
|
} ; ------ end of block c64utils
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
c64scr {
|
||||||
~ c64scr {
|
|
||||||
; ---- this block contains (character) Screen and text I/O related functions ----
|
; ---- this block contains (character) Screen and text I/O related functions ----
|
||||||
|
|
||||||
|
|
||||||
@ -480,7 +559,6 @@ asmsub clear_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
|
asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
|
||||||
; ---- clear the character screen with the given fill character (leaves colors)
|
; ---- clear the character screen with the given fill character (leaves colors)
|
||||||
; (assumes screen matrix is at the default address)
|
; (assumes screen matrix is at the default address)
|
||||||
@ -521,7 +599,6 @@ _loop sta c64.Colors,y
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
asmsub scroll_left_full (ubyte alsocolors @ Pc) clobbers(A, Y) {
|
asmsub scroll_left_full (ubyte alsocolors @ Pc) clobbers(A, Y) {
|
||||||
; ---- scroll the whole screen 1 character to the left
|
; ---- scroll the whole screen 1 character to the left
|
||||||
; contents of the rightmost column are unchanged, you should clear/refill this yourself
|
; contents of the rightmost column are unchanged, you should clear/refill this yourself
|
||||||
@ -582,7 +659,6 @@ _scroll_screen ; scroll the screen memory
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
asmsub scroll_right_full (ubyte alsocolors @ Pc) clobbers(A) {
|
asmsub scroll_right_full (ubyte alsocolors @ Pc) clobbers(A) {
|
||||||
; ---- scroll the whole screen 1 character to the right
|
; ---- scroll the whole screen 1 character to the right
|
||||||
; contents of the leftmost column are unchanged, you should clear/refill this yourself
|
; contents of the leftmost column are unchanged, you should clear/refill this yourself
|
||||||
@ -635,7 +711,6 @@ _scroll_screen ; scroll the screen memory
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
asmsub scroll_up_full (ubyte alsocolors @ Pc) clobbers(A) {
|
asmsub scroll_up_full (ubyte alsocolors @ Pc) clobbers(A) {
|
||||||
; ---- scroll the whole screen 1 character up
|
; ---- scroll the whole screen 1 character up
|
||||||
; contents of the bottom row are unchanged, you should refill/clear this yourself
|
; contents of the bottom row are unchanged, you should refill/clear this yourself
|
||||||
@ -688,7 +763,6 @@ _scroll_screen ; scroll the screen memory
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
asmsub scroll_down_full (ubyte alsocolors @ Pc) clobbers(A) {
|
asmsub scroll_down_full (ubyte alsocolors @ Pc) clobbers(A) {
|
||||||
; ---- scroll the whole screen 1 character down
|
; ---- scroll the whole screen 1 character down
|
||||||
; contents of the top row are unchanged, you should refill/clear this yourself
|
; contents of the top row are unchanged, you should refill/clear this yourself
|
||||||
@ -742,7 +816,6 @@ _scroll_screen ; scroll the screen memory
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
asmsub print (str text @ AY) clobbers(A,Y) {
|
asmsub print (str text @ AY) clobbers(A,Y) {
|
||||||
; ---- print null terminated string from A/Y
|
; ---- print null terminated string from A/Y
|
||||||
; note: the compiler contains an optimization that will replace
|
; note: the compiler contains an optimization that will replace
|
||||||
@ -761,7 +834,6 @@ asmsub print (str text @ AY) clobbers(A,Y) {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
asmsub print_ub0 (ubyte value @ A) clobbers(A,Y) {
|
asmsub print_ub0 (ubyte value @ A) clobbers(A,Y) {
|
||||||
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
|
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
|
||||||
%asm {{
|
%asm {{
|
||||||
@ -770,16 +842,15 @@ asmsub print_ub0 (ubyte value @ A) clobbers(A,Y) {
|
|||||||
pha
|
pha
|
||||||
tya
|
tya
|
||||||
jsr c64.CHROUT
|
jsr c64.CHROUT
|
||||||
txa
|
|
||||||
jsr c64.CHROUT
|
|
||||||
pla
|
pla
|
||||||
jsr c64.CHROUT
|
jsr c64.CHROUT
|
||||||
|
txa
|
||||||
|
jsr c64.CHROUT
|
||||||
ldx c64.SCRATCH_ZPREGX
|
ldx c64.SCRATCH_ZPREGX
|
||||||
rts
|
rts
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
asmsub print_ub (ubyte value @ A) clobbers(A,Y) {
|
asmsub print_ub (ubyte value @ A) clobbers(A,Y) {
|
||||||
; ---- print the ubyte in A in decimal form, without left padding 0s
|
; ---- print the ubyte in A in decimal form, without left padding 0s
|
||||||
%asm {{
|
%asm {{
|
||||||
@ -788,15 +859,14 @@ asmsub print_ub (ubyte value @ A) clobbers(A,Y) {
|
|||||||
_print_byte_digits
|
_print_byte_digits
|
||||||
pha
|
pha
|
||||||
cpy #'0'
|
cpy #'0'
|
||||||
bne _print_hundreds
|
beq +
|
||||||
cpx #'0'
|
tya
|
||||||
bne _print_tens
|
|
||||||
jmp _end
|
|
||||||
_print_hundreds tya
|
|
||||||
jsr c64.CHROUT
|
jsr c64.CHROUT
|
||||||
_print_tens txa
|
+ pla
|
||||||
jsr c64.CHROUT
|
cmp #'0'
|
||||||
_end pla
|
beq +
|
||||||
|
jsr c64.CHROUT
|
||||||
|
+ txa
|
||||||
jsr c64.CHROUT
|
jsr c64.CHROUT
|
||||||
ldx c64.SCRATCH_ZPREGX
|
ldx c64.SCRATCH_ZPREGX
|
||||||
rts
|
rts
|
||||||
@ -820,8 +890,7 @@ asmsub print_b (byte value @ A) clobbers(A,Y) {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
asmsub print_ubhex (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||||
asmsub print_ubhex (ubyte prefix @ Pc, ubyte value @ A) clobbers(A,Y) {
|
|
||||||
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
|
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
|
||||||
%asm {{
|
%asm {{
|
||||||
stx c64.SCRATCH_ZPREGX
|
stx c64.SCRATCH_ZPREGX
|
||||||
@ -839,8 +908,7 @@ asmsub print_ubhex (ubyte prefix @ Pc, ubyte value @ A) clobbers(A,Y) {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
asmsub print_ubbin (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||||
asmsub print_ubbin (ubyte prefix @ Pc, ubyte value @ A) clobbers(A,Y) {
|
|
||||||
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
||||||
%asm {{
|
%asm {{
|
||||||
stx c64.SCRATCH_ZPREGX
|
stx c64.SCRATCH_ZPREGX
|
||||||
@ -861,8 +929,7 @@ asmsub print_ubbin (ubyte prefix @ Pc, ubyte value @ A) clobbers(A,Y) {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||||
asmsub print_uwbin (ubyte prefix @ Pc, uword value @ AY) clobbers(A,Y) {
|
|
||||||
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
||||||
%asm {{
|
%asm {{
|
||||||
pha
|
pha
|
||||||
@ -874,8 +941,7 @@ asmsub print_uwbin (ubyte prefix @ Pc, uword value @ AY) clobbers(A,Y) {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||||
asmsub print_uwhex (ubyte prefix @ Pc, uword value @ AY) clobbers(A,Y) {
|
|
||||||
; ---- print the uword in A/Y in hexadecimal form (4 digits)
|
; ---- print the uword in A/Y in hexadecimal form (4 digits)
|
||||||
; (if Carry is set, a radix prefix '$' is printed as well)
|
; (if Carry is set, a radix prefix '$' is printed as well)
|
||||||
%asm {{
|
%asm {{
|
||||||
@ -888,51 +954,44 @@ asmsub print_uwhex (ubyte prefix @ Pc, uword value @ AY) clobbers(A,Y) {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
asmsub print_uw0 (uword value @ AY) clobbers(A,Y) {
|
asmsub print_uw0 (uword value @ AY) clobbers(A,Y) {
|
||||||
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
|
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
|
||||||
%asm {{
|
%asm {{
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
jsr c64utils.uword2decimal
|
jsr c64utils.uword2decimal
|
||||||
ldy #0
|
ldy #0
|
||||||
- lda c64utils.uword2decimal.output,y
|
- lda c64utils.uword2decimal.decTenThousands,y
|
||||||
|
beq +
|
||||||
jsr c64.CHROUT
|
jsr c64.CHROUT
|
||||||
iny
|
iny
|
||||||
cpy #5
|
|
||||||
bne -
|
bne -
|
||||||
|
+ ldx c64.SCRATCH_ZPREGX
|
||||||
rts
|
rts
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
asmsub print_uw (uword value @ AY) clobbers(A,Y) {
|
asmsub print_uw (uword value @ AY) clobbers(A,Y) {
|
||||||
; ---- print the uword in A/Y in decimal form, without left padding 0s
|
; ---- print the uword in A/Y in decimal form, without left padding 0s
|
||||||
%asm {{
|
%asm {{
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
jsr c64utils.uword2decimal
|
jsr c64utils.uword2decimal
|
||||||
ldy #0
|
ldy #0
|
||||||
lda c64utils.uword2decimal.output
|
- lda c64utils.uword2decimal.decTenThousands,y
|
||||||
|
beq _allzero
|
||||||
cmp #'0'
|
cmp #'0'
|
||||||
bne _pr_decimal
|
bne _gotdigit
|
||||||
iny
|
|
||||||
lda c64utils.uword2decimal.output+1
|
|
||||||
cmp #'0'
|
|
||||||
bne _pr_decimal
|
|
||||||
iny
|
|
||||||
lda c64utils.uword2decimal.output+2
|
|
||||||
cmp #'0'
|
|
||||||
bne _pr_decimal
|
|
||||||
iny
|
|
||||||
lda c64utils.uword2decimal.output+3
|
|
||||||
cmp #'0'
|
|
||||||
bne _pr_decimal
|
|
||||||
iny
|
iny
|
||||||
|
bne -
|
||||||
|
|
||||||
_pr_decimal
|
_gotdigit
|
||||||
lda c64utils.uword2decimal.output,y
|
|
||||||
jsr c64.CHROUT
|
jsr c64.CHROUT
|
||||||
iny
|
iny
|
||||||
cpy #5
|
lda c64utils.uword2decimal.decTenThousands,y
|
||||||
bcc _pr_decimal
|
bne _gotdigit
|
||||||
rts
|
rts
|
||||||
|
_allzero
|
||||||
|
lda #'0'
|
||||||
|
jmp c64.CHROUT
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1063,7 +1122,7 @@ _mod lda $ffff ; modified
|
|||||||
sub setcc (ubyte column, ubyte row, ubyte char, ubyte color) {
|
sub setcc (ubyte column, ubyte row, ubyte char, ubyte color) {
|
||||||
; ---- set char+color at the given position on the screen
|
; ---- set char+color at the given position on the screen
|
||||||
%asm {{
|
%asm {{
|
||||||
lda setcc_row
|
lda row
|
||||||
asl a
|
asl a
|
||||||
tay
|
tay
|
||||||
lda setchr._screenrows+1,y
|
lda setchr._screenrows+1,y
|
||||||
@ -1072,15 +1131,15 @@ sub setcc (ubyte column, ubyte row, ubyte char, ubyte color) {
|
|||||||
sta _colormod+2
|
sta _colormod+2
|
||||||
lda setchr._screenrows,y
|
lda setchr._screenrows,y
|
||||||
clc
|
clc
|
||||||
adc setcc_column
|
adc column
|
||||||
sta _charmod+1
|
sta _charmod+1
|
||||||
sta _colormod+1
|
sta _colormod+1
|
||||||
bcc +
|
bcc +
|
||||||
inc _charmod+2
|
inc _charmod+2
|
||||||
inc _colormod+2
|
inc _colormod+2
|
||||||
+ lda setcc_char
|
+ lda char
|
||||||
_charmod sta $ffff ; modified
|
_charmod sta $ffff ; modified
|
||||||
lda setcc_color
|
lda color
|
||||||
_colormod sta $ffff ; modified
|
_colormod sta $ffff ; modified
|
||||||
rts
|
rts
|
||||||
}}
|
}}
|
||||||
|
@ -643,3 +643,40 @@ mul_word_40 .proc
|
|||||||
sta c64.ESTACK_HI+1,x
|
sta c64.ESTACK_HI+1,x
|
||||||
rts
|
rts
|
||||||
.pend
|
.pend
|
||||||
|
|
||||||
|
sign_b .proc
|
||||||
|
lda c64.ESTACK_LO+1,x
|
||||||
|
beq _sign_zero
|
||||||
|
bmi _sign_neg
|
||||||
|
_sign_pos lda #1
|
||||||
|
sta c64.ESTACK_LO+1,x
|
||||||
|
rts
|
||||||
|
_sign_neg lda #-1
|
||||||
|
_sign_zero sta c64.ESTACK_LO+1,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
sign_ub .proc
|
||||||
|
lda c64.ESTACK_LO+1,x
|
||||||
|
beq sign_b._sign_zero
|
||||||
|
bne sign_b._sign_pos
|
||||||
|
.pend
|
||||||
|
|
||||||
|
sign_w .proc
|
||||||
|
lda c64.ESTACK_HI+1,x
|
||||||
|
bmi sign_b._sign_neg
|
||||||
|
beq sign_ub
|
||||||
|
bne sign_b._sign_pos
|
||||||
|
.pend
|
||||||
|
|
||||||
|
sign_uw .proc
|
||||||
|
lda c64.ESTACK_HI+1,x
|
||||||
|
beq _sign_possibly_zero
|
||||||
|
_sign_pos lda #1
|
||||||
|
sta c64.ESTACK_LO+1,x
|
||||||
|
rts
|
||||||
|
_sign_possibly_zero lda c64.ESTACK_LO+1,x
|
||||||
|
bne _sign_pos
|
||||||
|
sta c64.ESTACK_LO+1,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
@ -6,6 +6,6 @@
|
|||||||
|
|
||||||
%import c64lib
|
%import c64lib
|
||||||
|
|
||||||
~ math {
|
math {
|
||||||
%asminclude "library:math.asm", ""
|
%asminclude "library:math.asm", ""
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,17 @@ init_system .proc
|
|||||||
.pend
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
read_byte_from_address .proc
|
||||||
|
; -- read the byte from the memory address on the top of the stack, return in A (stack remains unchanged)
|
||||||
|
lda c64.ESTACK_LO+1,x
|
||||||
|
ldy c64.ESTACK_HI+1,x
|
||||||
|
sta (+) +1
|
||||||
|
sty (+) +2
|
||||||
|
+ lda $ffff ; modified
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
add_a_to_zpword .proc
|
add_a_to_zpword .proc
|
||||||
; -- add ubyte in A to the uword in c64.SCRATCH_ZPWORD1
|
; -- add ubyte in A to the uword in c64.SCRATCH_ZPWORD1
|
||||||
clc
|
clc
|
||||||
@ -840,11 +851,12 @@ func_all_w .proc
|
|||||||
bne +
|
bne +
|
||||||
iny
|
iny
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
bne +
|
bne ++
|
||||||
lda #0
|
lda #0
|
||||||
sta c64.ESTACK_LO+1,x
|
sta c64.ESTACK_LO+1,x
|
||||||
rts
|
rts
|
||||||
+ iny
|
+ iny
|
||||||
|
+ iny
|
||||||
_cmp_mod cpy #255 ; modified
|
_cmp_mod cpy #255 ; modified
|
||||||
bne -
|
bne -
|
||||||
lda #1
|
lda #1
|
||||||
@ -1372,3 +1384,378 @@ _mod2b lda #0 ; self-modified
|
|||||||
_done rts
|
_done rts
|
||||||
.pend
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
sort_ub .proc
|
||||||
|
; 8bit unsigned sort
|
||||||
|
; sorting subroutine coded by mats rosengren (mats.rosengren@esa.int)
|
||||||
|
; input: address of array to sort in c64.SCRATCH_ZPWORD1, length in c64.SCRATCH_ZPB1
|
||||||
|
; first, put pointer BEFORE array
|
||||||
|
lda c64.SCRATCH_ZPWORD1
|
||||||
|
bne +
|
||||||
|
dec c64.SCRATCH_ZPWORD1+1
|
||||||
|
+ dec c64.SCRATCH_ZPWORD1
|
||||||
|
_sortloop ldy c64.SCRATCH_ZPB1 ;start of subroutine sort
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y ;last value in (what is left of) sequence to be sorted
|
||||||
|
sta c64.SCRATCH_ZPREG ;save value. will be over-written by largest number
|
||||||
|
jmp _l2
|
||||||
|
_l1 dey
|
||||||
|
beq _l3
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
cmp c64.SCRATCH_ZPWORD2+1
|
||||||
|
bcc _l1
|
||||||
|
_l2 sty c64.SCRATCH_ZPWORD2 ;index of potentially largest value
|
||||||
|
sta c64.SCRATCH_ZPWORD2+1 ;potentially largest value
|
||||||
|
jmp _l1
|
||||||
|
_l3 ldy c64.SCRATCH_ZPB1 ;where the largest value shall be put
|
||||||
|
lda c64.SCRATCH_ZPWORD2+1 ;the largest value
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y ;put largest value in place
|
||||||
|
ldy c64.SCRATCH_ZPWORD2 ;index of free space
|
||||||
|
lda c64.SCRATCH_ZPREG ;the over-written value
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y ;put the over-written value in the free space
|
||||||
|
dec c64.SCRATCH_ZPB1 ;end of the shorter sequence still left
|
||||||
|
bne _sortloop ;start working with the shorter sequence
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
sort_b .proc
|
||||||
|
; 8bit signed sort
|
||||||
|
; sorting subroutine coded by mats rosengren (mats.rosengren@esa.int)
|
||||||
|
; input: address of array to sort in c64.SCRATCH_ZPWORD1, length in c64.SCRATCH_ZPB1
|
||||||
|
; first, put pointer BEFORE array
|
||||||
|
lda c64.SCRATCH_ZPWORD1
|
||||||
|
bne +
|
||||||
|
dec c64.SCRATCH_ZPWORD1+1
|
||||||
|
+ dec c64.SCRATCH_ZPWORD1
|
||||||
|
_sortloop ldy c64.SCRATCH_ZPB1 ;start of subroutine sort
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y ;last value in (what is left of) sequence to be sorted
|
||||||
|
sta c64.SCRATCH_ZPREG ;save value. will be over-written by largest number
|
||||||
|
jmp _l2
|
||||||
|
_l1 dey
|
||||||
|
beq _l3
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
cmp c64.SCRATCH_ZPWORD2+1
|
||||||
|
bmi _l1
|
||||||
|
_l2 sty c64.SCRATCH_ZPWORD2 ;index of potentially largest value
|
||||||
|
sta c64.SCRATCH_ZPWORD2+1 ;potentially largest value
|
||||||
|
jmp _l1
|
||||||
|
_l3 ldy c64.SCRATCH_ZPB1 ;where the largest value shall be put
|
||||||
|
lda c64.SCRATCH_ZPWORD2+1 ;the largest value
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y ;put largest value in place
|
||||||
|
ldy c64.SCRATCH_ZPWORD2 ;index of free space
|
||||||
|
lda c64.SCRATCH_ZPREG ;the over-written value
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y ;put the over-written value in the free space
|
||||||
|
dec c64.SCRATCH_ZPB1 ;end of the shorter sequence still left
|
||||||
|
bne _sortloop ;start working with the shorter sequence
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
sort_uw .proc
|
||||||
|
; 16bit unsigned sort
|
||||||
|
; sorting subroutine coded by mats rosengren (mats.rosengren@esa.int)
|
||||||
|
; input: address of array to sort in c64.SCRATCH_ZPWORD1, length in c64.SCRATCH_ZPB1
|
||||||
|
; first: subtract 2 of the pointer
|
||||||
|
asl c64.SCRATCH_ZPB1 ; *2 because words
|
||||||
|
lda c64.SCRATCH_ZPWORD1
|
||||||
|
sec
|
||||||
|
sbc #2
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
bcs _sort_loop
|
||||||
|
dec c64.SCRATCH_ZPWORD1+1
|
||||||
|
_sort_loop ldy c64.SCRATCH_ZPB1 ;start of subroutine sort
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y ;last value in (what is left of) sequence to be sorted
|
||||||
|
sta _work3 ;save value. will be over-written by largest number
|
||||||
|
iny
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
sta _work3+1
|
||||||
|
dey
|
||||||
|
jmp _l2
|
||||||
|
_l1 dey
|
||||||
|
dey
|
||||||
|
beq _l3
|
||||||
|
iny
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
dey
|
||||||
|
cmp c64.SCRATCH_ZPWORD2+1
|
||||||
|
bne +
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
cmp c64.SCRATCH_ZPWORD2
|
||||||
|
+ bcc _l1
|
||||||
|
_l2 sty _work1 ;index of potentially largest value
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
sta c64.SCRATCH_ZPWORD2 ;potentially largest value
|
||||||
|
iny
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
sta c64.SCRATCH_ZPWORD2+1
|
||||||
|
dey
|
||||||
|
jmp _l1
|
||||||
|
_l3 ldy c64.SCRATCH_ZPB1 ;where the largest value shall be put
|
||||||
|
lda c64.SCRATCH_ZPWORD2 ;the largest value
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y ;put largest value in place
|
||||||
|
iny
|
||||||
|
lda c64.SCRATCH_ZPWORD2+1
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
ldy _work1 ;index of free space
|
||||||
|
lda _work3 ;the over-written value
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y ;put the over-written value in the free space
|
||||||
|
iny
|
||||||
|
lda _work3+1
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
dey
|
||||||
|
dec c64.SCRATCH_ZPB1 ;end of the shorter sequence still left
|
||||||
|
dec c64.SCRATCH_ZPB1
|
||||||
|
bne _sort_loop ;start working with the shorter sequence
|
||||||
|
rts
|
||||||
|
_work1 .byte 0
|
||||||
|
_work3 .word 0
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
sort_w .proc
|
||||||
|
; 16bit signed sort
|
||||||
|
; sorting subroutine coded by mats rosengren (mats.rosengren@esa.int)
|
||||||
|
; input: address of array to sort in c64.SCRATCH_ZPWORD1, length in c64.SCRATCH_ZPB1
|
||||||
|
; first: subtract 2 of the pointer
|
||||||
|
asl c64.SCRATCH_ZPB1 ; *2 because words
|
||||||
|
lda c64.SCRATCH_ZPWORD1
|
||||||
|
sec
|
||||||
|
sbc #2
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
bcs _sort_loop
|
||||||
|
dec c64.SCRATCH_ZPWORD1+1
|
||||||
|
_sort_loop ldy c64.SCRATCH_ZPB1 ;start of subroutine sort
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y ;last value in (what is left of) sequence to be sorted
|
||||||
|
sta _work3 ;save value. will be over-written by largest number
|
||||||
|
iny
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
sta _work3+1
|
||||||
|
dey
|
||||||
|
jmp _l2
|
||||||
|
_l1 dey
|
||||||
|
dey
|
||||||
|
beq _l3
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
cmp c64.SCRATCH_ZPWORD2
|
||||||
|
iny
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
dey
|
||||||
|
sbc c64.SCRATCH_ZPWORD2+1
|
||||||
|
bvc +
|
||||||
|
eor #$80
|
||||||
|
+ bmi _l1
|
||||||
|
_l2 sty _work1 ;index of potentially largest value
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
sta c64.SCRATCH_ZPWORD2 ;potentially largest value
|
||||||
|
iny
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
sta c64.SCRATCH_ZPWORD2+1
|
||||||
|
dey
|
||||||
|
jmp _l1
|
||||||
|
_l3 ldy c64.SCRATCH_ZPB1 ;where the largest value shall be put
|
||||||
|
lda c64.SCRATCH_ZPWORD2 ;the largest value
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y ;put largest value in place
|
||||||
|
iny
|
||||||
|
lda c64.SCRATCH_ZPWORD2+1
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
ldy _work1 ;index of free space
|
||||||
|
lda _work3 ;the over-written value
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y ;put the over-written value in the free space
|
||||||
|
iny
|
||||||
|
lda _work3+1
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
dey
|
||||||
|
dec c64.SCRATCH_ZPB1 ;end of the shorter sequence still left
|
||||||
|
dec c64.SCRATCH_ZPB1
|
||||||
|
bne _sort_loop ;start working with the shorter sequence
|
||||||
|
rts
|
||||||
|
_work1 .byte 0
|
||||||
|
_work3 .word 0
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
reverse_b .proc
|
||||||
|
; --- reverse an array of bytes (in-place)
|
||||||
|
; inputs: pointer to array in c64.SCRATCH_ZPWORD1, length in A
|
||||||
|
_left_index = c64.SCRATCH_ZPWORD2
|
||||||
|
_right_index = c64.SCRATCH_ZPWORD2+1
|
||||||
|
pha
|
||||||
|
sec
|
||||||
|
sbc #1
|
||||||
|
sta _left_index
|
||||||
|
lda #0
|
||||||
|
sta _right_index
|
||||||
|
pla
|
||||||
|
lsr a
|
||||||
|
tay
|
||||||
|
_loop sty c64.SCRATCH_ZPREG
|
||||||
|
ldy _left_index
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
pha
|
||||||
|
ldy _right_index
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
ldy _left_index
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
pla
|
||||||
|
ldy _right_index
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
inc _right_index
|
||||||
|
dec _left_index
|
||||||
|
ldy c64.SCRATCH_ZPREG
|
||||||
|
dey
|
||||||
|
bne _loop
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
reverse_w .proc
|
||||||
|
; --- reverse an array of words (in-place)
|
||||||
|
; inputs: pointer to array in c64.SCRATCH_ZPWORD1, length in A
|
||||||
|
_left_index = c64.SCRATCH_ZPWORD2
|
||||||
|
_right_index = c64.SCRATCH_ZPWORD2+1
|
||||||
|
pha
|
||||||
|
asl a ; *2 because words
|
||||||
|
sec
|
||||||
|
sbc #2
|
||||||
|
sta _left_index
|
||||||
|
lda #0
|
||||||
|
sta _right_index
|
||||||
|
pla
|
||||||
|
lsr a
|
||||||
|
pha
|
||||||
|
tay
|
||||||
|
; first reverse the lsbs
|
||||||
|
_loop_lo sty c64.SCRATCH_ZPREG
|
||||||
|
ldy _left_index
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
pha
|
||||||
|
ldy _right_index
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
ldy _left_index
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
pla
|
||||||
|
ldy _right_index
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
inc _right_index
|
||||||
|
inc _right_index
|
||||||
|
dec _left_index
|
||||||
|
dec _left_index
|
||||||
|
ldy c64.SCRATCH_ZPREG
|
||||||
|
dey
|
||||||
|
bne _loop_lo
|
||||||
|
; now reverse the msbs
|
||||||
|
dec _right_index
|
||||||
|
inc _left_index
|
||||||
|
inc _left_index
|
||||||
|
inc _left_index
|
||||||
|
pla
|
||||||
|
tay
|
||||||
|
_loop_hi sty c64.SCRATCH_ZPREG
|
||||||
|
ldy _left_index
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
pha
|
||||||
|
ldy _right_index
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
ldy _left_index
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
pla
|
||||||
|
ldy _right_index
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
dec _right_index
|
||||||
|
dec _right_index
|
||||||
|
inc _left_index
|
||||||
|
inc _left_index
|
||||||
|
ldy c64.SCRATCH_ZPREG
|
||||||
|
dey
|
||||||
|
bne _loop_hi
|
||||||
|
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
ror2_mem_ub .proc
|
||||||
|
; -- in-place 8-bit ror of byte at memory location on stack
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
lda c64.ESTACK_HI,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1+1
|
||||||
|
ldy #0
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
lsr a
|
||||||
|
bcc +
|
||||||
|
ora #$80
|
||||||
|
+ sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
rol2_mem_ub .proc
|
||||||
|
; -- in-place 8-bit rol of byte at memory location on stack
|
||||||
|
;" lda ${number.toHex()} | cmp #\$80 | rol a | sta ${number.toHex()}"
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
lda c64.ESTACK_HI,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1+1
|
||||||
|
ldy #0
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
cmp #$80
|
||||||
|
rol a
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
lsl_array_b .proc
|
||||||
|
.warn "lsl_array_b" ; TODO
|
||||||
|
.pend
|
||||||
|
|
||||||
|
lsl_array_w .proc
|
||||||
|
.warn "lsl_array_w" ; TODO
|
||||||
|
.pend
|
||||||
|
|
||||||
|
lsr_array_ub .proc
|
||||||
|
.warn "lsr_array_ub" ; TODO
|
||||||
|
.pend
|
||||||
|
|
||||||
|
lsr_array_b .proc
|
||||||
|
.warn "lsr_array_b" ; TODO
|
||||||
|
.pend
|
||||||
|
|
||||||
|
lsr_array_uw .proc
|
||||||
|
.warn "lsr_array_uw" ; TODO
|
||||||
|
.pend
|
||||||
|
|
||||||
|
lsr_array_w .proc
|
||||||
|
.warn "lsr_array_w" ; TODO
|
||||||
|
.pend
|
||||||
|
|
||||||
|
rol_array_ub .proc
|
||||||
|
.warn "rol_array_ub" ; TODO
|
||||||
|
.pend
|
||||||
|
|
||||||
|
rol_array_uw .proc
|
||||||
|
.warn "rol_array_uw" ; TODO
|
||||||
|
.pend
|
||||||
|
|
||||||
|
rol2_array_ub .proc
|
||||||
|
.warn "rol2_array_ub" ; TODO
|
||||||
|
.pend
|
||||||
|
|
||||||
|
rol2_array_uw .proc
|
||||||
|
.warn "rol2_array_uw" ; TODO
|
||||||
|
.pend
|
||||||
|
|
||||||
|
ror_array_ub .proc
|
||||||
|
.warn "ror_array_ub" ; TODO
|
||||||
|
.pend
|
||||||
|
|
||||||
|
ror_array_uw .proc
|
||||||
|
.warn "ror_array_uw" ; TODO
|
||||||
|
.pend
|
||||||
|
|
||||||
|
ror2_array_ub .proc
|
||||||
|
.warn "ror2_array_ub" ; TODO
|
||||||
|
.pend
|
||||||
|
|
||||||
|
ror2_array_uw .proc
|
||||||
|
.warn "ror2_array_uw" ; TODO
|
||||||
|
.pend
|
||||||
|
@ -6,6 +6,6 @@
|
|||||||
|
|
||||||
%import c64lib
|
%import c64lib
|
||||||
|
|
||||||
~ prog8_lib {
|
prog8_lib {
|
||||||
%asminclude "library:prog8lib.asm", ""
|
%asminclude "library:prog8lib.asm", ""
|
||||||
}
|
}
|
||||||
|
@ -1 +1 @@
|
|||||||
1.10
|
1.62
|
||||||
|
@ -1,25 +1,21 @@
|
|||||||
package prog8
|
package prog8
|
||||||
|
|
||||||
|
import kotlinx.cli.*
|
||||||
|
import prog8.ast.base.AstException
|
||||||
|
import prog8.compiler.CompilationResult
|
||||||
|
import prog8.compiler.compileProgram
|
||||||
|
import prog8.parser.ParsingFailedError
|
||||||
import prog8.vm.astvm.AstVm
|
import prog8.vm.astvm.AstVm
|
||||||
import prog8.vm.stackvm.stackVmMain
|
import java.nio.file.FileSystems
|
||||||
import prog8.compiler.*
|
import java.nio.file.Path
|
||||||
import java.nio.file.Paths
|
import java.nio.file.StandardWatchEventKinds
|
||||||
|
import java.time.LocalDateTime
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
|
|
||||||
// check if the user wants to launch the VM instead
|
|
||||||
if("-vm" in args) {
|
|
||||||
val newArgs = args.toMutableList()
|
|
||||||
newArgs.remove("-vm")
|
|
||||||
return stackVmMain(newArgs.toTypedArray())
|
|
||||||
}
|
|
||||||
|
|
||||||
printSoftwareHeader("compiler")
|
printSoftwareHeader("compiler")
|
||||||
|
|
||||||
if (args.isEmpty())
|
|
||||||
usage()
|
|
||||||
compileMain(args)
|
compileMain(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,72 +26,92 @@ internal fun printSoftwareHeader(what: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun pathFrom(stringPath: String, vararg rest: String): Path = FileSystems.getDefault().getPath(stringPath, *rest)
|
||||||
|
|
||||||
|
|
||||||
private fun compileMain(args: Array<String>) {
|
private fun compileMain(args: Array<String>) {
|
||||||
var emulatorToStart = ""
|
val cli = CommandLineInterface("prog8compiler")
|
||||||
var moduleFile = ""
|
val startEmulator1 by cli.flagArgument("-emu", "auto-start the 'x64' C-64 emulator after successful compilation")
|
||||||
var writeVmCode = false
|
val startEmulator2 by cli.flagArgument("-emu2", "auto-start the 'x64sc' C-64 emulator after successful compilation")
|
||||||
var writeAssembly = true
|
val outputDir by cli.flagValueArgument("-out", "directory", "directory for output files instead of current directory", ".")
|
||||||
var optimize = true
|
val dontWriteAssembly by cli.flagArgument("-noasm", "don't create assembly code")
|
||||||
var optimizeInlining = true
|
val dontOptimize by cli.flagArgument("-noopt", "don't perform any optimizations")
|
||||||
var launchAstVm = false
|
val launchSimulator by cli.flagArgument("-sim", "launch the prog8 virtual machine/simulator after compilation")
|
||||||
for (arg in args) {
|
val watchMode by cli.flagArgument("-watch", "continuous compilation mode (watches for file changes)")
|
||||||
if(arg=="-emu")
|
val moduleFiles by cli.positionalArgumentsList("modules", "main module file(s) to compile", minArgs = 1)
|
||||||
emulatorToStart = "x64"
|
|
||||||
else if(arg=="-emu2")
|
|
||||||
emulatorToStart = "x64sc"
|
|
||||||
else if(arg=="-writevm")
|
|
||||||
writeVmCode = true
|
|
||||||
else if(arg=="-noasm")
|
|
||||||
writeAssembly = false
|
|
||||||
else if(arg=="-noopt")
|
|
||||||
optimize = false
|
|
||||||
else if(arg=="-nooptinline")
|
|
||||||
optimizeInlining = false
|
|
||||||
else if(arg=="-avm")
|
|
||||||
launchAstVm = true
|
|
||||||
else if(!arg.startsWith("-"))
|
|
||||||
moduleFile = arg
|
|
||||||
else
|
|
||||||
usage()
|
|
||||||
}
|
|
||||||
if(moduleFile.isBlank())
|
|
||||||
usage()
|
|
||||||
|
|
||||||
val filepath = Paths.get(moduleFile).normalize()
|
try {
|
||||||
|
cli.parse(args)
|
||||||
val (programAst, programName) = compileProgram(filepath, optimize, optimizeInlining,
|
} catch (e: Exception) {
|
||||||
!launchAstVm, writeVmCode, writeAssembly)
|
exitProcess(1)
|
||||||
|
|
||||||
if(launchAstVm) {
|
|
||||||
println("\nLaunching AST-based vm...")
|
|
||||||
val vm = AstVm(programAst)
|
|
||||||
vm.run()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(emulatorToStart.isNotEmpty()) {
|
val outputPath = pathFrom(outputDir)
|
||||||
if(programName==null)
|
if(!outputPath.toFile().isDirectory) {
|
||||||
println("\nCan't start emulator because no program was assembled.")
|
System.err.println("Output path doesn't exist")
|
||||||
else {
|
exitProcess(1)
|
||||||
println("\nStarting C-64 emulator $emulatorToStart...")
|
}
|
||||||
val cmdline = listOf(emulatorToStart, "-silent", "-moncommands", "$programName.vice-mon-list",
|
|
||||||
"-autostartprgmode", "1", "-autostart-warp", "-autostart", programName + ".prg")
|
if(watchMode && moduleFiles.size<=1) {
|
||||||
val process = ProcessBuilder(cmdline).inheritIO().start()
|
val watchservice = FileSystems.getDefault().newWatchService()
|
||||||
process.waitFor()
|
|
||||||
|
while(true) {
|
||||||
|
val filepath = pathFrom(moduleFiles.single()).normalize()
|
||||||
|
println("Continuous watch mode active. Main module: $filepath")
|
||||||
|
|
||||||
|
try {
|
||||||
|
val compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, outputDir=outputPath)
|
||||||
|
println("Imported files (now watching:)")
|
||||||
|
for (importedFile in compilationResult.importedFiles) {
|
||||||
|
print(" ")
|
||||||
|
println(importedFile)
|
||||||
|
importedFile.parent.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY)
|
||||||
|
}
|
||||||
|
println("[${LocalDateTime.now().withNano(0)}] Waiting for file changes.")
|
||||||
|
val event = watchservice.take()
|
||||||
|
for(changed in event.pollEvents()) {
|
||||||
|
val changedPath = changed.context() as Path
|
||||||
|
println(" change detected: $changedPath")
|
||||||
|
}
|
||||||
|
event.reset()
|
||||||
|
println("\u001b[H\u001b[2J") // clear the screen
|
||||||
|
} catch (x: Exception) {
|
||||||
|
throw x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
for(filepathRaw in moduleFiles) {
|
||||||
|
val filepath = pathFrom(filepathRaw).normalize()
|
||||||
|
val compilationResult: CompilationResult
|
||||||
|
try {
|
||||||
|
compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, outputDir=outputPath)
|
||||||
|
if(!compilationResult.success)
|
||||||
|
exitProcess(1)
|
||||||
|
} catch (x: ParsingFailedError) {
|
||||||
|
exitProcess(1)
|
||||||
|
} catch (x: AstException) {
|
||||||
|
exitProcess(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (launchSimulator) {
|
||||||
|
println("\nLaunching AST-based simulator...")
|
||||||
|
val vm = AstVm(compilationResult.programAst)
|
||||||
|
vm.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startEmulator1 || startEmulator2) {
|
||||||
|
if (compilationResult.programName.isEmpty())
|
||||||
|
println("\nCan't start emulator because no program was assembled.")
|
||||||
|
else {
|
||||||
|
val emulator = if(startEmulator1) "x64" else "x64sc"
|
||||||
|
println("\nStarting C-64 emulator $emulator...")
|
||||||
|
val cmdline = listOf(emulator, "-silent", "-moncommands", "${compilationResult.programName}.vice-mon-list",
|
||||||
|
"-autostartprgmode", "1", "-autostart-warp", "-autostart", compilationResult.programName + ".prg")
|
||||||
|
val process = ProcessBuilder(cmdline).inheritIO().start()
|
||||||
|
process.waitFor()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun usage() {
|
|
||||||
System.err.println("Missing argument(s):")
|
|
||||||
System.err.println(" [-emu] auto-start the 'x64' C-64 emulator after successful compilation")
|
|
||||||
System.err.println(" [-emu2] auto-start the 'x64sc' C-64 emulator after successful compilation")
|
|
||||||
System.err.println(" [-writevm] write intermediate vm code to a file as well")
|
|
||||||
System.err.println(" [-noasm] don't create assembly code")
|
|
||||||
System.err.println(" [-vm] launch the prog8 virtual machine instead of the compiler")
|
|
||||||
System.err.println(" [-avm] launch the prog8 ast-based virtual machine after compilation")
|
|
||||||
System.err.println(" [-noopt] don't perform any optimizations")
|
|
||||||
System.err.println(" [-nooptinline] don't perform subroutine inlining optimizations")
|
|
||||||
System.err.println(" modulefile main module file to compile")
|
|
||||||
exitProcess(1)
|
|
||||||
}
|
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
package prog8.compiler
|
package prog8.ast
|
||||||
|
|
||||||
import prog8.ast.antlr.escape
|
import prog8.ast.antlr.escape
|
||||||
import prog8.ast.IFunctionCall
|
import prog8.ast.base.DataType
|
||||||
import prog8.ast.IStatement
|
import prog8.ast.base.NumericDatatypes
|
||||||
import prog8.ast.Module
|
import prog8.ast.base.StringDatatypes
|
||||||
import prog8.ast.Program
|
import prog8.ast.base.VarDeclType
|
||||||
import prog8.ast.base.*
|
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.processing.IAstVisitor
|
import prog8.ast.processing.IAstVisitor
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
|
import prog8.compiler.toHex
|
||||||
|
|
||||||
class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
|
class AstToSourceCode(val output: (text: String) -> Unit, val program: Program): IAstVisitor {
|
||||||
var scopelevel = 0
|
private var scopelevel = 0
|
||||||
|
|
||||||
fun indent(s: String) = " ".repeat(scopelevel) + s
|
private fun indent(s: String) = " ".repeat(scopelevel) + s
|
||||||
fun outputln(text: String) = output(text + "\n")
|
private fun outputln(text: String) = output(text + "\n")
|
||||||
fun outputlni(s: Any) = outputln(indent(s.toString()))
|
private fun outputlni(s: Any) = outputln(indent(s.toString()))
|
||||||
fun outputi(s: Any) = output(indent(s.toString()))
|
private fun outputi(s: Any) = output(indent(s.toString()))
|
||||||
|
|
||||||
override fun visit(program: Program) {
|
override fun visit(program: Program) {
|
||||||
outputln("============= PROGRAM ${program.name} (FROM AST) ===============")
|
outputln("============= PROGRAM ${program.name} (FROM AST) ===============")
|
||||||
@ -76,7 +76,7 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
|
|||||||
output("\n")
|
output("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun datatypeString(dt: DataType): String {
|
private fun datatypeString(dt: DataType): String {
|
||||||
return when(dt) {
|
return when(dt) {
|
||||||
in NumericDatatypes -> dt.toString().toLowerCase()
|
in NumericDatatypes -> dt.toString().toLowerCase()
|
||||||
in StringDatatypes -> dt.toString().toLowerCase()
|
in StringDatatypes -> dt.toString().toLowerCase()
|
||||||
@ -85,21 +85,30 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
|
|||||||
DataType.ARRAY_UW -> "uword["
|
DataType.ARRAY_UW -> "uword["
|
||||||
DataType.ARRAY_W -> "word["
|
DataType.ARRAY_W -> "word["
|
||||||
DataType.ARRAY_F -> "float["
|
DataType.ARRAY_F -> "float["
|
||||||
else -> "?????"
|
DataType.STRUCT -> "" // the name of the struct is enough
|
||||||
|
else -> "?????2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(decl: VarDecl) {
|
override fun visit(structDecl: StructDecl) {
|
||||||
if(decl.autoGenerated) {
|
outputln("struct ${structDecl.name} {")
|
||||||
// skip autogenerated vardecl
|
scopelevel++
|
||||||
return
|
for(decl in structDecl.statements) {
|
||||||
|
outputi("")
|
||||||
|
decl.accept(this)
|
||||||
|
output("\n")
|
||||||
}
|
}
|
||||||
|
scopelevel--
|
||||||
|
outputlni("}")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(decl: VarDecl) {
|
||||||
when(decl.type) {
|
when(decl.type) {
|
||||||
VarDeclType.VAR -> {}
|
VarDeclType.VAR -> {}
|
||||||
VarDeclType.CONST -> output("const ")
|
VarDeclType.CONST -> output("const ")
|
||||||
VarDeclType.MEMORY -> output("&")
|
VarDeclType.MEMORY -> output("&")
|
||||||
}
|
}
|
||||||
|
output(decl.struct?.name ?: "")
|
||||||
output(datatypeString(decl.datatype))
|
output(datatypeString(decl.datatype))
|
||||||
if(decl.arraysize!=null) {
|
if(decl.arraysize!=null) {
|
||||||
decl.arraysize!!.index.accept(this)
|
decl.arraysize!!.index.accept(this)
|
||||||
@ -107,7 +116,7 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
|
|||||||
if(decl.isArray)
|
if(decl.isArray)
|
||||||
output("]")
|
output("]")
|
||||||
|
|
||||||
if(decl.zeropage)
|
if(decl.zeropage == ZeropageWish.REQUIRE_ZEROPAGE || decl.zeropage==ZeropageWish.PREFER_ZEROPAGE)
|
||||||
output(" @zp")
|
output(" @zp")
|
||||||
output(" ${decl.name} ")
|
output(" ${decl.name} ")
|
||||||
if(decl.value!=null) {
|
if(decl.value!=null) {
|
||||||
@ -123,10 +132,10 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
|
|||||||
for(param in subroutine.parameters.zip(subroutine.asmParameterRegisters)) {
|
for(param in subroutine.parameters.zip(subroutine.asmParameterRegisters)) {
|
||||||
val reg =
|
val reg =
|
||||||
when {
|
when {
|
||||||
true==param.second.stack -> "stack"
|
param.second.stack -> "stack"
|
||||||
param.second.registerOrPair!=null -> param.second.registerOrPair.toString()
|
param.second.registerOrPair!=null -> param.second.registerOrPair.toString()
|
||||||
param.second.statusflag!=null -> param.second.statusflag.toString()
|
param.second.statusflag!=null -> param.second.statusflag.toString()
|
||||||
else -> "?????"
|
else -> "?????1"
|
||||||
}
|
}
|
||||||
output("${datatypeString(param.first.type)} ${param.first.name} @$reg")
|
output("${datatypeString(param.first.type)} ${param.first.name} @$reg")
|
||||||
if(param.first!==subroutine.parameters.last())
|
if(param.first!==subroutine.parameters.last())
|
||||||
@ -167,10 +176,10 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun outputStatements(statements: List<IStatement>) {
|
private fun outputStatements(statements: List<Statement>) {
|
||||||
for(stmt in statements) {
|
for(stmt in statements) {
|
||||||
if(stmt is VarDecl && stmt.autoGenerated)
|
if(stmt is VarDecl && stmt.autogeneratedDontRemove)
|
||||||
continue // skip autogenerated decls
|
continue // skip autogenerated decls (to avoid generating a newline)
|
||||||
outputi("")
|
outputi("")
|
||||||
stmt.accept(this)
|
stmt.accept(this)
|
||||||
output("\n")
|
output("\n")
|
||||||
@ -243,36 +252,48 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
|
|||||||
output("${label.name}:")
|
output("${label.name}:")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(literalValue: LiteralValue) {
|
override fun visit(numLiteral: NumericLiteralValue) {
|
||||||
when {
|
output(numLiteral.number.toString())
|
||||||
literalValue.isNumeric -> output(literalValue.asNumericValue.toString())
|
}
|
||||||
literalValue.isString -> output("\"${escape(literalValue.strvalue!!)}\"")
|
|
||||||
literalValue.isArray -> {
|
override fun visit(string: StringLiteralValue) {
|
||||||
if(literalValue.arrayvalue!=null) {
|
output("\"${escape(string.value)}\"")
|
||||||
var counter = 0
|
}
|
||||||
output("[")
|
|
||||||
scopelevel++
|
override fun visit(array: ArrayLiteralValue) {
|
||||||
for (v in literalValue.arrayvalue) {
|
outputListMembers(array.value.asSequence(), '[', ']')
|
||||||
v.accept(this)
|
}
|
||||||
if (v !== literalValue.arrayvalue.last())
|
|
||||||
output(", ")
|
private fun outputListMembers(array: Sequence<Expression>, openchar: Char, closechar: Char) {
|
||||||
counter++
|
var counter = 0
|
||||||
if(counter > 16) {
|
output(openchar.toString())
|
||||||
outputln("")
|
scopelevel++
|
||||||
outputi("")
|
for (v in array) {
|
||||||
counter=0
|
v.accept(this)
|
||||||
}
|
if (v !== array.last())
|
||||||
}
|
output(", ")
|
||||||
scopelevel--
|
counter++
|
||||||
output("]")
|
if (counter > 16) {
|
||||||
}
|
outputln("")
|
||||||
|
outputi("")
|
||||||
|
counter = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
scopelevel--
|
||||||
|
output(closechar.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(assignment: Assignment) {
|
override fun visit(assignment: Assignment) {
|
||||||
|
if(assignment is VariableInitializationAssignment) {
|
||||||
|
val targetVar = assignment.target.identifier?.targetVarDecl(program.namespace)
|
||||||
|
if(targetVar?.struct != null) {
|
||||||
|
// skip STRUCT init assignments
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
assignment.target.accept(this)
|
assignment.target.accept(this)
|
||||||
if(assignment.aug_op!=null)
|
if (assignment.aug_op != null)
|
||||||
output(" ${assignment.aug_op} ")
|
output(" ${assignment.aug_op} ")
|
||||||
else
|
else
|
||||||
output(" = ")
|
output(" = ")
|
||||||
@ -294,13 +315,6 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
|
|||||||
|
|
||||||
override fun visit(forLoop: ForLoop) {
|
override fun visit(forLoop: ForLoop) {
|
||||||
output("for ")
|
output("for ")
|
||||||
if(forLoop.decltype!=null) {
|
|
||||||
output(datatypeString(forLoop.decltype))
|
|
||||||
if (forLoop.zeropage)
|
|
||||||
output(" @zp ")
|
|
||||||
else
|
|
||||||
output(" ")
|
|
||||||
}
|
|
||||||
if(forLoop.loopRegister!=null)
|
if(forLoop.loopRegister!=null)
|
||||||
output(forLoop.loopRegister.toString())
|
output(forLoop.loopRegister.toString())
|
||||||
else
|
else
|
||||||
@ -319,7 +333,7 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(repeatLoop: RepeatLoop) {
|
override fun visit(repeatLoop: RepeatLoop) {
|
||||||
outputln("repeat ")
|
output("repeat ")
|
||||||
repeatLoop.body.accept(this)
|
repeatLoop.body.accept(this)
|
||||||
output(" until ")
|
output(" until ")
|
||||||
repeatLoop.untilCondition.accept(this)
|
repeatLoop.untilCondition.accept(this)
|
||||||
@ -403,13 +417,14 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(whenChoice: WhenChoice) {
|
override fun visit(whenChoice: WhenChoice) {
|
||||||
if(whenChoice.values==null)
|
val choiceValues = whenChoice.values
|
||||||
|
if(choiceValues==null)
|
||||||
outputi("else -> ")
|
outputi("else -> ")
|
||||||
else {
|
else {
|
||||||
outputi("")
|
outputi("")
|
||||||
for(value in whenChoice.values) {
|
for(value in choiceValues) {
|
||||||
value.accept(this)
|
value.accept(this)
|
||||||
if(value !== whenChoice.values.last())
|
if(value !== choiceValues.last())
|
||||||
output(",")
|
output(",")
|
||||||
}
|
}
|
||||||
output(" -> ")
|
output(" -> ")
|
||||||
@ -420,7 +435,12 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
|
|||||||
whenChoice.statements.accept(this)
|
whenChoice.statements.accept(this)
|
||||||
outputln("")
|
outputln("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun visit(structLv: StructLiteralValue) {
|
||||||
|
outputListMembers(structLv.values.asSequence(), '{', '}')
|
||||||
|
}
|
||||||
|
|
||||||
override fun visit(nopStatement: NopStatement) {
|
override fun visit(nopStatement: NopStatement) {
|
||||||
output("; NOP")
|
output("; NOP @ ${nopStatement.position} $nopStatement")
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,29 +1,182 @@
|
|||||||
package prog8.ast
|
package prog8.ast
|
||||||
|
|
||||||
import prog8.ast.base.FatalAstException
|
import prog8.ast.base.*
|
||||||
import prog8.ast.base.NameError
|
import prog8.ast.expressions.Expression
|
||||||
import prog8.ast.base.ParentSentinel
|
import prog8.ast.expressions.IdentifierReference
|
||||||
import prog8.ast.base.Position
|
import prog8.ast.statements.*
|
||||||
import prog8.ast.statements.Block
|
|
||||||
import prog8.ast.statements.Label
|
|
||||||
import prog8.ast.statements.Subroutine
|
|
||||||
import prog8.ast.statements.VarDecl
|
|
||||||
import prog8.compiler.HeapValues
|
|
||||||
import prog8.functions.BuiltinFunctions
|
import prog8.functions.BuiltinFunctions
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
|
||||||
|
interface Node {
|
||||||
|
val position: Position
|
||||||
|
var parent: Node // will be linked correctly later (late init)
|
||||||
|
fun linkParents(parent: Node)
|
||||||
|
|
||||||
|
fun definingModule(): Module {
|
||||||
|
if(this is Module)
|
||||||
|
return this
|
||||||
|
return findParentNode<Module>(this)!!
|
||||||
|
}
|
||||||
|
|
||||||
|
fun definingSubroutine(): Subroutine? = findParentNode<Subroutine>(this)
|
||||||
|
|
||||||
|
fun definingScope(): INameScope {
|
||||||
|
val scope = findParentNode<INameScope>(this)
|
||||||
|
if(scope!=null) {
|
||||||
|
return scope
|
||||||
|
}
|
||||||
|
if(this is Label && this.name.startsWith("builtin::")) {
|
||||||
|
return BuiltinFunctionScopePlaceholder
|
||||||
|
}
|
||||||
|
if(this is GlobalNamespace)
|
||||||
|
return this
|
||||||
|
throw FatalAstException("scope missing from $this")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IFunctionCall {
|
||||||
|
var target: IdentifierReference
|
||||||
|
var arglist: MutableList<Expression>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface INameScope {
|
||||||
|
val name: String
|
||||||
|
val position: Position
|
||||||
|
val statements: MutableList<Statement>
|
||||||
|
val parent: Node
|
||||||
|
|
||||||
|
fun linkParents(parent: Node)
|
||||||
|
|
||||||
|
fun subScopes(): Map<String, INameScope> {
|
||||||
|
val subscopes = mutableMapOf<String, INameScope>()
|
||||||
|
for(stmt in statements) {
|
||||||
|
when(stmt) {
|
||||||
|
// NOTE: if other nodes are introduced that are a scope, or contain subscopes, they must be added here!
|
||||||
|
is ForLoop -> subscopes[stmt.body.name] = stmt.body
|
||||||
|
is RepeatLoop -> subscopes[stmt.body.name] = stmt.body
|
||||||
|
is WhileLoop -> subscopes[stmt.body.name] = stmt.body
|
||||||
|
is BranchStatement -> {
|
||||||
|
subscopes[stmt.truepart.name] = stmt.truepart
|
||||||
|
if(stmt.elsepart.containsCodeOrVars())
|
||||||
|
subscopes[stmt.elsepart.name] = stmt.elsepart
|
||||||
|
}
|
||||||
|
is IfStatement -> {
|
||||||
|
subscopes[stmt.truepart.name] = stmt.truepart
|
||||||
|
if(stmt.elsepart.containsCodeOrVars())
|
||||||
|
subscopes[stmt.elsepart.name] = stmt.elsepart
|
||||||
|
}
|
||||||
|
is WhenStatement -> {
|
||||||
|
stmt.choices.forEach { subscopes[it.statements.name] = it.statements }
|
||||||
|
}
|
||||||
|
is INameScope -> subscopes[stmt.name] = stmt
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return subscopes
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLabelOrVariable(name: String): Statement? {
|
||||||
|
// this is called A LOT and could perhaps be optimized a bit more,
|
||||||
|
// but adding a memoization cache didn't make much of a practical runtime difference
|
||||||
|
for (stmt in statements) {
|
||||||
|
if (stmt is VarDecl && stmt.name==name) return stmt
|
||||||
|
if (stmt is Label && stmt.name==name) return stmt
|
||||||
|
if (stmt is AnonymousScope) {
|
||||||
|
val sub = stmt.getLabelOrVariable(name)
|
||||||
|
if(sub!=null)
|
||||||
|
return sub
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun allDefinedSymbols(): List<Pair<String, Statement>> {
|
||||||
|
return statements.mapNotNull {
|
||||||
|
when (it) {
|
||||||
|
is Label -> it.name to it
|
||||||
|
is VarDecl -> it.name to it
|
||||||
|
is Subroutine -> it.name to it
|
||||||
|
is Block -> it.name to it
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun lookup(scopedName: List<String>, localContext: Node) : Statement? {
|
||||||
|
if(scopedName.size>1) {
|
||||||
|
// a scoped name can a) refer to a member of a struct, or b) refer to a name in another module.
|
||||||
|
// try the struct first.
|
||||||
|
val thing = lookup(scopedName.dropLast(1), localContext) as? VarDecl
|
||||||
|
val struct = thing?.struct
|
||||||
|
if (struct != null) {
|
||||||
|
if(struct.statements.any { (it as VarDecl).name == scopedName.last()}) {
|
||||||
|
// return ref to the mangled name variable
|
||||||
|
val mangled = mangledStructMemberName(thing.name, scopedName.last())
|
||||||
|
return thing.definingScope().getLabelOrVariable(mangled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// it's a qualified name, look it up from the root of the module's namespace (consider all modules in the program)
|
||||||
|
for(module in localContext.definingModule().program.modules) {
|
||||||
|
var scope: INameScope? = module
|
||||||
|
for(name in scopedName.dropLast(1)) {
|
||||||
|
scope = scope?.subScopes()?.get(name)
|
||||||
|
if(scope==null)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if(scope!=null) {
|
||||||
|
val result = scope.getLabelOrVariable(scopedName.last())
|
||||||
|
if(result!=null)
|
||||||
|
return result
|
||||||
|
return scope.subScopes()[scopedName.last()] as Statement?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
// unqualified name, find the scope the localContext is in, look in that first
|
||||||
|
var statementScope = localContext
|
||||||
|
while(statementScope !is ParentSentinel) {
|
||||||
|
val localScope = statementScope.definingScope()
|
||||||
|
val result = localScope.getLabelOrVariable(scopedName[0])
|
||||||
|
if (result != null)
|
||||||
|
return result
|
||||||
|
val subscope = localScope.subScopes()[scopedName[0]] as Statement?
|
||||||
|
if (subscope != null)
|
||||||
|
return subscope
|
||||||
|
// not found in this scope, look one higher up
|
||||||
|
statementScope = statementScope.parent
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun containsCodeOrVars() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm"}
|
||||||
|
fun containsNoCodeNorVars() = !containsCodeOrVars()
|
||||||
|
|
||||||
|
fun remove(stmt: Statement) {
|
||||||
|
if(!statements.remove(stmt))
|
||||||
|
throw FatalAstException("stmt to remove wasn't found in scope")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IAssignable {
|
||||||
|
// just a tag for now
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*********** Everything starts from here, the Program; zero or more modules *************/
|
/*********** Everything starts from here, the Program; zero or more modules *************/
|
||||||
|
|
||||||
class Program(val name: String, val modules: MutableList<Module>) {
|
class Program(val name: String, val modules: MutableList<Module>) {
|
||||||
val namespace = GlobalNamespace(modules)
|
val namespace = GlobalNamespace(modules)
|
||||||
val heap = HeapValues()
|
|
||||||
|
|
||||||
val loadAddress: Int
|
val definedLoadAddress: Int
|
||||||
get() = modules.first().loadAddress
|
get() = modules.first().loadAddress
|
||||||
|
|
||||||
|
var actualLoadAddress: Int = 0
|
||||||
|
|
||||||
fun entrypoint(): Subroutine? {
|
fun entrypoint(): Subroutine? {
|
||||||
val mainBlocks = modules.flatMap { it.statements }.filter { b -> b is Block && b.name=="main" }.map { it as Block }
|
val mainBlocks = allBlocks().filter { it.name=="main" }
|
||||||
if(mainBlocks.size > 1)
|
if(mainBlocks.size > 1)
|
||||||
throw FatalAstException("more than one 'main' block")
|
throw FatalAstException("more than one 'main' block")
|
||||||
return if(mainBlocks.isEmpty()) {
|
return if(mainBlocks.isEmpty()) {
|
||||||
@ -32,10 +185,12 @@ class Program(val name: String, val modules: MutableList<Module>) {
|
|||||||
mainBlocks[0].subScopes()["start"] as Subroutine?
|
mainBlocks[0].subScopes()["start"] as Subroutine?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun allBlocks(): List<Block> = modules.flatMap { it.statements.filterIsInstance<Block>() }
|
||||||
}
|
}
|
||||||
|
|
||||||
class Module(override val name: String,
|
class Module(override val name: String,
|
||||||
override var statements: MutableList<IStatement>,
|
override var statements: MutableList<Statement>,
|
||||||
override val position: Position,
|
override val position: Position,
|
||||||
val isLibraryModule: Boolean,
|
val isLibraryModule: Boolean,
|
||||||
val source: Path) : Node, INameScope {
|
val source: Path) : Node, INameScope {
|
||||||
@ -59,14 +214,14 @@ class Module(override val name: String,
|
|||||||
class GlobalNamespace(val modules: List<Module>): Node, INameScope {
|
class GlobalNamespace(val modules: List<Module>): Node, INameScope {
|
||||||
override val name = "<<<global>>>"
|
override val name = "<<<global>>>"
|
||||||
override val position = Position("<<<global>>>", 0, 0, 0)
|
override val position = Position("<<<global>>>", 0, 0, 0)
|
||||||
override val statements = mutableListOf<IStatement>()
|
override val statements = mutableListOf<Statement>()
|
||||||
override var parent: Node = ParentSentinel
|
override var parent: Node = ParentSentinel
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
modules.forEach { it.linkParents(this) }
|
modules.forEach { it.linkParents(this) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun lookup(scopedName: List<String>, localContext: Node): IStatement? {
|
override fun lookup(scopedName: List<String>, localContext: Node): Statement? {
|
||||||
if (scopedName.size == 1 && scopedName[0] in BuiltinFunctions) {
|
if (scopedName.size == 1 && scopedName[0] in BuiltinFunctions) {
|
||||||
// builtin functions always exist, return a dummy localContext for them
|
// builtin functions always exist, return a dummy localContext for them
|
||||||
val builtinPlaceholder = Label("builtin::${scopedName.last()}", localContext.position)
|
val builtinPlaceholder = Label("builtin::${scopedName.last()}", localContext.position)
|
||||||
@ -74,6 +229,20 @@ class GlobalNamespace(val modules: List<Module>): Node, INameScope {
|
|||||||
return builtinPlaceholder
|
return builtinPlaceholder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(scopedName.size>1) {
|
||||||
|
// a scoped name can a) refer to a member of a struct, or b) refer to a name in another module.
|
||||||
|
// try the struct first.
|
||||||
|
val thing = lookup(scopedName.dropLast(1), localContext) as? VarDecl
|
||||||
|
val struct = thing?.struct
|
||||||
|
if (struct != null) {
|
||||||
|
if(struct.statements.any { (it as VarDecl).name == scopedName.last()}) {
|
||||||
|
// return ref to the mangled name variable
|
||||||
|
val mangled = mangledStructMemberName(thing.name, scopedName.last())
|
||||||
|
return thing.definingScope().getLabelOrVariable(mangled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// lookup something from the module.
|
||||||
val stmt = localContext.definingModule().lookup(scopedName, localContext)
|
val stmt = localContext.definingModule().lookup(scopedName, localContext)
|
||||||
return when (stmt) {
|
return when (stmt) {
|
||||||
is Label, is VarDecl, is Block, is Subroutine -> stmt
|
is Label, is VarDecl, is Block, is Subroutine -> stmt
|
||||||
@ -86,7 +255,11 @@ class GlobalNamespace(val modules: List<Module>): Node, INameScope {
|
|||||||
object BuiltinFunctionScopePlaceholder : INameScope {
|
object BuiltinFunctionScopePlaceholder : INameScope {
|
||||||
override val name = "<<builtin-functions-scope-placeholder>>"
|
override val name = "<<builtin-functions-scope-placeholder>>"
|
||||||
override val position = Position("<<placeholder>>", 0, 0, 0)
|
override val position = Position("<<placeholder>>", 0, 0, 0)
|
||||||
override var statements = mutableListOf<IStatement>()
|
override var statements = mutableListOf<Statement>()
|
||||||
override var parent: Node = ParentSentinel
|
override var parent: Node = ParentSentinel
|
||||||
override fun linkParents(parent: Node) {}
|
override fun linkParents(parent: Node) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// prefix for struct member variables
|
||||||
|
internal fun mangledStructMemberName(varName: String, memberName: String) = "prog8struct_${varName}_$memberName"
|
||||||
|
@ -1,202 +0,0 @@
|
|||||||
package prog8.ast
|
|
||||||
|
|
||||||
import prog8.ast.base.*
|
|
||||||
import prog8.ast.expressions.*
|
|
||||||
import prog8.ast.processing.IAstModifyingVisitor
|
|
||||||
import prog8.ast.processing.IAstVisitor
|
|
||||||
import prog8.ast.statements.*
|
|
||||||
|
|
||||||
interface Node {
|
|
||||||
val position: Position
|
|
||||||
var parent: Node // will be linked correctly later (late init)
|
|
||||||
fun linkParents(parent: Node)
|
|
||||||
|
|
||||||
fun definingModule(): Module {
|
|
||||||
if(this is Module)
|
|
||||||
return this
|
|
||||||
return findParentNode<Module>(this)!!
|
|
||||||
}
|
|
||||||
|
|
||||||
fun definingSubroutine(): Subroutine? = findParentNode<Subroutine>(this)
|
|
||||||
|
|
||||||
fun definingScope(): INameScope {
|
|
||||||
val scope = findParentNode<INameScope>(this)
|
|
||||||
if(scope!=null) {
|
|
||||||
return scope
|
|
||||||
}
|
|
||||||
if(this is Label && this.name.startsWith("builtin::")) {
|
|
||||||
return BuiltinFunctionScopePlaceholder
|
|
||||||
}
|
|
||||||
if(this is GlobalNamespace)
|
|
||||||
return this
|
|
||||||
throw FatalAstException("scope missing from $this")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IStatement : Node {
|
|
||||||
fun accept(visitor: IAstModifyingVisitor) : IStatement
|
|
||||||
fun accept(visitor: IAstVisitor)
|
|
||||||
fun makeScopedName(name: String): String {
|
|
||||||
// easy way out is to always return the full scoped name.
|
|
||||||
// it would be nicer to find only the minimal prefixed scoped name, but that's too much hassle for now.
|
|
||||||
// and like this, we can cache the name even,
|
|
||||||
// like in a lazy property on the statement object itself (label, subroutine, vardecl)
|
|
||||||
val scope = mutableListOf<String>()
|
|
||||||
var statementScope = this.parent
|
|
||||||
while(statementScope !is ParentSentinel && statementScope !is Module) {
|
|
||||||
if(statementScope is INameScope) {
|
|
||||||
scope.add(0, statementScope.name)
|
|
||||||
}
|
|
||||||
statementScope = statementScope.parent
|
|
||||||
}
|
|
||||||
if(name.isNotEmpty())
|
|
||||||
scope.add(name)
|
|
||||||
return scope.joinToString(".")
|
|
||||||
}
|
|
||||||
|
|
||||||
val expensiveToInline: Boolean
|
|
||||||
|
|
||||||
fun definingBlock(): Block {
|
|
||||||
if(this is Block)
|
|
||||||
return this
|
|
||||||
return findParentNode<Block>(this)!!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IFunctionCall {
|
|
||||||
var target: IdentifierReference
|
|
||||||
var arglist: MutableList<IExpression>
|
|
||||||
}
|
|
||||||
|
|
||||||
interface INameScope {
|
|
||||||
val name: String
|
|
||||||
val position: Position
|
|
||||||
val statements: MutableList<IStatement>
|
|
||||||
val parent: Node
|
|
||||||
|
|
||||||
fun linkParents(parent: Node)
|
|
||||||
|
|
||||||
fun subScopes(): Map<String, INameScope> {
|
|
||||||
val subscopes = mutableMapOf<String, INameScope>()
|
|
||||||
for(stmt in statements) {
|
|
||||||
when(stmt) {
|
|
||||||
// NOTE: if other nodes are introduced that are a scope of contain subscopes, they must be added here!
|
|
||||||
is INameScope -> subscopes[stmt.name] = stmt
|
|
||||||
is ForLoop -> subscopes[stmt.body.name] = stmt.body
|
|
||||||
is RepeatLoop -> subscopes[stmt.body.name] = stmt.body
|
|
||||||
is WhileLoop -> subscopes[stmt.body.name] = stmt.body
|
|
||||||
is BranchStatement -> {
|
|
||||||
subscopes[stmt.truepart.name] = stmt.truepart
|
|
||||||
if(stmt.elsepart.containsCodeOrVars())
|
|
||||||
subscopes[stmt.elsepart.name] = stmt.elsepart
|
|
||||||
}
|
|
||||||
is IfStatement -> {
|
|
||||||
subscopes[stmt.truepart.name] = stmt.truepart
|
|
||||||
if(stmt.elsepart.containsCodeOrVars())
|
|
||||||
subscopes[stmt.elsepart.name] = stmt.elsepart
|
|
||||||
}
|
|
||||||
is WhenStatement -> {
|
|
||||||
stmt.choices.forEach { subscopes[it.statements.name] = it.statements }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return subscopes
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getLabelOrVariable(name: String): IStatement? {
|
|
||||||
// this is called A LOT and could perhaps be optimized a bit more,
|
|
||||||
// but adding a memoization cache didn't make much of a practical runtime difference
|
|
||||||
for (stmt in statements) {
|
|
||||||
if (stmt is VarDecl && stmt.name==name) return stmt
|
|
||||||
if (stmt is Label && stmt.name==name) return stmt
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun allDefinedSymbols(): List<Pair<String, IStatement>> {
|
|
||||||
return statements.mapNotNull {
|
|
||||||
when (it) {
|
|
||||||
is Label -> it.name to it
|
|
||||||
is VarDecl -> it.name to it
|
|
||||||
is Subroutine -> it.name to it
|
|
||||||
is Block -> it.name to it
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun lookup(scopedName: List<String>, localContext: Node) : IStatement? {
|
|
||||||
if(scopedName.size>1) {
|
|
||||||
// it's a qualified name, look it up from the root of the module's namespace (consider all modules in the program)
|
|
||||||
for(module in localContext.definingModule().program.modules) {
|
|
||||||
var scope: INameScope? = module
|
|
||||||
for(name in scopedName.dropLast(1)) {
|
|
||||||
scope = scope?.subScopes()?.get(name)
|
|
||||||
if(scope==null)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if(scope!=null) {
|
|
||||||
val result = scope.getLabelOrVariable(scopedName.last())
|
|
||||||
if(result!=null)
|
|
||||||
return result
|
|
||||||
return scope.subScopes()[scopedName.last()] as IStatement?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
} else {
|
|
||||||
// unqualified name, find the scope the localContext is in, look in that first
|
|
||||||
var statementScope = localContext
|
|
||||||
while(statementScope !is ParentSentinel) {
|
|
||||||
val localScope = statementScope.definingScope()
|
|
||||||
val result = localScope.getLabelOrVariable(scopedName[0])
|
|
||||||
if (result != null)
|
|
||||||
return result
|
|
||||||
val subscope = localScope.subScopes()[scopedName[0]] as IStatement?
|
|
||||||
if (subscope != null)
|
|
||||||
return subscope
|
|
||||||
// not found in this scope, look one higher up
|
|
||||||
statementScope = statementScope.parent
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun containsCodeOrVars() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm"}
|
|
||||||
fun containsNoCodeNorVars() = !containsCodeOrVars()
|
|
||||||
|
|
||||||
fun remove(stmt: IStatement) {
|
|
||||||
if(!statements.remove(stmt))
|
|
||||||
throw FatalAstException("stmt to remove wasn't found in scope")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IExpression: Node {
|
|
||||||
fun constValue(program: Program): LiteralValue?
|
|
||||||
fun accept(visitor: IAstModifyingVisitor): IExpression
|
|
||||||
fun accept(visitor: IAstVisitor)
|
|
||||||
fun referencesIdentifiers(vararg name: String): Boolean
|
|
||||||
fun inferType(program: Program): DataType?
|
|
||||||
|
|
||||||
infix fun isSameAs(other: IExpression): Boolean {
|
|
||||||
if(this===other)
|
|
||||||
return true
|
|
||||||
when(this) {
|
|
||||||
is RegisterExpr ->
|
|
||||||
return (other is RegisterExpr && other.register==register)
|
|
||||||
is IdentifierReference ->
|
|
||||||
return (other is IdentifierReference && other.nameInSource==nameInSource)
|
|
||||||
is PrefixExpression ->
|
|
||||||
return (other is PrefixExpression && other.operator==operator && other.expression isSameAs expression)
|
|
||||||
is BinaryExpression ->
|
|
||||||
return (other is BinaryExpression && other.operator==operator
|
|
||||||
&& other.left isSameAs left
|
|
||||||
&& other.right isSameAs right)
|
|
||||||
is ArrayIndexedExpression -> {
|
|
||||||
return (other is ArrayIndexedExpression && other.identifier.nameInSource == identifier.nameInSource
|
|
||||||
&& other.arrayspec.index isSameAs arrayspec.index)
|
|
||||||
}
|
|
||||||
is LiteralValue -> return (other is LiteralValue && other==this)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,16 +3,16 @@ package prog8.ast.antlr
|
|||||||
import org.antlr.v4.runtime.IntStream
|
import org.antlr.v4.runtime.IntStream
|
||||||
import org.antlr.v4.runtime.ParserRuleContext
|
import org.antlr.v4.runtime.ParserRuleContext
|
||||||
import org.antlr.v4.runtime.tree.TerminalNode
|
import org.antlr.v4.runtime.tree.TerminalNode
|
||||||
import prog8.ast.*
|
import prog8.ast.Module
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import java.io.CharConversionException
|
|
||||||
import java.io.File
|
|
||||||
import java.nio.file.Path
|
|
||||||
import prog8.compiler.target.c64.Petscii
|
import prog8.compiler.target.c64.Petscii
|
||||||
import prog8.parser.CustomLexer
|
import prog8.parser.CustomLexer
|
||||||
import prog8.parser.prog8Parser
|
import prog8.parser.prog8Parser
|
||||||
|
import java.io.CharConversionException
|
||||||
|
import java.io.File
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
|
||||||
/***************** Antlr Extension methods to create AST ****************/
|
/***************** Antlr Extension methods to create AST ****************/
|
||||||
@ -39,7 +39,7 @@ private fun ParserRuleContext.toPosition() : Position {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.ModulestatementContext.toAst(isInLibrary: Boolean) : IStatement {
|
private fun prog8Parser.ModulestatementContext.toAst(isInLibrary: Boolean) : Statement {
|
||||||
val directive = directive()?.toAst()
|
val directive = directive()?.toAst()
|
||||||
if(directive!=null) return directive
|
if(directive!=null) return directive
|
||||||
|
|
||||||
@ -50,66 +50,96 @@ private fun prog8Parser.ModulestatementContext.toAst(isInLibrary: Boolean) : ISt
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.BlockContext.toAst(isInLibrary: Boolean) : IStatement =
|
private fun prog8Parser.BlockContext.toAst(isInLibrary: Boolean) : Statement =
|
||||||
Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), statement_block().toAst(), isInLibrary, toPosition())
|
Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), statement_block().toAst(), isInLibrary, toPosition())
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.Statement_blockContext.toAst(): MutableList<IStatement> =
|
private fun prog8Parser.Statement_blockContext.toAst(): MutableList<Statement> =
|
||||||
statement().asSequence().map { it.toAst() }.toMutableList()
|
statement().asSequence().map { it.toAst() }.toMutableList()
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.StatementContext.toAst() : IStatement {
|
private fun prog8Parser.StatementContext.toAst() : Statement {
|
||||||
vardecl()?.let {
|
vardecl()?.let { return it.toAst() }
|
||||||
return VarDecl(VarDeclType.VAR,
|
|
||||||
it.datatype().toAst(),
|
|
||||||
it.ZEROPAGE() != null,
|
|
||||||
it.arrayindex()?.toAst(),
|
|
||||||
it.identifier().text,
|
|
||||||
null,
|
|
||||||
it.ARRAYSIG() != null || it.arrayindex() != null,
|
|
||||||
false,
|
|
||||||
it.toPosition())
|
|
||||||
}
|
|
||||||
|
|
||||||
varinitializer()?.let {
|
varinitializer()?.let {
|
||||||
val vd = it.vardecl()
|
val vd = it.vardecl()
|
||||||
return VarDecl(VarDeclType.VAR,
|
return VarDecl(
|
||||||
vd.datatype().toAst(),
|
VarDeclType.VAR,
|
||||||
vd.ZEROPAGE() != null,
|
vd.datatype()?.toAst() ?: DataType.STRUCT,
|
||||||
|
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||||
vd.arrayindex()?.toAst(),
|
vd.arrayindex()?.toAst(),
|
||||||
vd.identifier().text,
|
vd.varname.text,
|
||||||
|
null,
|
||||||
it.expression().toAst(),
|
it.expression().toAst(),
|
||||||
vd.ARRAYSIG() != null || vd.arrayindex() != null,
|
vd.ARRAYSIG() != null || vd.arrayindex() != null,
|
||||||
false,
|
false,
|
||||||
it.toPosition())
|
it.toPosition()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
structvarinitializer()?.let {
|
||||||
|
val vd = it.structvardecl()
|
||||||
|
return VarDecl(
|
||||||
|
VarDeclType.VAR,
|
||||||
|
DataType.STRUCT,
|
||||||
|
ZeropageWish.NOT_IN_ZEROPAGE,
|
||||||
|
null,
|
||||||
|
vd.varname.text,
|
||||||
|
vd.structname.text,
|
||||||
|
it.expression().toAst(),
|
||||||
|
isArray = false,
|
||||||
|
autogeneratedDontRemove = false,
|
||||||
|
position = it.toPosition()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
structvardecl()?.let {
|
||||||
|
return VarDecl(
|
||||||
|
VarDeclType.VAR,
|
||||||
|
DataType.STRUCT,
|
||||||
|
ZeropageWish.NOT_IN_ZEROPAGE,
|
||||||
|
null,
|
||||||
|
it.varname.text,
|
||||||
|
it.structname.text,
|
||||||
|
null,
|
||||||
|
isArray = false,
|
||||||
|
autogeneratedDontRemove = false,
|
||||||
|
position = it.toPosition()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
constdecl()?.let {
|
constdecl()?.let {
|
||||||
val cvarinit = it.varinitializer()
|
val cvarinit = it.varinitializer()
|
||||||
val vd = cvarinit.vardecl()
|
val vd = cvarinit.vardecl()
|
||||||
return VarDecl(VarDeclType.CONST,
|
return VarDecl(
|
||||||
vd.datatype().toAst(),
|
VarDeclType.CONST,
|
||||||
vd.ZEROPAGE() != null,
|
vd.datatype()?.toAst() ?: DataType.STRUCT,
|
||||||
|
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||||
vd.arrayindex()?.toAst(),
|
vd.arrayindex()?.toAst(),
|
||||||
vd.identifier().text,
|
vd.varname.text,
|
||||||
|
null,
|
||||||
cvarinit.expression().toAst(),
|
cvarinit.expression().toAst(),
|
||||||
vd.ARRAYSIG() != null || vd.arrayindex() != null,
|
vd.ARRAYSIG() != null || vd.arrayindex() != null,
|
||||||
false,
|
false,
|
||||||
cvarinit.toPosition())
|
cvarinit.toPosition()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
memoryvardecl()?.let {
|
memoryvardecl()?.let {
|
||||||
val mvarinit = it.varinitializer()
|
val mvarinit = it.varinitializer()
|
||||||
val vd = mvarinit.vardecl()
|
val vd = mvarinit.vardecl()
|
||||||
return VarDecl(VarDeclType.MEMORY,
|
return VarDecl(
|
||||||
vd.datatype().toAst(),
|
VarDeclType.MEMORY,
|
||||||
vd.ZEROPAGE() != null,
|
vd.datatype()?.toAst() ?: DataType.STRUCT,
|
||||||
|
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||||
vd.arrayindex()?.toAst(),
|
vd.arrayindex()?.toAst(),
|
||||||
vd.identifier().text,
|
vd.varname.text,
|
||||||
|
null,
|
||||||
mvarinit.expression().toAst(),
|
mvarinit.expression().toAst(),
|
||||||
vd.ARRAYSIG() != null || vd.arrayindex() != null,
|
vd.ARRAYSIG() != null || vd.arrayindex() != null,
|
||||||
false,
|
false,
|
||||||
mvarinit.toPosition())
|
mvarinit.toPosition()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
assignment()?.let {
|
assignment()?.let {
|
||||||
@ -175,10 +205,16 @@ private fun prog8Parser.StatementContext.toAst() : IStatement {
|
|||||||
val whenstmt = whenstmt()?.toAst()
|
val whenstmt = whenstmt()?.toAst()
|
||||||
if(whenstmt!=null) return whenstmt
|
if(whenstmt!=null) return whenstmt
|
||||||
|
|
||||||
|
structdecl()?.let {
|
||||||
|
return StructDecl(it.identifier().text,
|
||||||
|
it.vardecl().map { vd->vd.toAst() }.toMutableList(),
|
||||||
|
toPosition())
|
||||||
|
}
|
||||||
|
|
||||||
throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text")
|
throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun prog8Parser.AsmsubroutineContext.toAst(): IStatement {
|
private fun prog8Parser.AsmsubroutineContext.toAst(): Statement {
|
||||||
val name = identifier().text
|
val name = identifier().text
|
||||||
val address = asmsub_address()?.address?.toAst()?.number?.toInt()
|
val address = asmsub_address()?.address?.toAst()?.number?.toInt()
|
||||||
val params = asmsub_params()?.toAst() ?: emptyList()
|
val params = asmsub_params()?.toAst() ?: emptyList()
|
||||||
@ -215,15 +251,19 @@ private fun prog8Parser.Asmsub_returnsContext.toAst(): List<AsmSubroutineReturn>
|
|||||||
|
|
||||||
private fun prog8Parser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter>
|
private fun prog8Parser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter>
|
||||||
= asmsub_param().map {
|
= asmsub_param().map {
|
||||||
AsmSubroutineParameter(it.vardecl().identifier().text, it.vardecl().datatype().toAst(),
|
val vardecl = it.vardecl()
|
||||||
it.registerorpair()?.toAst(), it.statusregister()?.toAst(), !it.stack?.text.isNullOrEmpty(), toPosition())
|
val datatype = vardecl.datatype()?.toAst() ?: DataType.STRUCT
|
||||||
|
AsmSubroutineParameter(vardecl.varname.text, datatype,
|
||||||
|
it.registerorpair()?.toAst(),
|
||||||
|
it.statusregister()?.toAst(),
|
||||||
|
!it.stack?.text.isNullOrEmpty(), toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.StatusregisterContext.toAst() = Statusflag.valueOf(text)
|
private fun prog8Parser.StatusregisterContext.toAst() = Statusflag.valueOf(text)
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.Functioncall_stmtContext.toAst(): IStatement {
|
private fun prog8Parser.Functioncall_stmtContext.toAst(): Statement {
|
||||||
val location = scoped_identifier().toAst()
|
val location = scoped_identifier().toAst()
|
||||||
return if(expression_list() == null)
|
return if(expression_list() == null)
|
||||||
FunctionCallStatement(location, mutableListOf(), toPosition())
|
FunctionCallStatement(location, mutableListOf(), toPosition())
|
||||||
@ -256,7 +296,7 @@ private fun prog8Parser.UnconditionaljumpContext.toAst(): Jump {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.LabeldefContext.toAst(): IStatement =
|
private fun prog8Parser.LabeldefContext.toAst(): Statement =
|
||||||
Label(children[0].text, toPosition())
|
Label(children[0].text, toPosition())
|
||||||
|
|
||||||
|
|
||||||
@ -281,7 +321,8 @@ private fun prog8Parser.Sub_return_partContext.toAst(): List<DataType> {
|
|||||||
|
|
||||||
private fun prog8Parser.Sub_paramsContext.toAst(): List<SubroutineParameter> =
|
private fun prog8Parser.Sub_paramsContext.toAst(): List<SubroutineParameter> =
|
||||||
vardecl().map {
|
vardecl().map {
|
||||||
SubroutineParameter(it.identifier().text, it.datatype().toAst(), it.toPosition())
|
val datatype = it.datatype()?.toAst() ?: DataType.STRUCT
|
||||||
|
SubroutineParameter(it.varname.text, datatype, it.toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -368,39 +409,43 @@ private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.ExpressionContext.toAst() : IExpression {
|
private fun prog8Parser.ExpressionContext.toAst() : Expression {
|
||||||
|
|
||||||
val litval = literalvalue()
|
val litval = literalvalue()
|
||||||
if(litval!=null) {
|
if(litval!=null) {
|
||||||
val booleanlit = litval.booleanliteral()?.toAst()
|
val booleanlit = litval.booleanliteral()?.toAst()
|
||||||
return if(booleanlit!=null) {
|
return if(booleanlit!=null) {
|
||||||
LiteralValue.fromBoolean(booleanlit, litval.toPosition())
|
NumericLiteralValue.fromBoolean(booleanlit, litval.toPosition())
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
val intLit = litval.integerliteral()?.toAst()
|
val intLit = litval.integerliteral()?.toAst()
|
||||||
when {
|
when {
|
||||||
intLit!=null -> when(intLit.datatype) {
|
intLit!=null -> when(intLit.datatype) {
|
||||||
DataType.UBYTE -> LiteralValue(DataType.UBYTE, bytevalue = intLit.number.toShort(), position = litval.toPosition())
|
DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, intLit.number.toShort(), litval.toPosition())
|
||||||
DataType.BYTE -> LiteralValue(DataType.BYTE, bytevalue = intLit.number.toShort(), position = litval.toPosition())
|
DataType.BYTE -> NumericLiteralValue(DataType.BYTE, intLit.number.toShort(), litval.toPosition())
|
||||||
DataType.UWORD -> LiteralValue(DataType.UWORD, wordvalue = intLit.number.toInt(), position = litval.toPosition())
|
DataType.UWORD -> NumericLiteralValue(DataType.UWORD, intLit.number.toInt(), litval.toPosition())
|
||||||
DataType.WORD -> LiteralValue(DataType.WORD, wordvalue = intLit.number.toInt(), position = litval.toPosition())
|
DataType.WORD -> NumericLiteralValue(DataType.WORD, intLit.number.toInt(), litval.toPosition())
|
||||||
DataType.FLOAT -> LiteralValue(DataType.FLOAT, floatvalue = intLit.number.toDouble(), position = litval.toPosition())
|
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, intLit.number.toDouble(), litval.toPosition())
|
||||||
else -> throw FatalAstException("invalid datatype for numeric literal")
|
else -> throw FatalAstException("invalid datatype for numeric literal")
|
||||||
}
|
}
|
||||||
litval.floatliteral()!=null -> LiteralValue(DataType.FLOAT, floatvalue = litval.floatliteral().toAst(), position = litval.toPosition())
|
litval.floatliteral()!=null -> NumericLiteralValue(DataType.FLOAT, litval.floatliteral().toAst(), litval.toPosition())
|
||||||
litval.stringliteral()!=null -> LiteralValue(DataType.STR, strvalue = unescape(litval.stringliteral().text, litval.toPosition()), position = litval.toPosition())
|
litval.stringliteral()!=null -> StringLiteralValue(DataType.STR, unescape(litval.stringliteral().text, litval.toPosition()), litval.toPosition())
|
||||||
litval.charliteral()!=null -> {
|
litval.charliteral()!=null -> {
|
||||||
try {
|
try {
|
||||||
LiteralValue(DataType.UBYTE, bytevalue = Petscii.encodePetscii(unescape(litval.charliteral().text, litval.toPosition()), true)[0], position = litval.toPosition())
|
NumericLiteralValue(DataType.UBYTE, Petscii.encodePetscii(unescape(litval.charliteral().text, litval.toPosition()), true)[0], litval.toPosition())
|
||||||
} catch (ce: CharConversionException) {
|
} catch (ce: CharConversionException) {
|
||||||
throw SyntaxError(ce.message ?: ce.toString(), litval.toPosition())
|
throw SyntaxError(ce.message ?: ce.toString(), litval.toPosition())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
litval.arrayliteral()!=null -> {
|
litval.arrayliteral()!=null -> {
|
||||||
val array = litval.arrayliteral()?.toAst()
|
val array = litval.arrayliteral().toAst()
|
||||||
// the actual type of the arraysize can not yet be determined here (missing namespace & heap)
|
// the actual type of the arraysize can not yet be determined here (missing namespace & heap)
|
||||||
// the ConstantFolder takes care of that and converts the type if needed.
|
// the ConstantFold takes care of that and converts the type if needed.
|
||||||
LiteralValue(DataType.ARRAY_UB, arrayvalue = array, position = litval.toPosition())
|
ArrayLiteralValue(DataType.ARRAY_UB, array, position = litval.toPosition())
|
||||||
|
}
|
||||||
|
litval.structliteral()!=null -> {
|
||||||
|
val values = litval.structliteral().expression().map { it.toAst() }
|
||||||
|
StructLiteralValue(values, litval.toPosition())
|
||||||
}
|
}
|
||||||
else -> throw FatalAstException("invalid parsed literal")
|
else -> throw FatalAstException("invalid parsed literal")
|
||||||
}
|
}
|
||||||
@ -423,7 +468,7 @@ private fun prog8Parser.ExpressionContext.toAst() : IExpression {
|
|||||||
if(funcall!=null) return funcall
|
if(funcall!=null) return funcall
|
||||||
|
|
||||||
if (rangefrom!=null && rangeto!=null) {
|
if (rangefrom!=null && rangeto!=null) {
|
||||||
val step = rangestep?.toAst() ?: LiteralValue(DataType.UBYTE, 1, position = toPosition())
|
val step = rangestep?.toAst() ?: NumericLiteralValue(DataType.UBYTE, 1, toPosition())
|
||||||
return RangeExpr(rangefrom.toAst(), rangeto.toAst(), step, toPosition())
|
return RangeExpr(rangefrom.toAst(), rangeto.toAst(), step, toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -474,10 +519,9 @@ private fun prog8Parser.BooleanliteralContext.toAst() = when(text) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.ArrayliteralContext.toAst() : Array<IExpression> =
|
private fun prog8Parser.ArrayliteralContext.toAst() : Array<Expression> =
|
||||||
expression().map { it.toAst() }.toTypedArray()
|
expression().map { it.toAst() }.toTypedArray()
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.If_stmtContext.toAst(): IfStatement {
|
private fun prog8Parser.If_stmtContext.toAst(): IfStatement {
|
||||||
val condition = expression().toAst()
|
val condition = expression().toAst()
|
||||||
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||||
@ -488,7 +532,7 @@ private fun prog8Parser.If_stmtContext.toAst(): IfStatement {
|
|||||||
return IfStatement(condition, trueScope, elseScope, toPosition())
|
return IfStatement(condition, trueScope, elseScope, toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun prog8Parser.Else_partContext.toAst(): MutableList<IStatement> {
|
private fun prog8Parser.Else_partContext.toAst(): MutableList<Statement> {
|
||||||
return statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
return statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -508,8 +552,6 @@ private fun prog8Parser.BranchconditionContext.toAst() = BranchCondition.valueOf
|
|||||||
|
|
||||||
private fun prog8Parser.ForloopContext.toAst(): ForLoop {
|
private fun prog8Parser.ForloopContext.toAst(): ForLoop {
|
||||||
val loopregister = register()?.toAst()
|
val loopregister = register()?.toAst()
|
||||||
val datatype = datatype()?.toAst()
|
|
||||||
val zeropage = ZEROPAGE()!=null
|
|
||||||
val loopvar = identifier()?.toAst()
|
val loopvar = identifier()?.toAst()
|
||||||
val iterable = expression()!!.toAst()
|
val iterable = expression()!!.toAst()
|
||||||
val scope =
|
val scope =
|
||||||
@ -517,7 +559,7 @@ private fun prog8Parser.ForloopContext.toAst(): ForLoop {
|
|||||||
AnonymousScope(mutableListOf(statement().toAst()), statement().toPosition())
|
AnonymousScope(mutableListOf(statement().toAst()), statement().toPosition())
|
||||||
else
|
else
|
||||||
AnonymousScope(statement_block().toAst(), statement_block().toPosition())
|
AnonymousScope(statement_block().toAst(), statement_block().toPosition())
|
||||||
return ForLoop(loopregister, datatype, zeropage, loopvar, iterable, scope, toPosition())
|
return ForLoop(loopregister, loopvar, iterable, scope, toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -552,13 +594,28 @@ private fun prog8Parser.WhenstmtContext.toAst(): WhenStatement {
|
|||||||
private fun prog8Parser.When_choiceContext.toAst(): WhenChoice {
|
private fun prog8Parser.When_choiceContext.toAst(): WhenChoice {
|
||||||
val values = expression_list()?.toAst()
|
val values = expression_list()?.toAst()
|
||||||
val stmt = statement()?.toAst()
|
val stmt = statement()?.toAst()
|
||||||
val stmt_block = statement_block()?.toAst()?.toMutableList() ?: mutableListOf()
|
val stmtBlock = statement_block()?.toAst()?.toMutableList() ?: mutableListOf()
|
||||||
if(stmt!=null)
|
if(stmt!=null)
|
||||||
stmt_block.add(stmt)
|
stmtBlock.add(stmt)
|
||||||
val scope = AnonymousScope(stmt_block, toPosition())
|
val scope = AnonymousScope(stmtBlock, toPosition())
|
||||||
return WhenChoice(values, scope, toPosition())
|
return WhenChoice(values, scope, toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.VardeclContext.toAst(): VarDecl {
|
||||||
|
return VarDecl(
|
||||||
|
VarDeclType.VAR,
|
||||||
|
datatype()?.toAst() ?: DataType.STRUCT,
|
||||||
|
if(ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||||
|
arrayindex()?.toAst(),
|
||||||
|
varname.text,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
ARRAYSIG() != null || arrayindex() != null,
|
||||||
|
false,
|
||||||
|
toPosition()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
internal fun escape(str: String) = str.replace("\t", "\\t").replace("\n", "\\n").replace("\r", "\\r")
|
internal fun escape(str: String) = str.replace("\t", "\\t").replace("\n", "\\n").replace("\r", "\\r")
|
||||||
|
|
||||||
internal fun unescape(str: String, position: Position): String {
|
internal fun unescape(str: String, position: Position): String {
|
||||||
@ -584,3 +641,4 @@ internal fun unescape(str: String, position: Position): String {
|
|||||||
}
|
}
|
||||||
return result.joinToString("")
|
return result.joinToString("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,22 +1,24 @@
|
|||||||
package prog8.ast.base
|
package prog8.ast.base
|
||||||
|
|
||||||
import prog8.ast.Node
|
import prog8.ast.Node
|
||||||
|
import prog8.compiler.target.c64.MachineDefinition
|
||||||
|
|
||||||
/**************************** AST Data classes ****************************/
|
/**************************** AST Data classes ****************************/
|
||||||
|
|
||||||
enum class DataType {
|
enum class DataType {
|
||||||
UBYTE,
|
UBYTE, // pass by value
|
||||||
BYTE,
|
BYTE, // pass by value
|
||||||
UWORD,
|
UWORD, // pass by value
|
||||||
WORD,
|
WORD, // pass by value
|
||||||
FLOAT,
|
FLOAT, // pass by value
|
||||||
STR,
|
STR, // pass by reference
|
||||||
STR_S,
|
STR_S, // pass by reference
|
||||||
ARRAY_UB,
|
ARRAY_UB, // pass by reference
|
||||||
ARRAY_B,
|
ARRAY_B, // pass by reference
|
||||||
ARRAY_UW,
|
ARRAY_UW, // pass by reference
|
||||||
ARRAY_W,
|
ARRAY_W, // pass by reference
|
||||||
ARRAY_F;
|
ARRAY_F, // pass by reference
|
||||||
|
STRUCT; // pass by reference
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* is the type assignable to the given other type?
|
* is the type assignable to the given other type?
|
||||||
@ -24,14 +26,14 @@ enum class DataType {
|
|||||||
infix fun isAssignableTo(targetType: DataType) =
|
infix fun isAssignableTo(targetType: DataType) =
|
||||||
// what types are assignable to others without loss of precision?
|
// what types are assignable to others without loss of precision?
|
||||||
when(this) {
|
when(this) {
|
||||||
UBYTE -> targetType == UBYTE || targetType == UWORD || targetType==WORD || targetType == FLOAT
|
UBYTE -> targetType in setOf(UBYTE, WORD, UWORD, FLOAT)
|
||||||
BYTE -> targetType == BYTE || targetType == UBYTE || targetType == UWORD || targetType==WORD || targetType == FLOAT
|
BYTE -> targetType in setOf(BYTE, WORD, FLOAT)
|
||||||
UWORD -> targetType == UWORD || targetType == FLOAT
|
UWORD -> targetType in setOf(UWORD, FLOAT)
|
||||||
WORD -> targetType == WORD || targetType==UWORD || targetType == FLOAT
|
WORD -> targetType in setOf(WORD, FLOAT)
|
||||||
FLOAT -> targetType == FLOAT
|
FLOAT -> targetType == FLOAT
|
||||||
STR -> targetType == STR || targetType==STR_S
|
STR -> targetType == STR || targetType==STR_S
|
||||||
STR_S -> targetType == STR || targetType==STR_S
|
STR_S -> targetType == STR || targetType==STR_S
|
||||||
in ArrayDatatypes -> targetType === this
|
in ArrayDatatypes -> targetType == this
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,6 +53,16 @@ enum class DataType {
|
|||||||
in WordDatatypes -> other in WordDatatypes
|
in WordDatatypes -> other in WordDatatypes
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun memorySize(): Int {
|
||||||
|
return when(this) {
|
||||||
|
in ByteDatatypes -> 1
|
||||||
|
in WordDatatypes -> 2
|
||||||
|
FLOAT -> MachineDefinition.Mflpt5.MemorySize
|
||||||
|
in PassByReferenceDatatypes -> 2
|
||||||
|
else -> -9999999
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class Register {
|
enum class Register {
|
||||||
@ -96,18 +108,22 @@ enum class VarDeclType {
|
|||||||
MEMORY
|
MEMORY
|
||||||
}
|
}
|
||||||
|
|
||||||
val IterableDatatypes = setOf(
|
|
||||||
DataType.STR, DataType.STR_S,
|
|
||||||
DataType.ARRAY_UB, DataType.ARRAY_B,
|
|
||||||
DataType.ARRAY_UW, DataType.ARRAY_W,
|
|
||||||
DataType.ARRAY_F)
|
|
||||||
val ByteDatatypes = setOf(DataType.UBYTE, DataType.BYTE)
|
val ByteDatatypes = setOf(DataType.UBYTE, DataType.BYTE)
|
||||||
val WordDatatypes = setOf(DataType.UWORD, DataType.WORD)
|
val WordDatatypes = setOf(DataType.UWORD, DataType.WORD)
|
||||||
val IntegerDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD)
|
val IntegerDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD)
|
||||||
val NumericDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT)
|
val NumericDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT)
|
||||||
val StringDatatypes = setOf(DataType.STR, DataType.STR_S)
|
val StringDatatypes = setOf(DataType.STR, DataType.STR_S)
|
||||||
val ArrayDatatypes = setOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F)
|
val ArrayDatatypes = setOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F)
|
||||||
|
val IterableDatatypes = setOf(
|
||||||
|
DataType.STR, DataType.STR_S,
|
||||||
|
DataType.ARRAY_UB, DataType.ARRAY_B,
|
||||||
|
DataType.ARRAY_UW, DataType.ARRAY_W,
|
||||||
|
DataType.ARRAY_F)
|
||||||
|
val PassByValueDatatypes = NumericDatatypes
|
||||||
|
val PassByReferenceDatatypes = IterableDatatypes.plus(DataType.STRUCT)
|
||||||
val ArrayElementTypes = mapOf(
|
val ArrayElementTypes = mapOf(
|
||||||
|
DataType.STR to DataType.UBYTE,
|
||||||
|
DataType.STR_S to DataType.UBYTE,
|
||||||
DataType.ARRAY_B to DataType.BYTE,
|
DataType.ARRAY_B to DataType.BYTE,
|
||||||
DataType.ARRAY_UB to DataType.UBYTE,
|
DataType.ARRAY_UB to DataType.UBYTE,
|
||||||
DataType.ARRAY_W to DataType.WORD,
|
DataType.ARRAY_W to DataType.WORD,
|
||||||
|
@ -5,7 +5,7 @@ import prog8.parser.ParsingFailedError
|
|||||||
|
|
||||||
fun printErrors(errors: List<Any>, moduleName: String) {
|
fun printErrors(errors: List<Any>, moduleName: String) {
|
||||||
val reportedMessages = mutableSetOf<String>()
|
val reportedMessages = mutableSetOf<String>()
|
||||||
print("\u001b[91m") // bright red
|
System.err.print("\u001b[91m") // bright red
|
||||||
errors.forEach {
|
errors.forEach {
|
||||||
val msg = it.toString()
|
val msg = it.toString()
|
||||||
if(msg !in reportedMessages) {
|
if(msg !in reportedMessages) {
|
||||||
@ -13,7 +13,7 @@ fun printErrors(errors: List<Any>, moduleName: String) {
|
|||||||
reportedMessages.add(msg)
|
reportedMessages.add(msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
print("\u001b[0m") // reset color
|
System.err.print("\u001b[0m") // reset color
|
||||||
if(reportedMessages.isNotEmpty())
|
if(reportedMessages.isNotEmpty())
|
||||||
throw ParsingFailedError("There are ${reportedMessages.size} errors in module '$moduleName'.")
|
throw ParsingFailedError("There are ${reportedMessages.size} errors in module '$moduleName'.")
|
||||||
}
|
}
|
||||||
|
@ -10,13 +10,13 @@ class SyntaxError(override var message: String, val position: Position) : AstExc
|
|||||||
override fun toString() = "$position Syntax error: $message"
|
override fun toString() = "$position Syntax error: $message"
|
||||||
}
|
}
|
||||||
|
|
||||||
class NameError(override var message: String, val position: Position) : AstException(message) {
|
open class NameError(override var message: String, val position: Position) : AstException(message) {
|
||||||
override fun toString() = "$position Name error: $message"
|
override fun toString() = "$position Name error: $message"
|
||||||
}
|
}
|
||||||
|
|
||||||
open class ExpressionError(message: String, val position: Position) : AstException(message) {
|
class ExpressionError(message: String, val position: Position) : AstException(message) {
|
||||||
override fun toString() = "$position Error: $message"
|
override fun toString() = "$position Error: $message"
|
||||||
}
|
}
|
||||||
|
|
||||||
class UndefinedSymbolError(symbol: IdentifierReference)
|
class UndefinedSymbolError(symbol: IdentifierReference)
|
||||||
: ExpressionError("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)
|
: NameError("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)
|
||||||
|
@ -1,25 +1,20 @@
|
|||||||
package prog8.ast.base
|
package prog8.ast.base
|
||||||
|
|
||||||
import prog8.ast.*
|
import prog8.ast.Module
|
||||||
import prog8.ast.expressions.IdentifierReference
|
import prog8.ast.Program
|
||||||
import prog8.ast.processing.*
|
import prog8.ast.processing.*
|
||||||
import prog8.ast.statements.Assignment
|
|
||||||
import prog8.ast.statements.ForLoop
|
|
||||||
import prog8.compiler.CompilationOptions
|
import prog8.compiler.CompilationOptions
|
||||||
import prog8.optimizer.RemoveNops
|
import prog8.compiler.target.c64.codegen.AnonymousScopeVarsCleanup
|
||||||
|
import prog8.optimizer.FlattenAnonymousScopesAndRemoveNops
|
||||||
|
|
||||||
|
|
||||||
// the name of the subroutine that should be called for every block to initialize its variables
|
// the name of the subroutine that should be called for every block to initialize its variables
|
||||||
internal const val initvarsSubName="prog8_init_vars"
|
internal const val initvarsSubName="prog8_init_vars"
|
||||||
|
|
||||||
|
|
||||||
// prefix for literal values that are turned into a variable on the heap
|
internal fun Program.removeNopsFlattenAnonScopes() {
|
||||||
internal const val autoHeapValuePrefix = "auto_heap_value_"
|
val flattener = FlattenAnonymousScopesAndRemoveNops()
|
||||||
|
flattener.visit(this)
|
||||||
|
|
||||||
internal fun Program.removeNops() {
|
|
||||||
val remover = RemoveNops()
|
|
||||||
remover.visit(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -30,14 +25,26 @@ internal fun Program.checkValid(compilerOptions: CompilationOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal fun Program.anonscopeVarsCleanup() {
|
||||||
|
val mover = AnonymousScopeVarsCleanup(this)
|
||||||
|
mover.visit(this)
|
||||||
|
printErrors(mover.result(), name)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
internal fun Program.reorderStatements() {
|
internal fun Program.reorderStatements() {
|
||||||
val initvalueCreator = VarInitValueAndAddressOfCreator(namespace)
|
val initvalueCreator = VarInitValueAndAddressOfCreator(this)
|
||||||
initvalueCreator.visit(this)
|
initvalueCreator.visit(this)
|
||||||
|
|
||||||
val checker = StatementReorderer(this)
|
val checker = StatementReorderer(this)
|
||||||
checker.visit(this)
|
checker.visit(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun Program.addTypecasts() {
|
||||||
|
val caster = TypecastsAdder(this)
|
||||||
|
caster.visit(this)
|
||||||
|
}
|
||||||
|
|
||||||
internal fun Module.checkImportedValid() {
|
internal fun Module.checkImportedValid() {
|
||||||
val checker = ImportedModuleDirectiveRemover()
|
val checker = ImportedModuleDirectiveRemover()
|
||||||
checker.visit(this)
|
checker.visit(this)
|
||||||
@ -52,41 +59,12 @@ internal fun Program.checkRecursion() {
|
|||||||
|
|
||||||
|
|
||||||
internal fun Program.checkIdentifiers() {
|
internal fun Program.checkIdentifiers() {
|
||||||
val checker = AstIdentifiersChecker(namespace)
|
val checker = AstIdentifiersChecker(this)
|
||||||
checker.visit(this)
|
checker.visit(this)
|
||||||
|
|
||||||
if(modules.map {it.name}.toSet().size != modules.size) {
|
if(modules.map {it.name}.toSet().size != modules.size) {
|
||||||
throw FatalAstException("modules should all be unique")
|
throw FatalAstException("modules should all be unique")
|
||||||
}
|
}
|
||||||
|
|
||||||
// add any anonymous variables for heap values that are used,
|
|
||||||
// and replace an iterable literalvalue by identifierref to new local variable
|
|
||||||
// TODO: this is't doing anything anymore?
|
|
||||||
for (variable in checker.anonymousVariablesFromHeap.values) {
|
|
||||||
val scope = variable.first.definingScope()
|
|
||||||
scope.statements.add(variable.second)
|
|
||||||
val parent = variable.first.parent
|
|
||||||
when {
|
|
||||||
parent is Assignment && parent.value === variable.first -> {
|
|
||||||
val idref = IdentifierReference(listOf("$autoHeapValuePrefix${variable.first.heapId}"), variable.first.position)
|
|
||||||
idref.linkParents(parent)
|
|
||||||
parent.value = idref
|
|
||||||
}
|
|
||||||
parent is IFunctionCall -> {
|
|
||||||
val parameterPos = parent.arglist.indexOf(variable.first)
|
|
||||||
val idref = IdentifierReference(listOf("$autoHeapValuePrefix${variable.first.heapId}"), variable.first.position)
|
|
||||||
idref.linkParents(parent)
|
|
||||||
parent.arglist[parameterPos] = idref
|
|
||||||
}
|
|
||||||
parent is ForLoop -> {
|
|
||||||
val idref = IdentifierReference(listOf("$autoHeapValuePrefix${variable.first.heapId}"), variable.first.position)
|
|
||||||
idref.linkParents(parent)
|
|
||||||
parent.iterable = idref
|
|
||||||
}
|
|
||||||
else -> TODO("replace literalvalue by identifierref: $variable (in $parent)")
|
|
||||||
}
|
|
||||||
variable.second.linkParents(scope as Node)
|
|
||||||
}
|
|
||||||
|
|
||||||
printErrors(checker.result(), name)
|
printErrors(checker.result(), name)
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
61
compiler/src/prog8/ast/expressions/InferredTypes.kt
Normal file
61
compiler/src/prog8/ast/expressions/InferredTypes.kt
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package prog8.ast.expressions
|
||||||
|
|
||||||
|
import java.util.Objects
|
||||||
|
import prog8.ast.base.DataType
|
||||||
|
|
||||||
|
|
||||||
|
object InferredTypes {
|
||||||
|
class InferredType private constructor(val isUnknown: Boolean, val isVoid: Boolean, private var datatype: DataType?) {
|
||||||
|
init {
|
||||||
|
require(!(datatype!=null && (isUnknown || isVoid))) { "invalid combination of args" }
|
||||||
|
}
|
||||||
|
|
||||||
|
val isKnown = datatype!=null
|
||||||
|
fun typeOrElse(alternative: DataType) = if(isUnknown || isVoid) alternative else datatype!!
|
||||||
|
infix fun istype(type: DataType): Boolean = if(isUnknown || isVoid) false else this.datatype==type
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun unknown() = InferredType(isUnknown = true, isVoid = false, datatype = null)
|
||||||
|
fun void() = InferredType(isUnknown = false, isVoid = true, datatype = null)
|
||||||
|
fun known(type: DataType) = InferredType(isUnknown = false, isVoid = false, datatype = type)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if(other !is InferredType)
|
||||||
|
return false
|
||||||
|
return isVoid==other.isVoid && datatype==other.datatype
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return when {
|
||||||
|
datatype!=null -> datatype.toString()
|
||||||
|
isVoid -> "<void>"
|
||||||
|
else -> "<unknown>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int = Objects.hash(isVoid, datatype)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val unknownInstance = InferredType.unknown()
|
||||||
|
private val voidInstance = InferredType.void()
|
||||||
|
private val knownInstances = mapOf(
|
||||||
|
DataType.UBYTE to InferredType.known(DataType.UBYTE),
|
||||||
|
DataType.BYTE to InferredType.known(DataType.BYTE),
|
||||||
|
DataType.UWORD to InferredType.known(DataType.UWORD),
|
||||||
|
DataType.WORD to InferredType.known(DataType.WORD),
|
||||||
|
DataType.FLOAT to InferredType.known(DataType.FLOAT),
|
||||||
|
DataType.STR to InferredType.known(DataType.STR),
|
||||||
|
DataType.STR_S to InferredType.known(DataType.STR_S),
|
||||||
|
DataType.ARRAY_UB to InferredType.known(DataType.ARRAY_UB),
|
||||||
|
DataType.ARRAY_B to InferredType.known(DataType.ARRAY_B),
|
||||||
|
DataType.ARRAY_UW to InferredType.known(DataType.ARRAY_UW),
|
||||||
|
DataType.ARRAY_W to InferredType.known(DataType.ARRAY_W),
|
||||||
|
DataType.ARRAY_F to InferredType.known(DataType.ARRAY_F),
|
||||||
|
DataType.STRUCT to InferredType.known(DataType.STRUCT)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun void() = voidInstance
|
||||||
|
fun unknown() = unknownInstance
|
||||||
|
fun knownFor(type: DataType) = knownInstances.getValue(type)
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -1,32 +1,43 @@
|
|||||||
package prog8.ast.processing
|
package prog8.ast.processing
|
||||||
|
|
||||||
import prog8.ast.*
|
import prog8.ast.INameScope
|
||||||
|
import prog8.ast.Module
|
||||||
|
import prog8.ast.Node
|
||||||
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.base.autoHeapValuePrefix
|
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
|
import prog8.compiler.target.c64.AssemblyProgram
|
||||||
import prog8.functions.BuiltinFunctions
|
import prog8.functions.BuiltinFunctions
|
||||||
|
|
||||||
|
|
||||||
internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstModifyingVisitor {
|
internal class AstIdentifiersChecker(private val program: Program) : IAstModifyingVisitor {
|
||||||
|
|
||||||
private val checkResult: MutableList<AstException> = mutableListOf()
|
private val checkResult: MutableList<AstException> = mutableListOf()
|
||||||
|
|
||||||
private var blocks: MutableMap<String, Block> = mutableMapOf()
|
private var blocks = mutableMapOf<String, Block>()
|
||||||
|
private val vardeclsToAdd = mutableMapOf<INameScope, MutableList<VarDecl>>()
|
||||||
|
|
||||||
internal fun result(): List<AstException> {
|
internal fun result(): List<AstException> {
|
||||||
return checkResult
|
return checkResult
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun nameError(name: String, position: Position, existing: IStatement) {
|
private fun nameError(name: String, position: Position, existing: Statement) {
|
||||||
checkResult.add(NameError("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position))
|
checkResult.add(NameError("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(module: Module) {
|
override fun visit(module: Module) {
|
||||||
|
vardeclsToAdd.clear()
|
||||||
blocks.clear() // blocks may be redefined within a different module
|
blocks.clear() // blocks may be redefined within a different module
|
||||||
super.visit(module)
|
super.visit(module)
|
||||||
|
// add any new vardecls to the various scopes
|
||||||
|
for((where, decls) in vardeclsToAdd) {
|
||||||
|
where.statements.addAll(0, decls)
|
||||||
|
decls.forEach { it.linkParents(where as Node) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(block: Block): IStatement {
|
override fun visit(block: Block): Statement {
|
||||||
val existing = blocks[block.name]
|
val existing = blocks[block.name]
|
||||||
if(existing!=null)
|
if(existing!=null)
|
||||||
nameError(block.name, block.position, existing)
|
nameError(block.name, block.position, existing)
|
||||||
@ -36,7 +47,7 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
|
|||||||
return super.visit(block)
|
return super.visit(block)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(functionCall: FunctionCall): IExpression {
|
override fun visit(functionCall: FunctionCall): Expression {
|
||||||
if(functionCall.target.nameInSource.size==1 && functionCall.target.nameInSource[0]=="lsb") {
|
if(functionCall.target.nameInSource.size==1 && functionCall.target.nameInSource[0]=="lsb") {
|
||||||
// lsb(...) is just an alias for type cast to ubyte, so replace with "... as ubyte"
|
// lsb(...) is just an alias for type cast to ubyte, so replace with "... as ubyte"
|
||||||
val typecast = TypecastExpression(functionCall.arglist.single(), DataType.UBYTE, false, functionCall.position)
|
val typecast = TypecastExpression(functionCall.arglist.single(), DataType.UBYTE, false, functionCall.position)
|
||||||
@ -46,7 +57,7 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
|
|||||||
return super.visit(functionCall)
|
return super.visit(functionCall)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(decl: VarDecl): IStatement {
|
override fun visit(decl: VarDecl): Statement {
|
||||||
// first, check if there are datatype errors on the vardecl
|
// first, check if there are datatype errors on the vardecl
|
||||||
decl.datatypeErrors.forEach { checkResult.add(it) }
|
decl.datatypeErrors.forEach { checkResult.add(it) }
|
||||||
|
|
||||||
@ -55,25 +66,65 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
|
|||||||
// the builtin functions can't be redefined
|
// the builtin functions can't be redefined
|
||||||
checkResult.add(NameError("builtin function cannot be redefined", decl.position))
|
checkResult.add(NameError("builtin function cannot be redefined", decl.position))
|
||||||
|
|
||||||
val existing = namespace.lookup(listOf(decl.name), decl)
|
if(decl.name in AssemblyProgram.opcodeNames)
|
||||||
|
checkResult.add(NameError("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position))
|
||||||
|
|
||||||
|
// is it a struct variable? then define all its struct members as mangled names,
|
||||||
|
// and include the original decl as well.
|
||||||
|
if(decl.datatype==DataType.STRUCT) {
|
||||||
|
if(decl.structHasBeenFlattened)
|
||||||
|
return super.visit(decl) // don't do this multiple times
|
||||||
|
|
||||||
|
if(decl.struct==null) {
|
||||||
|
checkResult.add(NameError("undefined struct type", decl.position))
|
||||||
|
return super.visit(decl)
|
||||||
|
}
|
||||||
|
|
||||||
|
if(decl.struct!!.statements.any { (it as VarDecl).datatype !in NumericDatatypes})
|
||||||
|
return super.visit(decl) // a non-numeric member, not supported. proper error is given by AstChecker later
|
||||||
|
|
||||||
|
if(decl.value is NumericLiteralValue) {
|
||||||
|
checkResult.add(ExpressionError("you cannot initialize a struct using a single value", decl.position))
|
||||||
|
return super.visit(decl)
|
||||||
|
}
|
||||||
|
|
||||||
|
val decls = decl.flattenStructMembers()
|
||||||
|
decls.add(decl)
|
||||||
|
val result = AnonymousScope(decls, decl.position)
|
||||||
|
result.linkParents(decl.parent)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
val existing = program.namespace.lookup(listOf(decl.name), decl)
|
||||||
if (existing != null && existing !== decl)
|
if (existing != null && existing !== decl)
|
||||||
nameError(decl.name, decl.position, existing)
|
nameError(decl.name, decl.position, existing)
|
||||||
|
|
||||||
return super.visit(decl)
|
return super.visit(decl)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(subroutine: Subroutine): IStatement {
|
override fun visit(subroutine: Subroutine): Statement {
|
||||||
if(subroutine.name in BuiltinFunctions) {
|
if(subroutine.name in AssemblyProgram.opcodeNames) {
|
||||||
|
checkResult.add(NameError("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position))
|
||||||
|
} else if(subroutine.name in BuiltinFunctions) {
|
||||||
// the builtin functions can't be redefined
|
// the builtin functions can't be redefined
|
||||||
checkResult.add(NameError("builtin function cannot be redefined", subroutine.position))
|
checkResult.add(NameError("builtin function cannot be redefined", subroutine.position))
|
||||||
} else {
|
} else {
|
||||||
if (subroutine.parameters.any { it.name in BuiltinFunctions })
|
// already reported elsewhere:
|
||||||
checkResult.add(NameError("builtin function name cannot be used as parameter", subroutine.position))
|
// if (subroutine.parameters.any { it.name in BuiltinFunctions })
|
||||||
|
// checkResult.add(NameError("builtin function name cannot be used as parameter", subroutine.position))
|
||||||
|
|
||||||
val existing = namespace.lookup(listOf(subroutine.name), subroutine)
|
val existing = program.namespace.lookup(listOf(subroutine.name), subroutine)
|
||||||
if (existing != null && existing !== subroutine)
|
if (existing != null && existing !== subroutine)
|
||||||
nameError(subroutine.name, subroutine.position, existing)
|
nameError(subroutine.name, subroutine.position, existing)
|
||||||
|
|
||||||
|
// does the parameter redefine a variable declared elsewhere?
|
||||||
|
for(param in subroutine.parameters) {
|
||||||
|
val existingVar = subroutine.lookup(listOf(param.name), subroutine)
|
||||||
|
if (existingVar != null && existingVar.parent !== subroutine) {
|
||||||
|
nameError(param.name, param.position, existingVar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// check that there are no local variables, labels, or other subs that redefine the subroutine's parameters
|
// check that there are no local variables, labels, or other subs that redefine the subroutine's parameters
|
||||||
val symbolsInSub = subroutine.allDefinedSymbols()
|
val symbolsInSub = subroutine.allDefinedSymbols()
|
||||||
val namesInSub = symbolsInSub.map{ it.first }.toSet()
|
val namesInSub = symbolsInSub.map{ it.first }.toSet()
|
||||||
@ -92,69 +143,64 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
|
|||||||
// NOTE:
|
// NOTE:
|
||||||
// - numeric types BYTE and WORD and FLOAT are passed by value;
|
// - numeric types BYTE and WORD and FLOAT are passed by value;
|
||||||
// - strings, arrays, matrices are passed by reference (their 16-bit address is passed as an uword parameter)
|
// - strings, arrays, matrices are passed by reference (their 16-bit address is passed as an uword parameter)
|
||||||
// - do NOT do this is the statement can be transformed into an asm subroutine later!
|
if(subroutine.asmAddress==null) {
|
||||||
if(subroutine.asmAddress==null && !subroutine.canBeAsmSubroutine) {
|
|
||||||
if(subroutine.asmParameterRegisters.isEmpty()) {
|
if(subroutine.asmParameterRegisters.isEmpty()) {
|
||||||
subroutine.parameters
|
subroutine.parameters
|
||||||
.filter { it.name !in namesInSub }
|
.filter { it.name !in namesInSub }
|
||||||
.forEach {
|
.forEach {
|
||||||
val vardecl = VarDecl(VarDeclType.VAR, it.type, false, null, it.name, null,
|
val vardecl = VarDecl(VarDeclType.VAR, it.type, ZeropageWish.NOT_IN_ZEROPAGE, null, it.name, null, null,
|
||||||
isArray = false, autoGenerated = true, position = subroutine.position)
|
isArray = false, autogeneratedDontRemove = true, position = subroutine.position)
|
||||||
vardecl.linkParents(subroutine)
|
vardecl.linkParents(subroutine)
|
||||||
subroutine.statements.add(0, vardecl)
|
subroutine.statements.add(0, vardecl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) {
|
||||||
|
checkResult.add(SyntaxError("asmsub can only contain inline assembly (%asm)", subroutine.position))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return super.visit(subroutine)
|
return super.visit(subroutine)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(label: Label): IStatement {
|
override fun visit(label: Label): Statement {
|
||||||
|
if(label.name in AssemblyProgram.opcodeNames)
|
||||||
|
checkResult.add(NameError("can't use a cpu opcode name as a symbol: '${label.name}'", label.position))
|
||||||
|
|
||||||
if(label.name in BuiltinFunctions) {
|
if(label.name in BuiltinFunctions) {
|
||||||
// the builtin functions can't be redefined
|
// the builtin functions can't be redefined
|
||||||
checkResult.add(NameError("builtin function cannot be redefined", label.position))
|
checkResult.add(NameError("builtin function cannot be redefined", label.position))
|
||||||
} else {
|
} else {
|
||||||
val existing = namespace.lookup(listOf(label.name), label)
|
val existing = program.namespace.lookup(listOf(label.name), label)
|
||||||
if (existing != null && existing !== label)
|
if (existing != null && existing !== label)
|
||||||
nameError(label.name, label.position, existing)
|
nameError(label.name, label.position, existing)
|
||||||
}
|
}
|
||||||
return super.visit(label)
|
return super.visit(label)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(forLoop: ForLoop): IStatement {
|
override fun visit(forLoop: ForLoop): Statement {
|
||||||
// If the for loop has a decltype, it means to declare the loopvar inside the loop body
|
// If the for loop has a decltype, it means to declare the loopvar inside the loop body
|
||||||
// rather than reusing an already declared loopvar from an outer scope.
|
// rather than reusing an already declared loopvar from an outer scope.
|
||||||
// For loops that loop over an interable variable (instead of a range of numbers) get an
|
// For loops that loop over an interable variable (instead of a range of numbers) get an
|
||||||
// additional interation count variable in their scope.
|
// additional interation count variable in their scope.
|
||||||
if(forLoop.loopRegister!=null) {
|
if(forLoop.loopRegister!=null) {
|
||||||
if(forLoop.decltype!=null)
|
|
||||||
checkResult.add(SyntaxError("register loop variables have a fixed implicit datatype", forLoop.position))
|
|
||||||
if(forLoop.loopRegister == Register.X)
|
if(forLoop.loopRegister == Register.X)
|
||||||
printWarning("writing to the X register is dangerous, because it's used as an internal pointer", forLoop.position)
|
printWarning("writing to the X register is dangerous, because it's used as an internal pointer", forLoop.position)
|
||||||
} else if(forLoop.loopVar!=null) {
|
} else {
|
||||||
val varName = forLoop.loopVar.nameInSource.last()
|
val loopVar = forLoop.loopVar
|
||||||
if(forLoop.decltype!=null) {
|
if (loopVar != null) {
|
||||||
val existing = if(forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(forLoop.loopVar.nameInSource, forLoop.body.statements.first())
|
val validName = forLoop.body.name.replace("<", "").replace(">", "").replace("-", "")
|
||||||
if(existing==null) {
|
val loopvarName = "prog8_loopvar_$validName"
|
||||||
// create the local scoped for loop variable itself
|
if (forLoop.iterable !is RangeExpr) {
|
||||||
val vardecl = VarDecl(VarDeclType.VAR, forLoop.decltype, forLoop.zeropage, null, varName, null,
|
val existing = if (forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(listOf(loopvarName), forLoop.body.statements.first())
|
||||||
isArray = false, autoGenerated = true, position = forLoop.loopVar.position)
|
if (existing == null) {
|
||||||
vardecl.linkParents(forLoop.body)
|
// create loop iteration counter variable (without value, to avoid an assignment)
|
||||||
forLoop.body.statements.add(0, vardecl)
|
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.PREFER_ZEROPAGE, null, loopvarName, null, null,
|
||||||
forLoop.loopVar.parent = forLoop.body // loopvar 'is defined in the body'
|
isArray = false, autogeneratedDontRemove = true, position = loopVar.position)
|
||||||
}
|
vardecl.linkParents(forLoop.body)
|
||||||
|
forLoop.body.statements.add(0, vardecl)
|
||||||
}
|
loopVar.parent = forLoop.body // loopvar 'is defined in the body'
|
||||||
|
}
|
||||||
if(forLoop.iterable !is RangeExpr) {
|
|
||||||
val existing = if(forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(listOf(ForLoop.iteratorLoopcounterVarname), forLoop.body.statements.first())
|
|
||||||
if(existing==null) {
|
|
||||||
// create loop iteration counter variable (without value, to avoid an assignment)
|
|
||||||
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, true, null, ForLoop.iteratorLoopcounterVarname, null,
|
|
||||||
isArray = false, autoGenerated = true, position = forLoop.loopVar.position)
|
|
||||||
vardecl.linkParents(forLoop.body)
|
|
||||||
forLoop.body.statements.add(0, vardecl)
|
|
||||||
forLoop.loopVar.parent = forLoop.body // loopvar 'is defined in the body'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -167,49 +213,215 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
|
|||||||
return super.visit(assignTarget)
|
return super.visit(assignTarget)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(returnStmt: Return): IStatement {
|
override fun visit(returnStmt: Return): Statement {
|
||||||
if(returnStmt.value!=null) {
|
if(returnStmt.value!=null) {
|
||||||
// possibly adjust any literal values returned, into the desired returning data type
|
// possibly adjust any literal values returned, into the desired returning data type
|
||||||
val subroutine = returnStmt.definingSubroutine()!!
|
val subroutine = returnStmt.definingSubroutine()!!
|
||||||
if(subroutine.returntypes.size!=1)
|
if(subroutine.returntypes.size!=1)
|
||||||
return returnStmt // mismatch in number of return values, error will be printed later.
|
return returnStmt // mismatch in number of return values, error will be printed later.
|
||||||
val newValue: IExpression
|
val lval = returnStmt.value as? NumericLiteralValue
|
||||||
val lval = returnStmt.value as? LiteralValue
|
returnStmt.value = lval?.cast(subroutine.returntypes.single()) ?: returnStmt.value!!
|
||||||
if(lval!=null) {
|
|
||||||
val adjusted = lval.cast(subroutine.returntypes.single())
|
|
||||||
if(adjusted!=null && adjusted !== lval)
|
|
||||||
newValue = adjusted
|
|
||||||
else
|
|
||||||
newValue = lval
|
|
||||||
} else
|
|
||||||
newValue = returnStmt.value!!
|
|
||||||
|
|
||||||
returnStmt.value = newValue
|
|
||||||
}
|
}
|
||||||
return super.visit(returnStmt)
|
return super.visit(returnStmt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun visit(arrayLiteral: ArrayLiteralValue): Expression {
|
||||||
internal val anonymousVariablesFromHeap = mutableMapOf<String, Pair<LiteralValue, VarDecl>>()
|
val array = super.visit(arrayLiteral)
|
||||||
|
if(array is ArrayLiteralValue) {
|
||||||
|
val vardecl = array.parent as? VarDecl
|
||||||
override fun visit(literalValue: LiteralValue): LiteralValue {
|
return when {
|
||||||
if(literalValue.heapId!=null && literalValue.parent !is VarDecl) {
|
vardecl!=null -> fixupArrayDatatype(array, vardecl, program)
|
||||||
// a literal value that's not declared as a variable, which refers to something on the heap.
|
array.heapId!=null -> {
|
||||||
// we need to introduce an auto-generated variable for this to be able to refer to the value!
|
// fix the datatype of the array (also on the heap) to the 'biggest' datatype in the array
|
||||||
// (note: ususally, this has been taken care of already when the var was created)
|
// (we don't know the desired datatype here exactly so we guess)
|
||||||
val variable = VarDecl(VarDeclType.VAR, literalValue.type, false, null, "$autoHeapValuePrefix${literalValue.heapId}", literalValue,
|
val datatype = determineArrayDt(array.value)
|
||||||
isArray = false, autoGenerated = false, position = literalValue.position)
|
val litval2 = array.cast(datatype)!!
|
||||||
anonymousVariablesFromHeap[variable.name] = Pair(literalValue, variable)
|
litval2.parent = array.parent
|
||||||
|
// finally, replace the literal array by a identifier reference.
|
||||||
|
makeIdentifierFromRefLv(litval2)
|
||||||
|
}
|
||||||
|
else -> array
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return super.visit(literalValue)
|
return array
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(addressOf: AddressOf): IExpression {
|
override fun visit(stringLiteral: StringLiteralValue): Expression {
|
||||||
// register the scoped name of the referenced identifier
|
val string = super.visit(stringLiteral)
|
||||||
val variable= addressOf.identifier.targetVarDecl(namespace) ?: return addressOf
|
if(string is StringLiteralValue) {
|
||||||
addressOf.scopedname = variable.scopedname
|
val vardecl = string.parent as? VarDecl
|
||||||
return super.visit(addressOf)
|
// intern the string; move it into the heap
|
||||||
|
if (string.value.length !in 1..255)
|
||||||
|
checkResult.add(ExpressionError("string literal length must be between 1 and 255", string.position))
|
||||||
|
else {
|
||||||
|
string.addToHeap()
|
||||||
|
}
|
||||||
|
return if (vardecl != null)
|
||||||
|
string
|
||||||
|
else
|
||||||
|
makeIdentifierFromRefLv(string) // replace the literal string by a identifier reference.
|
||||||
|
}
|
||||||
|
return string
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun determineArrayDt(array: Array<Expression>): DataType {
|
||||||
|
val datatypesInArray = array.map { it.inferType(program) }
|
||||||
|
require(datatypesInArray.isNotEmpty() && datatypesInArray.all { it.isKnown }) { "can't determine type of empty array" }
|
||||||
|
val dts = datatypesInArray.map { it.typeOrElse(DataType.STRUCT) }
|
||||||
|
return when {
|
||||||
|
DataType.FLOAT in dts -> DataType.ARRAY_F
|
||||||
|
DataType.WORD in dts -> DataType.ARRAY_W
|
||||||
|
DataType.UWORD in dts -> DataType.ARRAY_UW
|
||||||
|
DataType.BYTE in dts -> DataType.ARRAY_B
|
||||||
|
DataType.UBYTE in dts -> DataType.ARRAY_UB
|
||||||
|
else -> throw IllegalArgumentException("can't determine type of array")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makeIdentifierFromRefLv(array: ArrayLiteralValue): IdentifierReference {
|
||||||
|
// a referencetype literal value that's not declared as a variable
|
||||||
|
// we need to introduce an auto-generated variable for this to be able to refer to the value
|
||||||
|
// note: if the var references the same literal value, it is not yet de-duplicated here.
|
||||||
|
array.addToHeap()
|
||||||
|
val scope = array.definingScope()
|
||||||
|
val variable = VarDecl.createAuto(array)
|
||||||
|
return replaceWithIdentifier(variable, scope, array.parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makeIdentifierFromRefLv(string: StringLiteralValue): IdentifierReference {
|
||||||
|
// a referencetype literal value that's not declared as a variable
|
||||||
|
// we need to introduce an auto-generated variable for this to be able to refer to the value
|
||||||
|
// note: if the var references the same literal value, it is not yet de-duplicated here.
|
||||||
|
string.addToHeap()
|
||||||
|
val scope = string.definingScope()
|
||||||
|
val variable = VarDecl.createAuto(string)
|
||||||
|
return replaceWithIdentifier(variable, scope, string.parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun replaceWithIdentifier(variable: VarDecl, scope: INameScope, parent: Node): IdentifierReference {
|
||||||
|
val variable1 = addVarDecl(scope, variable)
|
||||||
|
// replace the reference literal by a identifier reference
|
||||||
|
val identifier = IdentifierReference(listOf(variable1.name), variable1.position)
|
||||||
|
identifier.parent = parent
|
||||||
|
return identifier
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(structDecl: StructDecl): Statement {
|
||||||
|
for(member in structDecl.statements){
|
||||||
|
val decl = member as? VarDecl
|
||||||
|
if(decl!=null && decl.datatype !in NumericDatatypes)
|
||||||
|
checkResult.add(SyntaxError("structs can only contain numerical types", decl.position))
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.visit(structDecl)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(expr: BinaryExpression): Expression {
|
||||||
|
return when {
|
||||||
|
expr.left is StringLiteralValue ->
|
||||||
|
processBinaryExprWithString(expr.left as StringLiteralValue, expr.right, expr)
|
||||||
|
expr.right is StringLiteralValue ->
|
||||||
|
processBinaryExprWithString(expr.right as StringLiteralValue, expr.left, expr)
|
||||||
|
else -> super.visit(expr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processBinaryExprWithString(string: StringLiteralValue, operand: Expression, expr: BinaryExpression): Expression {
|
||||||
|
val constvalue = operand.constValue(program)
|
||||||
|
if(constvalue!=null) {
|
||||||
|
if (expr.operator == "*") {
|
||||||
|
// repeat a string a number of times
|
||||||
|
val idt = string.inferType(program)
|
||||||
|
return StringLiteralValue(idt.typeOrElse(DataType.STR),
|
||||||
|
string.value.repeat(constvalue.number.toInt()), expr.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(expr.operator == "+" && operand is StringLiteralValue) {
|
||||||
|
// concatenate two strings
|
||||||
|
val idt = string.inferType(program)
|
||||||
|
return StringLiteralValue(idt.typeOrElse(DataType.STR),
|
||||||
|
"${string.value}${operand.value}", expr.position)
|
||||||
|
}
|
||||||
|
return expr
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addVarDecl(scope: INameScope, variable: VarDecl): VarDecl {
|
||||||
|
if(scope !in vardeclsToAdd)
|
||||||
|
vardeclsToAdd[scope] = mutableListOf()
|
||||||
|
val declList = vardeclsToAdd.getValue(scope)
|
||||||
|
val existing = declList.singleOrNull { it.name==variable.name }
|
||||||
|
return if(existing!=null) {
|
||||||
|
existing
|
||||||
|
} else {
|
||||||
|
declList.add(variable)
|
||||||
|
variable
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun fixupArrayDatatype(array: ArrayLiteralValue, program: Program): ArrayLiteralValue {
|
||||||
|
val dts = array.value.map {it.inferType(program).typeOrElse(DataType.STRUCT)}.toSet()
|
||||||
|
if(dts.any { it !in NumericDatatypes }) {
|
||||||
|
return array
|
||||||
|
}
|
||||||
|
val dt = when {
|
||||||
|
DataType.FLOAT in dts -> DataType.ARRAY_F
|
||||||
|
DataType.WORD in dts -> DataType.ARRAY_W
|
||||||
|
DataType.UWORD in dts -> DataType.ARRAY_UW
|
||||||
|
DataType.BYTE in dts -> DataType.ARRAY_B
|
||||||
|
else -> DataType.ARRAY_UB
|
||||||
|
}
|
||||||
|
if(dt==array.type)
|
||||||
|
return array
|
||||||
|
|
||||||
|
// convert values and array type
|
||||||
|
val elementType = ArrayElementTypes.getValue(dt)
|
||||||
|
val values = array.value.map { (it as NumericLiteralValue).cast(elementType) as Expression}.toTypedArray()
|
||||||
|
val array2 = ArrayLiteralValue(dt, values, array.heapId, array.position)
|
||||||
|
array2.linkParents(array.parent)
|
||||||
|
return array2
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun fixupArrayDatatype(array: ArrayLiteralValue, vardecl: VarDecl, program: Program): ArrayLiteralValue {
|
||||||
|
if(array.heapId!=null) {
|
||||||
|
val arrayDt = array.type
|
||||||
|
if(arrayDt!=vardecl.datatype) {
|
||||||
|
// fix the datatype of the array (also on the heap) to match the vardecl
|
||||||
|
val litval2 =
|
||||||
|
try {
|
||||||
|
val result = array.cast(vardecl.datatype)
|
||||||
|
if(result==null) {
|
||||||
|
val constElements = array.value.mapNotNull { it.constValue(program) }
|
||||||
|
val elementDts = constElements.map { it.type }
|
||||||
|
if(DataType.FLOAT in elementDts) {
|
||||||
|
array.cast(DataType.ARRAY_F) ?: ArrayLiteralValue(DataType.ARRAY_F, array.value, array.heapId, array.position)
|
||||||
|
} else {
|
||||||
|
val numbers = constElements.map { it.number.toInt() }
|
||||||
|
val minValue = numbers.min()!!
|
||||||
|
val maxValue = numbers.max()!!
|
||||||
|
if (minValue >= 0) {
|
||||||
|
// only positive values, so uword or ubyte
|
||||||
|
val dt = if(maxValue<256) DataType.ARRAY_UB else DataType.ARRAY_UW
|
||||||
|
array.cast(dt) ?: ArrayLiteralValue(dt, array.value, array.heapId, array.position)
|
||||||
|
} else {
|
||||||
|
// negative value present, so word or byte
|
||||||
|
val dt = if(minValue >= -128 && maxValue<=127) DataType.ARRAY_B else DataType.ARRAY_W
|
||||||
|
array.cast(dt) ?: ArrayLiteralValue(dt, array.value, array.heapId, array.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else result
|
||||||
|
} catch(x: ExpressionError) {
|
||||||
|
// couldn't cast permanently.
|
||||||
|
// instead, simply adjust the array type and trust the AstChecker to report the exact error
|
||||||
|
ArrayLiteralValue(vardecl.datatype, array.value, array.heapId, array.position)
|
||||||
|
}
|
||||||
|
vardecl.value = litval2
|
||||||
|
litval2.linkParents(vardecl)
|
||||||
|
litval2.addToHeap()
|
||||||
|
return litval2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return array
|
||||||
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package prog8.ast.processing
|
package prog8.ast.processing
|
||||||
|
|
||||||
import prog8.ast.*
|
import prog8.ast.INameScope
|
||||||
import prog8.ast.base.AstException
|
import prog8.ast.base.AstException
|
||||||
import prog8.ast.expressions.FunctionCall
|
import prog8.ast.expressions.FunctionCall
|
||||||
import prog8.ast.statements.FunctionCallStatement
|
import prog8.ast.statements.FunctionCallStatement
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package prog8.ast.processing
|
package prog8.ast.processing
|
||||||
|
|
||||||
import prog8.ast.*
|
import prog8.ast.Module
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.FatalAstException
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
|
|
||||||
@ -10,63 +12,67 @@ interface IAstModifyingVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun visit(module: Module) {
|
fun visit(module: Module) {
|
||||||
module.statements = module.statements.asSequence().map { it.accept(this) }.toMutableList()
|
module.statements = module.statements.map { it.accept(this) }.toMutableList()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(expr: PrefixExpression): IExpression {
|
fun visit(expr: PrefixExpression): Expression {
|
||||||
expr.expression = expr.expression.accept(this)
|
expr.expression = expr.expression.accept(this)
|
||||||
return expr
|
return expr
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(expr: BinaryExpression): IExpression {
|
fun visit(expr: BinaryExpression): Expression {
|
||||||
expr.left = expr.left.accept(this)
|
expr.left = expr.left.accept(this)
|
||||||
expr.right = expr.right.accept(this)
|
expr.right = expr.right.accept(this)
|
||||||
return expr
|
return expr
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(directive: Directive): IStatement {
|
fun visit(directive: Directive): Statement {
|
||||||
return directive
|
return directive
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(block: Block): IStatement {
|
fun visit(block: Block): Statement {
|
||||||
block.statements = block.statements.asSequence().map { it.accept(this) }.toMutableList()
|
block.statements = block.statements.map { it.accept(this) }.toMutableList()
|
||||||
return block
|
return block
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(decl: VarDecl): IStatement {
|
fun visit(decl: VarDecl): Statement {
|
||||||
decl.value = decl.value?.accept(this)
|
decl.value = decl.value?.accept(this)
|
||||||
decl.arraysize?.accept(this)
|
decl.arraysize?.accept(this)
|
||||||
return decl
|
return decl
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(subroutine: Subroutine): IStatement {
|
fun visit(subroutine: Subroutine): Statement {
|
||||||
subroutine.statements = subroutine.statements.asSequence().map { it.accept(this) }.toMutableList()
|
subroutine.statements = subroutine.statements.map { it.accept(this) }.toMutableList()
|
||||||
return subroutine
|
return subroutine
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(functionCall: FunctionCall): IExpression {
|
fun visit(functionCall: FunctionCall): Expression {
|
||||||
val newtarget = functionCall.target.accept(this)
|
val newtarget = functionCall.target.accept(this)
|
||||||
if(newtarget is IdentifierReference)
|
if(newtarget is IdentifierReference)
|
||||||
functionCall.target = newtarget
|
functionCall.target = newtarget
|
||||||
|
else
|
||||||
|
throw FatalAstException("cannot change class of function call target")
|
||||||
functionCall.arglist = functionCall.arglist.map { it.accept(this) }.toMutableList()
|
functionCall.arglist = functionCall.arglist.map { it.accept(this) }.toMutableList()
|
||||||
return functionCall
|
return functionCall
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(functionCallStatement: FunctionCallStatement): IStatement {
|
fun visit(functionCallStatement: FunctionCallStatement): Statement {
|
||||||
val newtarget = functionCallStatement.target.accept(this)
|
val newtarget = functionCallStatement.target.accept(this)
|
||||||
if(newtarget is IdentifierReference)
|
if(newtarget is IdentifierReference)
|
||||||
functionCallStatement.target = newtarget
|
functionCallStatement.target = newtarget
|
||||||
|
else
|
||||||
|
throw FatalAstException("cannot change class of function call target")
|
||||||
functionCallStatement.arglist = functionCallStatement.arglist.map { it.accept(this) }.toMutableList()
|
functionCallStatement.arglist = functionCallStatement.arglist.map { it.accept(this) }.toMutableList()
|
||||||
return functionCallStatement
|
return functionCallStatement
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(identifier: IdentifierReference): IExpression {
|
fun visit(identifier: IdentifierReference): Expression {
|
||||||
// note: this is an identifier that is used in an expression.
|
// note: this is an identifier that is used in an expression.
|
||||||
// other identifiers are simply part of the other statements (such as jumps, subroutine defs etc)
|
// other identifiers are simply part of the other statements (such as jumps, subroutine defs etc)
|
||||||
return identifier
|
return identifier
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(jump: Jump): IStatement {
|
fun visit(jump: Jump): Statement {
|
||||||
if(jump.identifier!=null) {
|
if(jump.identifier!=null) {
|
||||||
val ident = jump.identifier.accept(this)
|
val ident = jump.identifier.accept(this)
|
||||||
if(ident is IdentifierReference && ident!==jump.identifier) {
|
if(ident is IdentifierReference && ident!==jump.identifier) {
|
||||||
@ -76,107 +82,123 @@ interface IAstModifyingVisitor {
|
|||||||
return jump
|
return jump
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(ifStatement: IfStatement): IStatement {
|
fun visit(ifStatement: IfStatement): Statement {
|
||||||
ifStatement.condition = ifStatement.condition.accept(this)
|
ifStatement.condition = ifStatement.condition.accept(this)
|
||||||
ifStatement.truepart = ifStatement.truepart.accept(this) as AnonymousScope
|
ifStatement.truepart = ifStatement.truepart.accept(this) as AnonymousScope
|
||||||
ifStatement.elsepart = ifStatement.elsepart.accept(this) as AnonymousScope
|
ifStatement.elsepart = ifStatement.elsepart.accept(this) as AnonymousScope
|
||||||
return ifStatement
|
return ifStatement
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(branchStatement: BranchStatement): IStatement {
|
fun visit(branchStatement: BranchStatement): Statement {
|
||||||
branchStatement.truepart = branchStatement.truepart.accept(this) as AnonymousScope
|
branchStatement.truepart = branchStatement.truepart.accept(this) as AnonymousScope
|
||||||
branchStatement.elsepart = branchStatement.elsepart.accept(this) as AnonymousScope
|
branchStatement.elsepart = branchStatement.elsepart.accept(this) as AnonymousScope
|
||||||
return branchStatement
|
return branchStatement
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(range: RangeExpr): IExpression {
|
fun visit(range: RangeExpr): Expression {
|
||||||
range.from = range.from.accept(this)
|
range.from = range.from.accept(this)
|
||||||
range.to = range.to.accept(this)
|
range.to = range.to.accept(this)
|
||||||
range.step = range.step.accept(this)
|
range.step = range.step.accept(this)
|
||||||
return range
|
return range
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(label: Label): IStatement {
|
fun visit(label: Label): Statement {
|
||||||
return label
|
return label
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(literalValue: LiteralValue): LiteralValue {
|
fun visit(literalValue: NumericLiteralValue): NumericLiteralValue {
|
||||||
if(literalValue.arrayvalue!=null) {
|
|
||||||
for(av in literalValue.arrayvalue.withIndex()) {
|
|
||||||
val newvalue = av.value.accept(this)
|
|
||||||
literalValue.arrayvalue[av.index] = newvalue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return literalValue
|
return literalValue
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(assignment: Assignment): IStatement {
|
fun visit(stringLiteral: StringLiteralValue): Expression {
|
||||||
|
return stringLiteral
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(arrayLiteral: ArrayLiteralValue): Expression {
|
||||||
|
for(av in arrayLiteral.value.withIndex()) {
|
||||||
|
val newvalue = av.value.accept(this)
|
||||||
|
arrayLiteral.value[av.index] = newvalue
|
||||||
|
}
|
||||||
|
return arrayLiteral
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(assignment: Assignment): Statement {
|
||||||
assignment.target = assignment.target.accept(this)
|
assignment.target = assignment.target.accept(this)
|
||||||
assignment.value = assignment.value.accept(this)
|
assignment.value = assignment.value.accept(this)
|
||||||
return assignment
|
return assignment
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(postIncrDecr: PostIncrDecr): IStatement {
|
fun visit(postIncrDecr: PostIncrDecr): Statement {
|
||||||
postIncrDecr.target = postIncrDecr.target.accept(this)
|
postIncrDecr.target = postIncrDecr.target.accept(this)
|
||||||
return postIncrDecr
|
return postIncrDecr
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(contStmt: Continue): IStatement {
|
fun visit(contStmt: Continue): Statement {
|
||||||
return contStmt
|
return contStmt
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(breakStmt: Break): IStatement {
|
fun visit(breakStmt: Break): Statement {
|
||||||
return breakStmt
|
return breakStmt
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(forLoop: ForLoop): IStatement {
|
fun visit(forLoop: ForLoop): Statement {
|
||||||
forLoop.loopVar?.accept(this)
|
when(val newloopvar = forLoop.loopVar?.accept(this)) {
|
||||||
|
is IdentifierReference -> forLoop.loopVar = newloopvar
|
||||||
|
null -> forLoop.loopVar = null
|
||||||
|
else -> throw FatalAstException("can't change class of loopvar")
|
||||||
|
}
|
||||||
forLoop.iterable = forLoop.iterable.accept(this)
|
forLoop.iterable = forLoop.iterable.accept(this)
|
||||||
forLoop.body = forLoop.body.accept(this) as AnonymousScope
|
forLoop.body = forLoop.body.accept(this) as AnonymousScope
|
||||||
return forLoop
|
return forLoop
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(whileLoop: WhileLoop): IStatement {
|
fun visit(whileLoop: WhileLoop): Statement {
|
||||||
whileLoop.condition = whileLoop.condition.accept(this)
|
whileLoop.condition = whileLoop.condition.accept(this)
|
||||||
whileLoop.body = whileLoop.body.accept(this) as AnonymousScope
|
whileLoop.body = whileLoop.body.accept(this) as AnonymousScope
|
||||||
return whileLoop
|
return whileLoop
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(repeatLoop: RepeatLoop): IStatement {
|
fun visit(repeatLoop: RepeatLoop): Statement {
|
||||||
repeatLoop.untilCondition = repeatLoop.untilCondition.accept(this)
|
repeatLoop.untilCondition = repeatLoop.untilCondition.accept(this)
|
||||||
repeatLoop.body = repeatLoop.body.accept(this) as AnonymousScope
|
repeatLoop.body = repeatLoop.body.accept(this) as AnonymousScope
|
||||||
return repeatLoop
|
return repeatLoop
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(returnStmt: Return): IStatement {
|
fun visit(returnStmt: Return): Statement {
|
||||||
returnStmt.value = returnStmt.value?.accept(this)
|
returnStmt.value = returnStmt.value?.accept(this)
|
||||||
return returnStmt
|
return returnStmt
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(arrayIndexedExpression: ArrayIndexedExpression): IExpression {
|
fun visit(arrayIndexedExpression: ArrayIndexedExpression): ArrayIndexedExpression {
|
||||||
arrayIndexedExpression.identifier.accept(this)
|
val ident = arrayIndexedExpression.identifier.accept(this)
|
||||||
|
if(ident is IdentifierReference)
|
||||||
|
arrayIndexedExpression.identifier = ident
|
||||||
arrayIndexedExpression.arrayspec.accept(this)
|
arrayIndexedExpression.arrayspec.accept(this)
|
||||||
return arrayIndexedExpression
|
return arrayIndexedExpression
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(assignTarget: AssignTarget): AssignTarget {
|
fun visit(assignTarget: AssignTarget): AssignTarget {
|
||||||
assignTarget.arrayindexed?.accept(this)
|
when (val ident = assignTarget.identifier?.accept(this)) {
|
||||||
assignTarget.identifier?.accept(this)
|
is IdentifierReference -> assignTarget.identifier = ident
|
||||||
|
null -> assignTarget.identifier = null
|
||||||
|
else -> throw FatalAstException("can't change class of assign target identifier")
|
||||||
|
}
|
||||||
|
assignTarget.arrayindexed = assignTarget.arrayindexed?.accept(this)
|
||||||
assignTarget.memoryAddress?.let { visit(it) }
|
assignTarget.memoryAddress?.let { visit(it) }
|
||||||
return assignTarget
|
return assignTarget
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(scope: AnonymousScope): IStatement {
|
fun visit(scope: AnonymousScope): Statement {
|
||||||
scope.statements = scope.statements.asSequence().map { it.accept(this) }.toMutableList()
|
scope.statements = scope.statements.map { it.accept(this) }.toMutableList()
|
||||||
return scope
|
return scope
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(typecast: TypecastExpression): IExpression {
|
fun visit(typecast: TypecastExpression): Expression {
|
||||||
typecast.expression = typecast.expression.accept(this)
|
typecast.expression = typecast.expression.accept(this)
|
||||||
return typecast
|
return typecast
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(memread: DirectMemoryRead): IExpression {
|
fun visit(memread: DirectMemoryRead): Expression {
|
||||||
memread.addressExpression = memread.addressExpression.accept(this)
|
memread.addressExpression = memread.addressExpression.accept(this)
|
||||||
return memread
|
return memread
|
||||||
}
|
}
|
||||||
@ -185,35 +207,55 @@ interface IAstModifyingVisitor {
|
|||||||
memwrite.addressExpression = memwrite.addressExpression.accept(this)
|
memwrite.addressExpression = memwrite.addressExpression.accept(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(addressOf: AddressOf): IExpression {
|
fun visit(addressOf: AddressOf): Expression {
|
||||||
addressOf.identifier.accept(this)
|
val ident = addressOf.identifier.accept(this)
|
||||||
|
if(ident is IdentifierReference)
|
||||||
|
addressOf.identifier = ident
|
||||||
|
else
|
||||||
|
throw FatalAstException("can't change class of addressof identifier")
|
||||||
return addressOf
|
return addressOf
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(inlineAssembly: InlineAssembly): IStatement {
|
fun visit(inlineAssembly: InlineAssembly): Statement {
|
||||||
return inlineAssembly
|
return inlineAssembly
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(registerExpr: RegisterExpr): IExpression {
|
fun visit(registerExpr: RegisterExpr): Expression {
|
||||||
return registerExpr
|
return registerExpr
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder): IStatement {
|
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder): Statement {
|
||||||
return builtinFunctionStatementPlaceholder
|
return builtinFunctionStatementPlaceholder
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(nopStatement: NopStatement): IStatement {
|
fun visit(nopStatement: NopStatement): Statement {
|
||||||
return nopStatement
|
return nopStatement
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(whenStatement: WhenStatement): IStatement {
|
fun visit(whenStatement: WhenStatement): Statement {
|
||||||
whenStatement.condition.accept(this)
|
whenStatement.condition = whenStatement.condition.accept(this)
|
||||||
whenStatement.choices.forEach { it.accept(this) }
|
whenStatement.choices.forEach { it.accept(this) }
|
||||||
return whenStatement
|
return whenStatement
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(whenChoice: WhenChoice) {
|
fun visit(whenChoice: WhenChoice) {
|
||||||
whenChoice.values?.forEach { it.accept(this) }
|
whenChoice.values = whenChoice.values?.map { it.accept(this) }
|
||||||
whenChoice.statements.accept(this)
|
val stmt = whenChoice.statements.accept(this)
|
||||||
|
if(stmt is AnonymousScope)
|
||||||
|
whenChoice.statements = stmt
|
||||||
|
else {
|
||||||
|
whenChoice.statements = AnonymousScope(mutableListOf(stmt), stmt.position)
|
||||||
|
whenChoice.statements.linkParents(whenChoice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(structDecl: StructDecl): Statement {
|
||||||
|
structDecl.statements = structDecl.statements.map{ it.accept(this) }.toMutableList()
|
||||||
|
return structDecl
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(structLv: StructLiteralValue): Expression {
|
||||||
|
structLv.values = structLv.values.map { it.accept(this) }
|
||||||
|
return structLv
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package prog8.ast.processing
|
package prog8.ast.processing
|
||||||
|
|
||||||
import prog8.ast.*
|
import prog8.ast.Module
|
||||||
|
import prog8.ast.Program
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
|
|
||||||
@ -75,8 +76,14 @@ interface IAstVisitor {
|
|||||||
fun visit(label: Label) {
|
fun visit(label: Label) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(literalValue: LiteralValue) {
|
fun visit(numLiteral: NumericLiteralValue) {
|
||||||
literalValue.arrayvalue?.let { it.forEach { v->v.accept(this) }}
|
}
|
||||||
|
|
||||||
|
fun visit(string: StringLiteralValue) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(array: ArrayLiteralValue) {
|
||||||
|
array.value.forEach { v->v.accept(this) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(assignment: Assignment) {
|
fun visit(assignment: Assignment) {
|
||||||
@ -166,4 +173,12 @@ interface IAstVisitor {
|
|||||||
whenChoice.values?.forEach { it.accept(this) }
|
whenChoice.values?.forEach { it.accept(this) }
|
||||||
whenChoice.statements.accept(this)
|
whenChoice.statements.accept(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun visit(structDecl: StructDecl) {
|
||||||
|
structDecl.statements.forEach { it.accept(this) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(structLv: StructLiteralValue) {
|
||||||
|
structLv.values.forEach { it.accept(this) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
package prog8.ast.processing
|
package prog8.ast.processing
|
||||||
|
|
||||||
import prog8.ast.*
|
import prog8.ast.Module
|
||||||
import prog8.ast.base.SyntaxError
|
import prog8.ast.base.SyntaxError
|
||||||
import prog8.ast.base.printWarning
|
import prog8.ast.base.printWarning
|
||||||
import prog8.ast.statements.Directive
|
import prog8.ast.statements.Directive
|
||||||
|
import prog8.ast.statements.Statement
|
||||||
|
|
||||||
internal class ImportedModuleDirectiveRemover : IAstModifyingVisitor {
|
internal class ImportedModuleDirectiveRemover : IAstModifyingVisitor {
|
||||||
private val checkResult: MutableList<SyntaxError> = mutableListOf()
|
private val checkResult: MutableList<SyntaxError> = mutableListOf()
|
||||||
@ -17,7 +18,7 @@ internal class ImportedModuleDirectiveRemover : IAstModifyingVisitor {
|
|||||||
*/
|
*/
|
||||||
override fun visit(module: Module) {
|
override fun visit(module: Module) {
|
||||||
super.visit(module)
|
super.visit(module)
|
||||||
val newStatements : MutableList<IStatement> = mutableListOf()
|
val newStatements : MutableList<Statement> = mutableListOf()
|
||||||
|
|
||||||
val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address")
|
val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address")
|
||||||
for (sourceStmt in module.statements) {
|
for (sourceStmt in module.statements) {
|
||||||
|
@ -1,14 +1,51 @@
|
|||||||
package prog8.ast.processing
|
package prog8.ast.processing
|
||||||
|
|
||||||
import kotlin.comparisons.nullsLast
|
|
||||||
import prog8.ast.*
|
import prog8.ast.*
|
||||||
import prog8.ast.base.DataType
|
import prog8.ast.base.DataType
|
||||||
import prog8.ast.base.FatalAstException
|
import prog8.ast.base.FatalAstException
|
||||||
import prog8.ast.base.initvarsSubName
|
import prog8.ast.base.initvarsSubName
|
||||||
import prog8.ast.base.printWarning
|
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.functions.BuiltinFunctions
|
|
||||||
|
|
||||||
|
private fun flattenStructAssignmentFromIdentifier(structAssignment: Assignment, program: Program): List<Assignment> {
|
||||||
|
val identifier = structAssignment.target.identifier!!
|
||||||
|
val identifierName = identifier.nameInSource.single()
|
||||||
|
val targetVar = identifier.targetVarDecl(program.namespace)!!
|
||||||
|
val struct = targetVar.struct!!
|
||||||
|
when {
|
||||||
|
structAssignment.value is IdentifierReference -> {
|
||||||
|
val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program.namespace)!!
|
||||||
|
if (sourceVar.struct == null)
|
||||||
|
throw FatalAstException("can only assign arrays or structs to structs")
|
||||||
|
// struct memberwise copy
|
||||||
|
val sourceStruct = sourceVar.struct!!
|
||||||
|
if(sourceStruct!==targetVar.struct) {
|
||||||
|
// structs are not the same in assignment
|
||||||
|
return listOf() // error will be printed elsewhere
|
||||||
|
}
|
||||||
|
return struct.statements.zip(sourceStruct.statements).map { member ->
|
||||||
|
val targetDecl = member.first as VarDecl
|
||||||
|
val sourceDecl = member.second as VarDecl
|
||||||
|
if(targetDecl.name != sourceDecl.name)
|
||||||
|
throw FatalAstException("struct member mismatch")
|
||||||
|
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
|
||||||
|
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
|
||||||
|
val sourcemangled = mangledStructMemberName(sourceVar.name, sourceDecl.name)
|
||||||
|
val sourceIdref = IdentifierReference(listOf(sourcemangled), structAssignment.position)
|
||||||
|
val assign = Assignment(AssignTarget(null, idref, null, null, structAssignment.position),
|
||||||
|
null, sourceIdref, member.second.position)
|
||||||
|
assign.linkParents(structAssignment)
|
||||||
|
assign
|
||||||
|
}
|
||||||
|
}
|
||||||
|
structAssignment.value is StructLiteralValue -> {
|
||||||
|
throw IllegalArgumentException("not going to flatten a structLv assignment here")
|
||||||
|
}
|
||||||
|
else -> throw FatalAstException("strange struct value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
internal class StatementReorderer(private val program: Program): IAstModifyingVisitor {
|
internal class StatementReorderer(private val program: Program): IAstModifyingVisitor {
|
||||||
// Reorders the statements in a way the compiler needs.
|
// Reorders the statements in a way the compiler needs.
|
||||||
@ -22,13 +59,13 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
|
|||||||
// - the 'start' subroutine in the 'main' block will be moved to the top immediately following the directives.
|
// - the 'start' subroutine in the 'main' block will be moved to the top immediately following the directives.
|
||||||
// - all other subroutines will be moved to the end of their block.
|
// - all other subroutines will be moved to the end of their block.
|
||||||
// - sorts the choices in when statement.
|
// - sorts the choices in when statement.
|
||||||
//
|
|
||||||
// Also, makes sure any value assignments get the proper type casts if needed to cast them into the target variable's type.
|
|
||||||
// (this includes function call arguments)
|
|
||||||
|
|
||||||
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option")
|
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option")
|
||||||
|
|
||||||
|
private val addReturns = mutableListOf<Pair<INameScope, Int>>()
|
||||||
|
|
||||||
override fun visit(module: Module) {
|
override fun visit(module: Module) {
|
||||||
|
addReturns.clear()
|
||||||
super.visit(module)
|
super.visit(module)
|
||||||
|
|
||||||
val (blocks, other) = module.statements.partition { it is Block }
|
val (blocks, other) = module.statements.partition { it is Block }
|
||||||
@ -56,9 +93,16 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
|
|||||||
val directives = module.statements.filter {it is Directive && it.directive in directivesToMove}
|
val directives = module.statements.filter {it is Directive && it.directive in directivesToMove}
|
||||||
module.statements.removeAll(directives)
|
module.statements.removeAll(directives)
|
||||||
module.statements.addAll(0, directives)
|
module.statements.addAll(0, directives)
|
||||||
|
|
||||||
|
for(pos in addReturns) {
|
||||||
|
println(pos)
|
||||||
|
val returnStmt = Return(null, pos.first.position)
|
||||||
|
returnStmt.linkParents(pos.first as Node)
|
||||||
|
pos.first.statements.add(pos.second, returnStmt)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(block: Block): IStatement {
|
override fun visit(block: Block): Statement {
|
||||||
|
|
||||||
val subroutines = block.statements.filterIsInstance<Subroutine>()
|
val subroutines = block.statements.filterIsInstance<Subroutine>()
|
||||||
var numSubroutinesAtEnd = 0
|
var numSubroutinesAtEnd = 0
|
||||||
@ -122,9 +166,22 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
|
|||||||
return super.visit(block)
|
return super.visit(block)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(subroutine: Subroutine): IStatement {
|
override fun visit(subroutine: Subroutine): Statement {
|
||||||
super.visit(subroutine)
|
super.visit(subroutine)
|
||||||
|
|
||||||
|
val scope = subroutine.definingScope()
|
||||||
|
if(scope is Subroutine) {
|
||||||
|
for(stmt in scope.statements.withIndex()) {
|
||||||
|
if(stmt.index>0 && stmt.value===subroutine) {
|
||||||
|
val precedingStmt = scope.statements[stmt.index-1]
|
||||||
|
if(precedingStmt !is Jump && precedingStmt !is Subroutine) {
|
||||||
|
// insert a return statement before a nested subroutine, to avoid falling trough inside the subroutine
|
||||||
|
addReturns.add(Pair(scope, stmt.index))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val varDecls = subroutine.statements.filterIsInstance<VarDecl>()
|
val varDecls = subroutine.statements.filterIsInstance<VarDecl>()
|
||||||
subroutine.statements.removeAll(varDecls)
|
subroutine.statements.removeAll(varDecls)
|
||||||
subroutine.statements.addAll(0, varDecls)
|
subroutine.statements.addAll(0, varDecls)
|
||||||
@ -147,176 +204,55 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
|
|||||||
return subroutine
|
return subroutine
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(expr: BinaryExpression): IExpression {
|
override fun visit(assignment: Assignment): Statement {
|
||||||
val leftDt = expr.left.inferType(program)
|
val assg = super.visit(assignment)
|
||||||
val rightDt = expr.right.inferType(program)
|
if(assg !is Assignment)
|
||||||
if(leftDt!=null && rightDt!=null && leftDt!=rightDt) {
|
return assg
|
||||||
// determine common datatype and add typecast as required to make left and right equal types
|
|
||||||
val (commonDt, toFix) = expr.commonDatatype(leftDt, rightDt, expr.left, expr.right)
|
|
||||||
if(toFix!=null) {
|
|
||||||
when {
|
|
||||||
toFix===expr.left -> {
|
|
||||||
expr.left = TypecastExpression(expr.left, commonDt, true, expr.left.position)
|
|
||||||
expr.left.linkParents(expr)
|
|
||||||
}
|
|
||||||
toFix===expr.right -> {
|
|
||||||
expr.right = TypecastExpression(expr.right, commonDt, true, expr.right.position)
|
|
||||||
expr.right.linkParents(expr)
|
|
||||||
}
|
|
||||||
else -> throw FatalAstException("confused binary expression side")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.visit(expr)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(assignment: Assignment): IStatement {
|
|
||||||
// see if a typecast is needed to convert the value's type into the proper target type
|
// see if a typecast is needed to convert the value's type into the proper target type
|
||||||
val valuetype = assignment.value.inferType(program)
|
val valueItype = assg.value.inferType(program)
|
||||||
val targettype = assignment.target.inferType(program, assignment)
|
val targetItype = assg.target.inferType(program, assg)
|
||||||
if(targettype!=null && valuetype!=null && valuetype!=targettype) {
|
|
||||||
if(valuetype isAssignableTo targettype) {
|
if(targetItype.isKnown && valueItype.isKnown) {
|
||||||
assignment.value = TypecastExpression(assignment.value, targettype, true, assignment.value.position)
|
val targettype = targetItype.typeOrElse(DataType.STRUCT)
|
||||||
assignment.value.linkParents(assignment)
|
val valuetype = valueItype.typeOrElse(DataType.STRUCT)
|
||||||
|
|
||||||
|
// struct assignments will be flattened (if it's not a struct literal)
|
||||||
|
if (valuetype == DataType.STRUCT && targettype == DataType.STRUCT) {
|
||||||
|
if (assg.value is StructLiteralValue)
|
||||||
|
return assg // do NOT flatten it at this point!! (the compiler will take care if it, later, if needed)
|
||||||
|
|
||||||
|
val assignments = flattenStructAssignmentFromIdentifier(assg, program) // 'structvar1 = structvar2'
|
||||||
|
return if (assignments.isEmpty()) {
|
||||||
|
// something went wrong (probably incompatible struct types)
|
||||||
|
// we'll get an error later from the AstChecker
|
||||||
|
assg
|
||||||
|
} else {
|
||||||
|
val scope = AnonymousScope(assignments.toMutableList(), assg.position)
|
||||||
|
scope.linkParents(assg.parent)
|
||||||
|
scope
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// if they're not assignable, we'll get a proper error later from the AstChecker
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.visit(assignment)
|
if(assg.aug_op!=null) {
|
||||||
}
|
// transform augmented assg into normal assg so we have one case less to deal with later
|
||||||
|
val newTarget: Expression =
|
||||||
override fun visit(functionCallStatement: FunctionCallStatement): IStatement {
|
when {
|
||||||
checkFunctionCallArguments(functionCallStatement, functionCallStatement.definingScope())
|
assg.target.register != null -> RegisterExpr(assg.target.register!!, assg.target.position)
|
||||||
return super.visit(functionCallStatement)
|
assg.target.identifier != null -> assg.target.identifier!!
|
||||||
}
|
assg.target.arrayindexed != null -> assg.target.arrayindexed!!
|
||||||
|
assg.target.memoryAddress != null -> DirectMemoryRead(assg.target.memoryAddress!!.addressExpression, assg.value.position)
|
||||||
override fun visit(functionCall: FunctionCall): IExpression {
|
else -> throw FatalAstException("strange assg")
|
||||||
checkFunctionCallArguments(functionCall, functionCall.definingScope())
|
|
||||||
return super.visit(functionCall)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun checkFunctionCallArguments(call: IFunctionCall, scope: INameScope) {
|
|
||||||
// see if a typecast is needed to convert the arguments into the required parameter's type
|
|
||||||
when(val sub = call.target.targetStatement(scope)) {
|
|
||||||
is Subroutine -> {
|
|
||||||
for(arg in sub.parameters.zip(call.arglist.withIndex())) {
|
|
||||||
val argtype = arg.second.value.inferType(program)
|
|
||||||
if(argtype!=null) {
|
|
||||||
val requiredType = arg.first.type
|
|
||||||
if (requiredType != argtype) {
|
|
||||||
if (argtype isAssignableTo requiredType) {
|
|
||||||
val typecasted = TypecastExpression(arg.second.value, requiredType, true, arg.second.value.position)
|
|
||||||
typecasted.linkParents(arg.second.value.parent)
|
|
||||||
call.arglist[arg.second.index] = typecasted
|
|
||||||
}
|
|
||||||
// if they're not assignable, we'll get a proper error later from the AstChecker
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
is BuiltinFunctionStatementPlaceholder -> {
|
|
||||||
// if(sub.name in setOf("lsl", "lsr", "rol", "ror", "rol2", "ror2", "memset", "memcopy", "memsetw", "swap"))
|
|
||||||
val func = BuiltinFunctions.getValue(sub.name)
|
|
||||||
if(func.pure) {
|
|
||||||
// non-pure functions don't get automatic typecasts because sometimes they act directly on their parameters
|
|
||||||
for (arg in func.parameters.zip(call.arglist.withIndex())) {
|
|
||||||
val argtype = arg.second.value.inferType(program)
|
|
||||||
if (argtype != null) {
|
|
||||||
if (arg.first.possibleDatatypes.any { argtype == it })
|
|
||||||
continue
|
|
||||||
for (possibleType in arg.first.possibleDatatypes) {
|
|
||||||
if (argtype isAssignableTo possibleType) {
|
|
||||||
val typecasted = TypecastExpression(arg.second.value, possibleType, true, arg.second.value.position)
|
|
||||||
typecasted.linkParents(arg.second.value.parent)
|
|
||||||
call.arglist[arg.second.index] = typecasted
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
null -> {}
|
|
||||||
else -> TODO("call to something weird $sub ${call.target}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun sortConstantAssignmentSequence(first: Assignment, stmtIter: MutableIterator<IStatement>): Pair<List<Assignment>, IStatement?> {
|
val expression = BinaryExpression(newTarget, assg.aug_op.substringBeforeLast('='), assg.value, assg.position)
|
||||||
val sequence= mutableListOf(first)
|
expression.linkParents(assg.parent)
|
||||||
var trailing: IStatement? = null
|
val convertedAssignment = Assignment(assg.target, null, expression, assg.position)
|
||||||
while(stmtIter.hasNext()) {
|
convertedAssignment.linkParents(assg.parent)
|
||||||
val next = stmtIter.next()
|
return super.visit(convertedAssignment)
|
||||||
if(next is Assignment) {
|
|
||||||
val constValue = next.value.constValue(program)
|
|
||||||
if(constValue==null) {
|
|
||||||
trailing = next
|
|
||||||
break
|
|
||||||
}
|
|
||||||
sequence.add(next)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
trailing=next
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val sorted = sequence.sortedWith(compareBy({it.value.inferType(program)}, {it.target.shortString(true)}))
|
|
||||||
return Pair(sorted, trailing)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(typecast: TypecastExpression): IExpression {
|
|
||||||
// warn about any implicit type casts to Float, because that may not be intended
|
|
||||||
if(typecast.implicit && typecast.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
|
|
||||||
printWarning("byte or word value implicitly converted to float. Suggestion: use explicit cast as float, a float number, or revert to integer arithmetic", typecast.position)
|
|
||||||
}
|
|
||||||
return super.visit(typecast)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(whenStatement: WhenStatement): IStatement {
|
|
||||||
// make sure all choices are just for one single value
|
|
||||||
val choices = whenStatement.choices.toList()
|
|
||||||
for(choice in choices) {
|
|
||||||
if(choice.values==null || choice.values.size==1)
|
|
||||||
continue
|
|
||||||
for(v in choice.values) {
|
|
||||||
val newchoice=WhenChoice(listOf(v), choice.statements, choice.position)
|
|
||||||
newchoice.parent = choice.parent
|
|
||||||
whenStatement.choices.add(newchoice)
|
|
||||||
}
|
|
||||||
whenStatement.choices.remove(choice)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort the choices in low-to-high value order (nulls last)
|
return assg
|
||||||
whenStatement.choices
|
|
||||||
.sortWith(compareBy<WhenChoice, Int?>(nullsLast(), {it.values?.single()?.constValue(program)?.asIntegerValue}))
|
|
||||||
return super.visit(whenStatement)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(memread: DirectMemoryRead): IExpression {
|
|
||||||
// make sure the memory address is an uword
|
|
||||||
val dt = memread.addressExpression.inferType(program)
|
|
||||||
if(dt!=DataType.UWORD) {
|
|
||||||
val literaladdr = memread.addressExpression as? LiteralValue
|
|
||||||
if(literaladdr!=null) {
|
|
||||||
memread.addressExpression = literaladdr.cast(DataType.UWORD)!!
|
|
||||||
} else {
|
|
||||||
memread.addressExpression = TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
|
|
||||||
memread.addressExpression.parent = memread
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.visit(memread)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(memwrite: DirectMemoryWrite) {
|
|
||||||
val dt = memwrite.addressExpression.inferType(program)
|
|
||||||
if(dt!=DataType.UWORD) {
|
|
||||||
val literaladdr = memwrite.addressExpression as? LiteralValue
|
|
||||||
if(literaladdr!=null) {
|
|
||||||
memwrite.addressExpression = literaladdr.cast(DataType.UWORD)!!
|
|
||||||
} else {
|
|
||||||
memwrite.addressExpression = TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
|
|
||||||
memwrite.addressExpression.parent = memwrite
|
|
||||||
}
|
|
||||||
}
|
|
||||||
super.visit(memwrite)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
198
compiler/src/prog8/ast/processing/TypecastsAdder.kt
Normal file
198
compiler/src/prog8/ast/processing/TypecastsAdder.kt
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
package prog8.ast.processing
|
||||||
|
|
||||||
|
import prog8.ast.IFunctionCall
|
||||||
|
import prog8.ast.INameScope
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.DataType
|
||||||
|
import prog8.ast.base.FatalAstException
|
||||||
|
import prog8.ast.base.printWarning
|
||||||
|
import prog8.ast.expressions.*
|
||||||
|
import prog8.ast.statements.*
|
||||||
|
import prog8.functions.BuiltinFunctions
|
||||||
|
|
||||||
|
|
||||||
|
internal class TypecastsAdder(private val program: Program): IAstModifyingVisitor {
|
||||||
|
// Make sure any value assignments get the proper type casts if needed to cast them into the target variable's type.
|
||||||
|
// (this includes function call arguments)
|
||||||
|
|
||||||
|
override fun visit(expr: BinaryExpression): Expression {
|
||||||
|
val expr2 = super.visit(expr)
|
||||||
|
if(expr2 !is BinaryExpression)
|
||||||
|
return expr2
|
||||||
|
val leftDt = expr2.left.inferType(program)
|
||||||
|
val rightDt = expr2.right.inferType(program)
|
||||||
|
if(leftDt.isKnown && rightDt.isKnown && leftDt!=rightDt) {
|
||||||
|
// determine common datatype and add typecast as required to make left and right equal types
|
||||||
|
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.typeOrElse(DataType.STRUCT), rightDt.typeOrElse(DataType.STRUCT), expr2.left, expr2.right)
|
||||||
|
if(toFix!=null) {
|
||||||
|
when {
|
||||||
|
toFix===expr2.left -> {
|
||||||
|
expr2.left = TypecastExpression(expr2.left, commonDt, true, expr2.left.position)
|
||||||
|
expr2.left.linkParents(expr2)
|
||||||
|
}
|
||||||
|
toFix===expr2.right -> {
|
||||||
|
expr2.right = TypecastExpression(expr2.right, commonDt, true, expr2.right.position)
|
||||||
|
expr2.right.linkParents(expr2)
|
||||||
|
}
|
||||||
|
else -> throw FatalAstException("confused binary expression side")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return expr2
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(assignment: Assignment): Statement {
|
||||||
|
val assg = super.visit(assignment)
|
||||||
|
if(assg !is Assignment)
|
||||||
|
return assg
|
||||||
|
|
||||||
|
// see if a typecast is needed to convert the value's type into the proper target type
|
||||||
|
val valueItype = assg.value.inferType(program)
|
||||||
|
val targetItype = assg.target.inferType(program, assg)
|
||||||
|
|
||||||
|
if(targetItype.isKnown && valueItype.isKnown) {
|
||||||
|
val targettype = targetItype.typeOrElse(DataType.STRUCT)
|
||||||
|
val valuetype = valueItype.typeOrElse(DataType.STRUCT)
|
||||||
|
if (valuetype != targettype) {
|
||||||
|
if (valuetype isAssignableTo targettype) {
|
||||||
|
assg.value = TypecastExpression(assg.value, targettype, true, assg.value.position)
|
||||||
|
assg.value.linkParents(assg)
|
||||||
|
}
|
||||||
|
// if they're not assignable, we'll get a proper error later from the AstChecker
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return assg
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
|
||||||
|
checkFunctionCallArguments(functionCallStatement, functionCallStatement.definingScope())
|
||||||
|
return super.visit(functionCallStatement)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(functionCall: FunctionCall): Expression {
|
||||||
|
checkFunctionCallArguments(functionCall, functionCall.definingScope())
|
||||||
|
return super.visit(functionCall)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkFunctionCallArguments(call: IFunctionCall, scope: INameScope) {
|
||||||
|
// see if a typecast is needed to convert the arguments into the required parameter's type
|
||||||
|
when(val sub = call.target.targetStatement(scope)) {
|
||||||
|
is Subroutine -> {
|
||||||
|
for(arg in sub.parameters.zip(call.arglist.withIndex())) {
|
||||||
|
val argItype = arg.second.value.inferType(program)
|
||||||
|
if(argItype.isKnown) {
|
||||||
|
val argtype = argItype.typeOrElse(DataType.STRUCT)
|
||||||
|
val requiredType = arg.first.type
|
||||||
|
if (requiredType != argtype) {
|
||||||
|
if (argtype isAssignableTo requiredType) {
|
||||||
|
val typecasted = TypecastExpression(arg.second.value, requiredType, true, arg.second.value.position)
|
||||||
|
typecasted.linkParents(arg.second.value.parent)
|
||||||
|
call.arglist[arg.second.index] = typecasted
|
||||||
|
}
|
||||||
|
// if they're not assignable, we'll get a proper error later from the AstChecker
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is BuiltinFunctionStatementPlaceholder -> {
|
||||||
|
val func = BuiltinFunctions.getValue(sub.name)
|
||||||
|
if(func.pure) {
|
||||||
|
// non-pure functions don't get automatic typecasts because sometimes they act directly on their parameters
|
||||||
|
for (arg in func.parameters.zip(call.arglist.withIndex())) {
|
||||||
|
val argItype = arg.second.value.inferType(program)
|
||||||
|
if (argItype.isKnown) {
|
||||||
|
val argtype = argItype.typeOrElse(DataType.STRUCT)
|
||||||
|
if (arg.first.possibleDatatypes.any { argtype == it })
|
||||||
|
continue
|
||||||
|
for (possibleType in arg.first.possibleDatatypes) {
|
||||||
|
if (argtype isAssignableTo possibleType) {
|
||||||
|
val typecasted = TypecastExpression(arg.second.value, possibleType, true, arg.second.value.position)
|
||||||
|
typecasted.linkParents(arg.second.value.parent)
|
||||||
|
call.arglist[arg.second.index] = typecasted
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
null -> {}
|
||||||
|
else -> throw FatalAstException("call to something weird $sub ${call.target}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(typecast: TypecastExpression): Expression {
|
||||||
|
// warn about any implicit type casts to Float, because that may not be intended
|
||||||
|
if(typecast.implicit && typecast.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
|
||||||
|
printWarning("byte or word value implicitly converted to float. Suggestion: use explicit cast as float, a float number, or revert to integer arithmetic", typecast.position)
|
||||||
|
}
|
||||||
|
return super.visit(typecast)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(memread: DirectMemoryRead): Expression {
|
||||||
|
// make sure the memory address is an uword
|
||||||
|
val dt = memread.addressExpression.inferType(program)
|
||||||
|
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
|
||||||
|
val literaladdr = memread.addressExpression as? NumericLiteralValue
|
||||||
|
if(literaladdr!=null) {
|
||||||
|
memread.addressExpression = literaladdr.cast(DataType.UWORD)
|
||||||
|
} else {
|
||||||
|
memread.addressExpression = TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
|
||||||
|
memread.addressExpression.parent = memread
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.visit(memread)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(memwrite: DirectMemoryWrite) {
|
||||||
|
val dt = memwrite.addressExpression.inferType(program)
|
||||||
|
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
|
||||||
|
val literaladdr = memwrite.addressExpression as? NumericLiteralValue
|
||||||
|
if(literaladdr!=null) {
|
||||||
|
memwrite.addressExpression = literaladdr.cast(DataType.UWORD)
|
||||||
|
} else {
|
||||||
|
memwrite.addressExpression = TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
|
||||||
|
memwrite.addressExpression.parent = memwrite
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.visit(memwrite)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(structLv: StructLiteralValue): Expression {
|
||||||
|
val litval = super.visit(structLv)
|
||||||
|
if(litval !is StructLiteralValue)
|
||||||
|
return litval
|
||||||
|
|
||||||
|
val decl = litval.parent as? VarDecl
|
||||||
|
if(decl != null) {
|
||||||
|
val struct = decl.struct
|
||||||
|
if(struct != null) {
|
||||||
|
addTypecastsIfNeeded(litval, struct)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val assign = litval.parent as? Assignment
|
||||||
|
if (assign != null) {
|
||||||
|
val decl2 = assign.target.identifier?.targetVarDecl(program.namespace)
|
||||||
|
if(decl2 != null) {
|
||||||
|
val struct = decl2.struct
|
||||||
|
if(struct != null) {
|
||||||
|
addTypecastsIfNeeded(litval, struct)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return litval
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addTypecastsIfNeeded(structLv: StructLiteralValue, struct: StructDecl) {
|
||||||
|
structLv.values = struct.statements.zip(structLv.values).map {
|
||||||
|
val memberDt = (it.first as VarDecl).datatype
|
||||||
|
val valueDt = it.second.inferType(program)
|
||||||
|
if (valueDt.typeOrElse(memberDt) != memberDt)
|
||||||
|
TypecastExpression(it.second, memberDt, true, it.second.position)
|
||||||
|
else
|
||||||
|
it.second
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +1,18 @@
|
|||||||
package prog8.ast.processing
|
package prog8.ast.processing
|
||||||
|
|
||||||
import prog8.ast.*
|
import prog8.ast.INameScope
|
||||||
|
import prog8.ast.Module
|
||||||
|
import prog8.ast.Node
|
||||||
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.base.autoHeapValuePrefix
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.expressions.AddressOf
|
|
||||||
import prog8.ast.expressions.FunctionCall
|
|
||||||
import prog8.ast.expressions.IdentifierReference
|
|
||||||
import prog8.ast.expressions.LiteralValue
|
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
|
import prog8.compiler.CompilerException
|
||||||
|
import prog8.functions.BuiltinFunctions
|
||||||
|
import prog8.functions.FunctionSignature
|
||||||
|
|
||||||
internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope): IAstModifyingVisitor {
|
|
||||||
|
internal class VarInitValueAndAddressOfCreator(private val program: Program): IAstModifyingVisitor {
|
||||||
// For VarDecls that declare an initialization value:
|
// For VarDecls that declare an initialization value:
|
||||||
// Replace the vardecl with an assignment (to set the initial value),
|
// Replace the vardecl with an assignment (to set the initial value),
|
||||||
// and add a new vardecl with the default constant value of that type (usually zero) to the scope.
|
// and add a new vardecl with the default constant value of that type (usually zero) to the scope.
|
||||||
@ -21,22 +24,31 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope
|
|||||||
|
|
||||||
// Also takes care to insert AddressOf (&) expression where required (string params to a UWORD function param etc).
|
// Also takes care to insert AddressOf (&) expression where required (string params to a UWORD function param etc).
|
||||||
|
|
||||||
private val vardeclsToAdd = mutableMapOf<INameScope, MutableMap<String, VarDecl>>()
|
private val vardeclsToAdd = mutableMapOf<INameScope, MutableList<VarDecl>>()
|
||||||
|
|
||||||
override fun visit(module: Module) {
|
override fun visit(module: Module) {
|
||||||
vardeclsToAdd.clear()
|
vardeclsToAdd.clear()
|
||||||
super.visit(module)
|
super.visit(module)
|
||||||
|
|
||||||
// add any new vardecls to the various scopes
|
// add any new vardecls to the various scopes
|
||||||
for(decl in vardeclsToAdd)
|
for((where, decls) in vardeclsToAdd) {
|
||||||
for(d in decl.value) {
|
where.statements.addAll(0, decls)
|
||||||
d.value.linkParents(decl.key as Node)
|
decls.forEach { it.linkParents(where as Node) }
|
||||||
decl.key.statements.add(0, d.value)
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(decl: VarDecl): IStatement {
|
override fun visit(decl: VarDecl): Statement {
|
||||||
super.visit(decl)
|
super.visit(decl)
|
||||||
|
|
||||||
|
if(decl.isArray && decl.value==null) {
|
||||||
|
// array datatype without initialization value, add list of zeros
|
||||||
|
val arraysize = decl.arraysize!!.size()!!
|
||||||
|
val array = ArrayLiteralValue(decl.datatype,
|
||||||
|
Array(arraysize) { NumericLiteralValue.optimalInteger(0, decl.position) },
|
||||||
|
null, decl.position)
|
||||||
|
array.addToHeap()
|
||||||
|
decl.value = array
|
||||||
|
}
|
||||||
|
|
||||||
if(decl.type!= VarDeclType.VAR || decl.value==null)
|
if(decl.type!= VarDeclType.VAR || decl.value==null)
|
||||||
return decl
|
return decl
|
||||||
|
|
||||||
@ -45,13 +57,11 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope
|
|||||||
addVarDecl(scope, decl.asDefaultValueDecl(null))
|
addVarDecl(scope, decl.asDefaultValueDecl(null))
|
||||||
val declvalue = decl.value!!
|
val declvalue = decl.value!!
|
||||||
val value =
|
val value =
|
||||||
if(declvalue is LiteralValue) {
|
if(declvalue is NumericLiteralValue)
|
||||||
val converted = declvalue.cast(decl.datatype)
|
declvalue.cast(decl.datatype)
|
||||||
converted ?: declvalue
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
declvalue
|
declvalue
|
||||||
val identifierName = listOf(decl.name) // // TODO this was: (scoped name) decl.scopedname.split(".")
|
val identifierName = listOf(decl.name) // this was: (scoped name) decl.scopedname.split(".")
|
||||||
return VariableInitializationAssignment(
|
return VariableInitializationAssignment(
|
||||||
AssignTarget(null, IdentifierReference(identifierName, decl.position), null, null, decl.position),
|
AssignTarget(null, IdentifierReference(identifierName, decl.position), null, null, decl.position),
|
||||||
null,
|
null,
|
||||||
@ -59,67 +69,89 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope
|
|||||||
decl.position
|
decl.position
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return decl
|
return decl
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(functionCall: FunctionCall): IExpression {
|
override fun visit(functionCall: FunctionCall): Expression {
|
||||||
val targetStatement = functionCall.target.targetSubroutine(namespace)
|
var parentStatement: Node = functionCall
|
||||||
|
while(parentStatement !is Statement)
|
||||||
|
parentStatement = parentStatement.parent
|
||||||
|
val targetStatement = functionCall.target.targetSubroutine(program.namespace)
|
||||||
if(targetStatement!=null) {
|
if(targetStatement!=null) {
|
||||||
var node: Node = functionCall
|
addAddressOfExprIfNeeded(targetStatement, functionCall.arglist, parentStatement)
|
||||||
while(node !is IStatement)
|
} else {
|
||||||
node=node.parent
|
val builtinFunc = BuiltinFunctions[functionCall.target.nameInSource.joinToString (".")]
|
||||||
addAddressOfExprIfNeeded(targetStatement, functionCall.arglist, node)
|
if(builtinFunc!=null)
|
||||||
|
addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCall.arglist, parentStatement)
|
||||||
}
|
}
|
||||||
return functionCall
|
return functionCall
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(functionCallStatement: FunctionCallStatement): IStatement {
|
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
|
||||||
val targetStatement = functionCallStatement.target.targetSubroutine(namespace)
|
val targetStatement = functionCallStatement.target.targetSubroutine(program.namespace)
|
||||||
if(targetStatement!=null)
|
if(targetStatement!=null) {
|
||||||
addAddressOfExprIfNeeded(targetStatement, functionCallStatement.arglist, functionCallStatement)
|
addAddressOfExprIfNeeded(targetStatement, functionCallStatement.arglist, functionCallStatement)
|
||||||
|
} else {
|
||||||
|
val builtinFunc = BuiltinFunctions[functionCallStatement.target.nameInSource.joinToString (".")]
|
||||||
|
if(builtinFunc!=null)
|
||||||
|
addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCallStatement.arglist, functionCallStatement)
|
||||||
|
}
|
||||||
return functionCallStatement
|
return functionCallStatement
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addAddressOfExprIfNeeded(subroutine: Subroutine, arglist: MutableList<IExpression>, parent: IStatement) {
|
private fun addAddressOfExprIfNeeded(subroutine: Subroutine, arglist: MutableList<Expression>, parent: Statement) {
|
||||||
// functions that accept UWORD and are given an array type, or string, will receive the AddressOf (memory location) of that value instead.
|
// functions that accept UWORD and are given an array type, or string, will receive the AddressOf (memory location) of that value instead.
|
||||||
for(argparam in subroutine.parameters.withIndex().zip(arglist)) {
|
for(argparam in subroutine.parameters.withIndex().zip(arglist)) {
|
||||||
if(argparam.first.value.type== DataType.UWORD || argparam.first.value.type in StringDatatypes) {
|
if(argparam.first.value.type==DataType.UWORD || argparam.first.value.type in StringDatatypes) {
|
||||||
if(argparam.second is AddressOf)
|
if(argparam.second is AddressOf)
|
||||||
continue
|
continue
|
||||||
val idref = argparam.second as? IdentifierReference
|
val idref = argparam.second as? IdentifierReference
|
||||||
val strvalue = argparam.second as? LiteralValue
|
val strvalue = argparam.second as? StringLiteralValue
|
||||||
if(idref!=null) {
|
if(idref!=null) {
|
||||||
val variable = idref.targetVarDecl(namespace)
|
val variable = idref.targetVarDecl(program.namespace)
|
||||||
if(variable!=null && (variable.datatype in StringDatatypes || variable.datatype in ArrayDatatypes)) {
|
if(variable!=null && (variable.datatype in StringDatatypes || variable.datatype in ArrayDatatypes)) {
|
||||||
val pointerExpr = AddressOf(idref, idref.position)
|
val pointerExpr = AddressOf(idref, idref.position)
|
||||||
pointerExpr.scopedname = parent.makeScopedName(idref.nameInSource.single())
|
|
||||||
pointerExpr.linkParents(arglist[argparam.first.index].parent)
|
pointerExpr.linkParents(arglist[argparam.first.index].parent)
|
||||||
arglist[argparam.first.index] = pointerExpr
|
arglist[argparam.first.index] = pointerExpr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(strvalue!=null) {
|
else if(strvalue!=null) {
|
||||||
if(strvalue.isString) {
|
// add a vardecl so that the autovar can be resolved in later lookups
|
||||||
// replace the argument with &autovar
|
val variable = VarDecl.createAuto(strvalue)
|
||||||
val autoVarName = "$autoHeapValuePrefix${strvalue.heapId}"
|
addVarDecl(strvalue.definingScope(), variable)
|
||||||
val autoHeapvarRef = IdentifierReference(listOf(autoVarName), strvalue.position)
|
// replace the argument with &autovar
|
||||||
val pointerExpr = AddressOf(autoHeapvarRef, strvalue.position)
|
val autoHeapvarRef = IdentifierReference(listOf(variable.name), strvalue.position)
|
||||||
pointerExpr.scopedname = parent.makeScopedName(autoVarName)
|
val pointerExpr = AddressOf(autoHeapvarRef, strvalue.position)
|
||||||
pointerExpr.linkParents(arglist[argparam.first.index].parent)
|
pointerExpr.linkParents(arglist[argparam.first.index].parent)
|
||||||
arglist[argparam.first.index] = pointerExpr
|
arglist[argparam.first.index] = pointerExpr
|
||||||
// add a vardecl so that the autovar can be resolved in later lookups
|
|
||||||
val variable = VarDecl(VarDeclType.VAR, strvalue.type, false, null, autoVarName, strvalue,
|
|
||||||
isArray = false, autoGenerated = false, position = strvalue.position)
|
|
||||||
addVarDecl(strvalue.definingScope(), variable)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun addAddressOfExprIfNeededForBuiltinFuncs(signature: FunctionSignature, args: MutableList<Expression>, parent: Statement) {
|
||||||
|
// val paramTypesForAddressOf = PassByReferenceDatatypes + DataType.UWORD
|
||||||
|
for(arg in args.withIndex().zip(signature.parameters)) {
|
||||||
|
val argvalue = arg.first.value
|
||||||
|
val argDt = argvalue.inferType(program)
|
||||||
|
if(argDt.typeOrElse(DataType.UBYTE) in PassByReferenceDatatypes && DataType.UWORD in arg.second.possibleDatatypes) {
|
||||||
|
if(argvalue !is IdentifierReference)
|
||||||
|
throw CompilerException("pass-by-reference parameter isn't an identifier? $argvalue")
|
||||||
|
val addrOf = AddressOf(argvalue, argvalue.position)
|
||||||
|
args[arg.first.index] = addrOf
|
||||||
|
addrOf.linkParents(parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun addVarDecl(scope: INameScope, variable: VarDecl) {
|
private fun addVarDecl(scope: INameScope, variable: VarDecl) {
|
||||||
if(scope !in vardeclsToAdd)
|
if(scope !in vardeclsToAdd)
|
||||||
vardeclsToAdd[scope] = mutableMapOf()
|
vardeclsToAdd[scope] = mutableListOf()
|
||||||
vardeclsToAdd.getValue(scope)[variable.name]=variable
|
val declList = vardeclsToAdd.getValue(scope)
|
||||||
|
if(declList.all{it.name!=variable.name})
|
||||||
|
declList.add(variable)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -5,10 +5,39 @@ import prog8.ast.base.*
|
|||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.processing.IAstModifyingVisitor
|
import prog8.ast.processing.IAstModifyingVisitor
|
||||||
import prog8.ast.processing.IAstVisitor
|
import prog8.ast.processing.IAstVisitor
|
||||||
import prog8.compiler.HeapValues
|
|
||||||
|
|
||||||
|
|
||||||
class BuiltinFunctionStatementPlaceholder(val name: String, override val position: Position) : IStatement {
|
sealed class Statement : Node {
|
||||||
|
abstract fun accept(visitor: IAstModifyingVisitor) : Statement
|
||||||
|
abstract fun accept(visitor: IAstVisitor)
|
||||||
|
fun makeScopedName(name: String): String {
|
||||||
|
// easy way out is to always return the full scoped name.
|
||||||
|
// it would be nicer to find only the minimal prefixed scoped name, but that's too much hassle for now.
|
||||||
|
// and like this, we can cache the name even,
|
||||||
|
// like in a lazy property on the statement object itself (label, subroutine, vardecl)
|
||||||
|
val scope = mutableListOf<String>()
|
||||||
|
var statementScope = this.parent
|
||||||
|
while(statementScope !is ParentSentinel && statementScope !is Module) {
|
||||||
|
if(statementScope is INameScope) {
|
||||||
|
scope.add(0, statementScope.name)
|
||||||
|
}
|
||||||
|
statementScope = statementScope.parent
|
||||||
|
}
|
||||||
|
if(name.isNotEmpty())
|
||||||
|
scope.add(name)
|
||||||
|
return scope.joinToString(".")
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract val expensiveToInline: Boolean
|
||||||
|
|
||||||
|
fun definingBlock(): Block {
|
||||||
|
if(this is Block)
|
||||||
|
return this
|
||||||
|
return findParentNode<Block>(this)!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BuiltinFunctionStatementPlaceholder(val name: String, override val position: Position) : Statement() {
|
||||||
override var parent: Node = ParentSentinel
|
override var parent: Node = ParentSentinel
|
||||||
override fun linkParents(parent: Node) {}
|
override fun linkParents(parent: Node) {}
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||||
@ -17,15 +46,13 @@ class BuiltinFunctionStatementPlaceholder(val name: String, override val positio
|
|||||||
override val expensiveToInline = false
|
override val expensiveToInline = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusflag: Statusflag?, val stack: Boolean)
|
||||||
data class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusflag: Statusflag?, val stack: Boolean?)
|
|
||||||
|
|
||||||
|
|
||||||
class Block(override val name: String,
|
class Block(override val name: String,
|
||||||
val address: Int?,
|
val address: Int?,
|
||||||
override var statements: MutableList<IStatement>,
|
override var statements: MutableList<Statement>,
|
||||||
val isInLibrary: Boolean,
|
val isInLibrary: Boolean,
|
||||||
override val position: Position) : IStatement, INameScope {
|
override val position: Position) : Statement(), INameScope {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline
|
override val expensiveToInline
|
||||||
get() = statements.any { it.expensiveToInline }
|
get() = statements.any { it.expensiveToInline }
|
||||||
@ -45,7 +72,7 @@ class Block(override val name: String,
|
|||||||
fun options() = statements.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.map {it.name!!}.toSet()
|
fun options() = statements.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.map {it.name!!}.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Directive(val directive: String, val args: List<DirectiveArg>, override val position: Position) : IStatement {
|
data class Directive(val directive: String, val args: List<DirectiveArg>, override val position: Position) : Statement() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline = false
|
override val expensiveToInline = false
|
||||||
|
|
||||||
@ -66,7 +93,7 @@ data class DirectiveArg(val str: String?, val name: String?, val int: Int?, over
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Label(val name: String, override val position: Position) : IStatement {
|
data class Label(val name: String, override val position: Position) : Statement() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline = false
|
override val expensiveToInline = false
|
||||||
|
|
||||||
@ -80,13 +107,11 @@ data class Label(val name: String, override val position: Position) : IStatement
|
|||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "Label(name=$name, pos=$position)"
|
return "Label(name=$name, pos=$position)"
|
||||||
}
|
}
|
||||||
|
|
||||||
val scopedname: String by lazy { makeScopedName(name) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
open class Return(var value: IExpression?, override val position: Position) : IStatement {
|
open class Return(var value: Expression?, override val position: Position) : Statement() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline = value!=null && value !is LiteralValue
|
override val expensiveToInline = value!=null && value !is NumericLiteralValue
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
@ -110,7 +135,7 @@ class ReturnFromIrq(override val position: Position) : Return(null, position) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Continue(override val position: Position) : IStatement {
|
class Continue(override val position: Position) : Statement() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline = false
|
override val expensiveToInline = false
|
||||||
|
|
||||||
@ -122,7 +147,7 @@ class Continue(override val position: Position) : IStatement {
|
|||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
class Break(override val position: Position) : IStatement {
|
class Break(override val position: Position) : Statement() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline = false
|
override val expensiveToInline = false
|
||||||
|
|
||||||
@ -134,18 +159,57 @@ class Break(override val position: Position) : IStatement {
|
|||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enum class ZeropageWish {
|
||||||
|
REQUIRE_ZEROPAGE,
|
||||||
|
PREFER_ZEROPAGE,
|
||||||
|
DONTCARE,
|
||||||
|
NOT_IN_ZEROPAGE
|
||||||
|
}
|
||||||
|
|
||||||
class VarDecl(val type: VarDeclType,
|
class VarDecl(val type: VarDeclType,
|
||||||
private val declaredDatatype: DataType,
|
private val declaredDatatype: DataType,
|
||||||
val zeropage: Boolean,
|
val zeropage: ZeropageWish,
|
||||||
var arraysize: ArrayIndex?,
|
var arraysize: ArrayIndex?,
|
||||||
val name: String,
|
val name: String,
|
||||||
var value: IExpression?,
|
private val structName: String?,
|
||||||
|
var value: Expression?,
|
||||||
val isArray: Boolean,
|
val isArray: Boolean,
|
||||||
val autoGenerated: Boolean,
|
val autogeneratedDontRemove: Boolean,
|
||||||
override val position: Position) : IStatement {
|
override val position: Position) : Statement() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
|
var struct: StructDecl? = null // set later (because at parse time, we only know the name)
|
||||||
|
private set
|
||||||
|
var structHasBeenFlattened = false // set later
|
||||||
|
private set
|
||||||
|
|
||||||
override val expensiveToInline
|
override val expensiveToInline
|
||||||
get() = value!=null && value !is LiteralValue
|
get() = value!=null && value !is NumericLiteralValue
|
||||||
|
|
||||||
|
// prefix for literal values that are turned into a variable on the heap
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private var autoHeapValueSequenceNumber = 0
|
||||||
|
|
||||||
|
fun createAuto(string: StringLiteralValue): VarDecl {
|
||||||
|
if(string.heapId==null)
|
||||||
|
throw FatalAstException("can only create autovar for a string that has a heapid $string")
|
||||||
|
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
|
||||||
|
return VarDecl(VarDeclType.VAR, string.type, ZeropageWish.NOT_IN_ZEROPAGE, null, autoVarName, null, string,
|
||||||
|
isArray = false, autogeneratedDontRemove = true, position = string.position)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createAuto(array: ArrayLiteralValue): VarDecl {
|
||||||
|
if(array.heapId==null)
|
||||||
|
throw FatalAstException("can only create autovar for an array that has a heapid $array")
|
||||||
|
|
||||||
|
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
|
||||||
|
val declaredType = ArrayElementTypes.getValue(array.type)
|
||||||
|
val arraysize = ArrayIndex.forArray(array)
|
||||||
|
return VarDecl(VarDeclType.VAR, declaredType, ZeropageWish.NOT_IN_ZEROPAGE, arraysize, autoVarName, null, array,
|
||||||
|
isArray = true, autogeneratedDontRemove = true, position = array.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val datatypeErrors = mutableListOf<SyntaxError>() // don't crash at init time, report them in the AstChecker
|
val datatypeErrors = mutableListOf<SyntaxError>() // don't crash at init time, report them in the AstChecker
|
||||||
val datatype =
|
val datatype =
|
||||||
@ -158,7 +222,7 @@ class VarDecl(val type: VarDeclType,
|
|||||||
DataType.FLOAT -> DataType.ARRAY_F
|
DataType.FLOAT -> DataType.ARRAY_F
|
||||||
else -> {
|
else -> {
|
||||||
datatypeErrors.add(SyntaxError("array can only contain bytes/words/floats", position))
|
datatypeErrors.add(SyntaxError("array can only contain bytes/words/floats", position))
|
||||||
DataType.UBYTE
|
DataType.ARRAY_UB
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,6 +230,11 @@ class VarDecl(val type: VarDeclType,
|
|||||||
this.parent = parent
|
this.parent = parent
|
||||||
arraysize?.linkParents(this)
|
arraysize?.linkParents(this)
|
||||||
value?.linkParents(this)
|
value?.linkParents(this)
|
||||||
|
if(structName!=null) {
|
||||||
|
val structStmt = definingScope().lookup(listOf(structName), this)
|
||||||
|
if(structStmt!=null)
|
||||||
|
struct = definingScope().lookup(listOf(structName), this) as StructDecl
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||||
@ -174,26 +243,47 @@ class VarDecl(val type: VarDeclType,
|
|||||||
val scopedname: String by lazy { makeScopedName(name) }
|
val scopedname: String by lazy { makeScopedName(name) }
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "VarDecl(name=$name, vartype=$type, datatype=$datatype, array=$isArray, value=$value, pos=$position)"
|
return "VarDecl(name=$name, vartype=$type, datatype=$datatype, struct=$structName, value=$value, pos=$position)"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun asDefaultValueDecl(parent: Node?): VarDecl {
|
fun asDefaultValueDecl(parent: Node?): VarDecl {
|
||||||
val constValue = when(declaredDatatype) {
|
val constValue = when(declaredDatatype) {
|
||||||
DataType.UBYTE -> LiteralValue(DataType.UBYTE, 0, position = position)
|
DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, 0, position)
|
||||||
DataType.BYTE -> LiteralValue(DataType.BYTE, 0, position = position)
|
DataType.BYTE -> NumericLiteralValue(DataType.BYTE, 0, position)
|
||||||
DataType.UWORD -> LiteralValue(DataType.UWORD, wordvalue = 0, position = position)
|
DataType.UWORD -> NumericLiteralValue(DataType.UWORD, 0, position)
|
||||||
DataType.WORD -> LiteralValue(DataType.WORD, wordvalue = 0, position = position)
|
DataType.WORD -> NumericLiteralValue(DataType.WORD, 0, position)
|
||||||
DataType.FLOAT -> LiteralValue(DataType.FLOAT, floatvalue = 0.0, position = position)
|
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, 0.0, position)
|
||||||
else -> throw FatalAstException("can only set a default value for a numeric type")
|
else -> throw FatalAstException("can only set a default value for a numeric type")
|
||||||
}
|
}
|
||||||
val decl = VarDecl(type, declaredDatatype, zeropage, arraysize, name, constValue, isArray, true, position)
|
val decl = VarDecl(type, declaredDatatype, zeropage, arraysize, name, structName, constValue, isArray, false, position)
|
||||||
if(parent!=null)
|
if(parent!=null)
|
||||||
decl.linkParents(parent)
|
decl.linkParents(parent)
|
||||||
return decl
|
return decl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun flattenStructMembers(): MutableList<Statement> {
|
||||||
|
val result = struct!!.statements.withIndex().map {
|
||||||
|
val member = it.value as VarDecl
|
||||||
|
val initvalue = if(value!=null) (value as StructLiteralValue).values[it.index] else null
|
||||||
|
VarDecl(
|
||||||
|
VarDeclType.VAR,
|
||||||
|
member.datatype,
|
||||||
|
ZeropageWish.NOT_IN_ZEROPAGE,
|
||||||
|
member.arraysize,
|
||||||
|
mangledStructMemberName(name, member.name),
|
||||||
|
struct!!.name,
|
||||||
|
initvalue,
|
||||||
|
member.isArray,
|
||||||
|
true,
|
||||||
|
member.position
|
||||||
|
) as Statement
|
||||||
|
}.toMutableList()
|
||||||
|
structHasBeenFlattened = true
|
||||||
|
return result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ArrayIndex(var index: IExpression, override val position: Position) : Node {
|
class ArrayIndex(var index: Expression, override val position: Position) : Node {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
@ -202,15 +292,15 @@ class ArrayIndex(var index: IExpression, override val position: Position) : Node
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun forArray(v: LiteralValue, heap: HeapValues): ArrayIndex {
|
fun forArray(v: ArrayLiteralValue): ArrayIndex {
|
||||||
val arraySize = v.arrayvalue?.size ?: heap.get(v.heapId!!).arraysize
|
return ArrayIndex(NumericLiteralValue.optimalNumeric(v.value.size, v.position), v.position)
|
||||||
return ArrayIndex(LiteralValue.optimalNumeric(arraySize, v.position), v.position)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun accept(visitor: IAstModifyingVisitor) {
|
fun accept(visitor: IAstModifyingVisitor) {
|
||||||
index = index.accept(visitor)
|
index = index.accept(visitor)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun accept(visitor: IAstVisitor) {
|
fun accept(visitor: IAstVisitor) {
|
||||||
index.accept(visitor)
|
index.accept(visitor)
|
||||||
}
|
}
|
||||||
@ -219,13 +309,13 @@ class ArrayIndex(var index: IExpression, override val position: Position) : Node
|
|||||||
return("ArrayIndex($index, pos=$position)")
|
return("ArrayIndex($index, pos=$position)")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun size() = (index as? LiteralValue)?.asIntegerValue
|
fun size() = (index as? NumericLiteralValue)?.number?.toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
open class Assignment(var target: AssignTarget, val aug_op : String?, var value: IExpression, override val position: Position) : IStatement {
|
open class Assignment(var target: AssignTarget, val aug_op : String?, var value: Expression, override val position: Position) : Statement() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline
|
override val expensiveToInline
|
||||||
get() = value !is LiteralValue
|
get() = value !is NumericLiteralValue
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
@ -243,13 +333,13 @@ open class Assignment(var target: AssignTarget, val aug_op : String?, var value:
|
|||||||
|
|
||||||
// This is a special class so the compiler can see if the assignments are for initializing the vars in the scope,
|
// This is a special class so the compiler can see if the assignments are for initializing the vars in the scope,
|
||||||
// or just a regular assignment. It may optimize the initialization step from this.
|
// or just a regular assignment. It may optimize the initialization step from this.
|
||||||
class VariableInitializationAssignment(target: AssignTarget, aug_op: String?, value: IExpression, position: Position)
|
class VariableInitializationAssignment(target: AssignTarget, aug_op: String?, value: Expression, position: Position)
|
||||||
: Assignment(target, aug_op, value, position)
|
: Assignment(target, aug_op, value, position)
|
||||||
|
|
||||||
data class AssignTarget(val register: Register?,
|
data class AssignTarget(val register: Register?,
|
||||||
val identifier: IdentifierReference?,
|
var identifier: IdentifierReference?,
|
||||||
val arrayindexed: ArrayIndexedExpression?,
|
var arrayindexed: ArrayIndexedExpression?,
|
||||||
var memoryAddress: DirectMemoryWrite?,
|
val memoryAddress: DirectMemoryWrite?,
|
||||||
override val position: Position) : Node {
|
override val position: Position) : Node {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
|
|
||||||
@ -264,7 +354,7 @@ data class AssignTarget(val register: Register?,
|
|||||||
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromExpr(expr: IExpression): AssignTarget {
|
fun fromExpr(expr: Expression): AssignTarget {
|
||||||
return when (expr) {
|
return when (expr) {
|
||||||
is RegisterExpr -> AssignTarget(expr.register, null, null, null, expr.position)
|
is RegisterExpr -> AssignTarget(expr.register, null, null, null, expr.position)
|
||||||
is IdentifierReference -> AssignTarget(null, expr, null, null, expr.position)
|
is IdentifierReference -> AssignTarget(null, expr, null, null, expr.position)
|
||||||
@ -275,53 +365,35 @@ data class AssignTarget(val register: Register?,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun inferType(program: Program, stmt: IStatement): DataType? {
|
fun inferType(program: Program, stmt: Statement): InferredTypes.InferredType {
|
||||||
if(register!=null)
|
if(register!=null)
|
||||||
return DataType.UBYTE
|
return InferredTypes.knownFor(DataType.UBYTE)
|
||||||
|
|
||||||
if(identifier!=null) {
|
if(identifier!=null) {
|
||||||
val symbol = program.namespace.lookup(identifier.nameInSource, stmt) ?: return null
|
val symbol = program.namespace.lookup(identifier!!.nameInSource, stmt) ?: return InferredTypes.unknown()
|
||||||
if (symbol is VarDecl) return symbol.datatype
|
if (symbol is VarDecl) return InferredTypes.knownFor(symbol.datatype)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(arrayindexed!=null) {
|
if(arrayindexed!=null) {
|
||||||
val dt = arrayindexed.inferType(program)
|
return arrayindexed!!.inferType(program)
|
||||||
if(dt!=null)
|
|
||||||
return dt
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(memoryAddress!=null)
|
if(memoryAddress!=null)
|
||||||
return DataType.UBYTE
|
return InferredTypes.knownFor(DataType.UBYTE)
|
||||||
|
|
||||||
return null
|
return InferredTypes.unknown()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun shortString(withTypePrefix: Boolean=false): String {
|
infix fun isSameAs(value: Expression): Boolean {
|
||||||
if(register!=null)
|
|
||||||
return (if(withTypePrefix) "0register::" else "") + register.name
|
|
||||||
if(identifier!=null)
|
|
||||||
return (if(withTypePrefix) "3identifier::" else "") + identifier.nameInSource.last()
|
|
||||||
if(arrayindexed!=null)
|
|
||||||
return (if(withTypePrefix) "2arrayidx::" else "") + arrayindexed.identifier.nameInSource.last()
|
|
||||||
val address = memoryAddress?.addressExpression
|
|
||||||
if(address is LiteralValue)
|
|
||||||
return (if(withTypePrefix) "1address::" else "") +address.asIntegerValue.toString()
|
|
||||||
return if(withTypePrefix) "???::???" else "???"
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isMemoryMapped(namespace: INameScope): Boolean =
|
|
||||||
memoryAddress!=null || (identifier?.targetVarDecl(namespace)?.type== VarDeclType.MEMORY)
|
|
||||||
|
|
||||||
infix fun isSameAs(value: IExpression): Boolean {
|
|
||||||
return when {
|
return when {
|
||||||
this.memoryAddress!=null -> false
|
this.memoryAddress!=null -> false
|
||||||
this.register!=null -> value is RegisterExpr && value.register==register
|
this.register!=null -> value is RegisterExpr && value.register==register
|
||||||
this.identifier!=null -> value is IdentifierReference && value.nameInSource==identifier.nameInSource
|
this.identifier!=null -> value is IdentifierReference && value.nameInSource==identifier!!.nameInSource
|
||||||
this.arrayindexed!=null -> value is ArrayIndexedExpression &&
|
this.arrayindexed!=null -> value is ArrayIndexedExpression &&
|
||||||
value.identifier.nameInSource==arrayindexed.identifier.nameInSource &&
|
value.identifier.nameInSource==arrayindexed!!.identifier.nameInSource &&
|
||||||
value.arrayspec.size()!=null &&
|
value.arrayspec.size()!=null &&
|
||||||
arrayindexed.arrayspec.size()!=null &&
|
arrayindexed!!.arrayspec.size()!=null &&
|
||||||
value.arrayspec.size()==arrayindexed.arrayspec.size()
|
value.arrayspec.size()==arrayindexed!!.arrayspec.size()
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -332,16 +404,16 @@ data class AssignTarget(val register: Register?,
|
|||||||
if(this.register!=null && other.register!=null)
|
if(this.register!=null && other.register!=null)
|
||||||
return this.register==other.register
|
return this.register==other.register
|
||||||
if(this.identifier!=null && other.identifier!=null)
|
if(this.identifier!=null && other.identifier!=null)
|
||||||
return this.identifier.nameInSource==other.identifier.nameInSource
|
return this.identifier!!.nameInSource==other.identifier!!.nameInSource
|
||||||
if(this.memoryAddress!=null && other.memoryAddress!=null) {
|
if(this.memoryAddress!=null && other.memoryAddress!=null) {
|
||||||
val addr1 = this.memoryAddress!!.addressExpression.constValue(program)
|
val addr1 = this.memoryAddress.addressExpression.constValue(program)
|
||||||
val addr2 = other.memoryAddress!!.addressExpression.constValue(program)
|
val addr2 = other.memoryAddress.addressExpression.constValue(program)
|
||||||
return addr1!=null && addr2!=null && addr1==addr2
|
return addr1!=null && addr2!=null && addr1==addr2
|
||||||
}
|
}
|
||||||
if(this.arrayindexed!=null && other.arrayindexed!=null) {
|
if(this.arrayindexed!=null && other.arrayindexed!=null) {
|
||||||
if(this.arrayindexed.identifier.nameInSource == other.arrayindexed.identifier.nameInSource) {
|
if(this.arrayindexed!!.identifier.nameInSource == other.arrayindexed!!.identifier.nameInSource) {
|
||||||
val x1 = this.arrayindexed.arrayspec.index.constValue(program)
|
val x1 = this.arrayindexed!!.arrayspec.index.constValue(program)
|
||||||
val x2 = other.arrayindexed.arrayspec.index.constValue(program)
|
val x2 = other.arrayindexed!!.arrayspec.index.constValue(program)
|
||||||
return x1!=null && x2!=null && x1==x2
|
return x1!=null && x2!=null && x1==x2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -354,12 +426,12 @@ data class AssignTarget(val register: Register?,
|
|||||||
if(this.memoryAddress!=null)
|
if(this.memoryAddress!=null)
|
||||||
return false
|
return false
|
||||||
if(this.arrayindexed!=null) {
|
if(this.arrayindexed!=null) {
|
||||||
val targetStmt = this.arrayindexed.identifier.targetVarDecl(namespace)
|
val targetStmt = this.arrayindexed!!.identifier.targetVarDecl(namespace)
|
||||||
if(targetStmt!=null)
|
if(targetStmt!=null)
|
||||||
return targetStmt.type!= VarDeclType.MEMORY
|
return targetStmt.type!= VarDeclType.MEMORY
|
||||||
}
|
}
|
||||||
if(this.identifier!=null) {
|
if(this.identifier!=null) {
|
||||||
val targetStmt = this.identifier.targetVarDecl(namespace)
|
val targetStmt = this.identifier!!.targetVarDecl(namespace)
|
||||||
if(targetStmt!=null)
|
if(targetStmt!=null)
|
||||||
return targetStmt.type!= VarDeclType.MEMORY
|
return targetStmt.type!= VarDeclType.MEMORY
|
||||||
}
|
}
|
||||||
@ -367,7 +439,7 @@ data class AssignTarget(val register: Register?,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PostIncrDecr(var target: AssignTarget, val operator: String, override val position: Position) : IStatement {
|
class PostIncrDecr(var target: AssignTarget, val operator: String, override val position: Position) : Statement() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline = false
|
override val expensiveToInline = false
|
||||||
|
|
||||||
@ -387,7 +459,7 @@ class PostIncrDecr(var target: AssignTarget, val operator: String, override val
|
|||||||
class Jump(val address: Int?,
|
class Jump(val address: Int?,
|
||||||
val identifier: IdentifierReference?,
|
val identifier: IdentifierReference?,
|
||||||
val generatedLabel: String?, // used in code generation scenarios
|
val generatedLabel: String?, // used in code generation scenarios
|
||||||
override val position: Position) : IStatement {
|
override val position: Position) : Statement() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline = false
|
override val expensiveToInline = false
|
||||||
|
|
||||||
@ -405,11 +477,11 @@ class Jump(val address: Int?,
|
|||||||
}
|
}
|
||||||
|
|
||||||
class FunctionCallStatement(override var target: IdentifierReference,
|
class FunctionCallStatement(override var target: IdentifierReference,
|
||||||
override var arglist: MutableList<IExpression>,
|
override var arglist: MutableList<Expression>,
|
||||||
override val position: Position) : IStatement, IFunctionCall {
|
override val position: Position) : Statement(), IFunctionCall {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline
|
override val expensiveToInline
|
||||||
get() = arglist.any { it !is LiteralValue }
|
get() = arglist.any { it !is NumericLiteralValue }
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
@ -425,7 +497,7 @@ class FunctionCallStatement(override var target: IdentifierReference,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InlineAssembly(val assembly: String, override val position: Position) : IStatement {
|
class InlineAssembly(val assembly: String, override val position: Position) : Statement() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline = true
|
override val expensiveToInline = true
|
||||||
|
|
||||||
@ -437,22 +509,22 @@ class InlineAssembly(val assembly: String, override val position: Position) : IS
|
|||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
class AnonymousScope(override var statements: MutableList<IStatement>,
|
class AnonymousScope(override var statements: MutableList<Statement>,
|
||||||
override val position: Position) : INameScope, IStatement {
|
override val position: Position) : INameScope, Statement() {
|
||||||
override val name: String
|
override val name: String
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline
|
override val expensiveToInline
|
||||||
get() = statements.any { it.expensiveToInline }
|
get() = statements.any { it.expensiveToInline }
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private var sequenceNumber = 1
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
name = "<anon-$sequenceNumber>" // make sure it's an invalid soruce code identifier so user source code can never produce it
|
name = "<anon-$sequenceNumber>" // make sure it's an invalid soruce code identifier so user source code can never produce it
|
||||||
sequenceNumber++
|
sequenceNumber++
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
private var sequenceNumber = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
statements.forEach { it.linkParents(this) }
|
statements.forEach { it.linkParents(this) }
|
||||||
@ -462,7 +534,7 @@ class AnonymousScope(override var statements: MutableList<IStatement>,
|
|||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
class NopStatement(override val position: Position): IStatement {
|
class NopStatement(override val position: Position): Statement() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline = false
|
override val expensiveToInline = false
|
||||||
|
|
||||||
@ -474,7 +546,7 @@ class NopStatement(override val position: Position): IStatement {
|
|||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun insteadOf(stmt: IStatement): NopStatement {
|
fun insteadOf(stmt: Statement): NopStatement {
|
||||||
val nop = NopStatement(stmt.position)
|
val nop = NopStatement(stmt.position)
|
||||||
nop.parent = stmt.parent
|
nop.parent = stmt.parent
|
||||||
return nop
|
return nop
|
||||||
@ -493,8 +565,8 @@ class Subroutine(override val name: String,
|
|||||||
val asmClobbers: Set<Register>,
|
val asmClobbers: Set<Register>,
|
||||||
val asmAddress: Int?,
|
val asmAddress: Int?,
|
||||||
val isAsmSubroutine: Boolean,
|
val isAsmSubroutine: Boolean,
|
||||||
override var statements: MutableList<IStatement>,
|
override var statements: MutableList<Statement>,
|
||||||
override val position: Position) : IStatement, INameScope {
|
override val position: Position) : Statement(), INameScope {
|
||||||
|
|
||||||
var keepAlways: Boolean = false
|
var keepAlways: Boolean = false
|
||||||
override val expensiveToInline
|
override val expensiveToInline
|
||||||
@ -524,40 +596,6 @@ class Subroutine(override val name: String,
|
|||||||
.filter { it is InlineAssembly }
|
.filter { it is InlineAssembly }
|
||||||
.map { (it as InlineAssembly).assembly }
|
.map { (it as InlineAssembly).assembly }
|
||||||
.count { " rti" in it || "\trti" in it || " rts" in it || "\trts" in it || " jmp" in it || "\tjmp" in it }
|
.count { " rti" in it || "\trti" in it || " rts" in it || "\trts" in it || " jmp" in it || "\tjmp" in it }
|
||||||
|
|
||||||
val canBeAsmSubroutine =false // TODO disabled for now, see below about problem with converting to asm subroutine
|
|
||||||
// !isAsmSubroutine
|
|
||||||
// && ((parameters.size == 1 && parameters[0].type in setOf(DataType.BYTE, DataType.UBYTE, DataType.WORD, DataType.UWORD))
|
|
||||||
// || (parameters.size == 2 && parameters.map { it.type }.all { it == DataType.BYTE || it == DataType.UBYTE }))
|
|
||||||
|
|
||||||
fun intoAsmSubroutine(): Subroutine {
|
|
||||||
// TODO turn subroutine into asm calling convention. Requires rethinking of how parameters are handled (conflicts with local vardefs now, see AstIdentifierChecker...)
|
|
||||||
return this // TODO
|
|
||||||
|
|
||||||
// println("TO ASM $this") // TODO
|
|
||||||
// val paramregs = if (parameters.size == 1 && parameters[0].type in setOf(DataType.BYTE, DataType.UBYTE))
|
|
||||||
// listOf(RegisterOrStatusflag(RegisterOrPair.Y, null, null))
|
|
||||||
// else if (parameters.size == 1 && parameters[0].type in setOf(DataType.WORD, DataType.UWORD))
|
|
||||||
// listOf(RegisterOrStatusflag(RegisterOrPair.AY, null, null))
|
|
||||||
// else if (parameters.size == 2 && parameters.map { it.type }.all { it == DataType.BYTE || it == DataType.UBYTE })
|
|
||||||
// listOf(RegisterOrStatusflag(RegisterOrPair.A, null, null), RegisterOrStatusflag(RegisterOrPair.Y, null, null))
|
|
||||||
// else throw FatalAstException("cannot convert subroutine to asm parameters")
|
|
||||||
//
|
|
||||||
// val asmsub=Subroutine(
|
|
||||||
// name,
|
|
||||||
// parameters,
|
|
||||||
// returntypes,
|
|
||||||
// paramregs,
|
|
||||||
// emptyList(),
|
|
||||||
// emptySet(),
|
|
||||||
// null,
|
|
||||||
// true,
|
|
||||||
// statements,
|
|
||||||
// position
|
|
||||||
// )
|
|
||||||
// asmsub.linkParents(parent)
|
|
||||||
// return asmsub
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
open class SubroutineParameter(val name: String,
|
open class SubroutineParameter(val name: String,
|
||||||
@ -570,10 +608,10 @@ open class SubroutineParameter(val name: String,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class IfStatement(var condition: IExpression,
|
class IfStatement(var condition: Expression,
|
||||||
var truepart: AnonymousScope,
|
var truepart: AnonymousScope,
|
||||||
var elsepart: AnonymousScope,
|
var elsepart: AnonymousScope,
|
||||||
override val position: Position) : IStatement {
|
override val position: Position) : Statement() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline: Boolean
|
override val expensiveToInline: Boolean
|
||||||
get() = truepart.expensiveToInline || elsepart.expensiveToInline
|
get() = truepart.expensiveToInline || elsepart.expensiveToInline
|
||||||
@ -592,7 +630,7 @@ class IfStatement(var condition: IExpression,
|
|||||||
class BranchStatement(var condition: BranchCondition,
|
class BranchStatement(var condition: BranchCondition,
|
||||||
var truepart: AnonymousScope,
|
var truepart: AnonymousScope,
|
||||||
var elsepart: AnonymousScope,
|
var elsepart: AnonymousScope,
|
||||||
override val position: Position) : IStatement {
|
override val position: Position) : Statement() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline: Boolean
|
override val expensiveToInline: Boolean
|
||||||
get() = truepart.expensiveToInline || elsepart.expensiveToInline
|
get() = truepart.expensiveToInline || elsepart.expensiveToInline
|
||||||
@ -608,18 +646,16 @@ class BranchStatement(var condition: BranchCondition,
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ForLoop(val loopRegister: Register?,
|
class ForLoop(val loopRegister: Register?,
|
||||||
val decltype: DataType?,
|
var loopVar: IdentifierReference?,
|
||||||
val zeropage: Boolean,
|
var iterable: Expression,
|
||||||
val loopVar: IdentifierReference?,
|
|
||||||
var iterable: IExpression,
|
|
||||||
var body: AnonymousScope,
|
var body: AnonymousScope,
|
||||||
override val position: Position) : IStatement {
|
override val position: Position) : Statement() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline = true
|
override val expensiveToInline = true
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent=parent
|
this.parent=parent
|
||||||
loopVar?.linkParents(if(decltype==null) this else body)
|
loopVar?.linkParents(this)
|
||||||
iterable.linkParents(this)
|
iterable.linkParents(this)
|
||||||
body.linkParents(this)
|
body.linkParents(this)
|
||||||
}
|
}
|
||||||
@ -630,15 +666,11 @@ class ForLoop(val loopRegister: Register?,
|
|||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "ForLoop(loopVar: $loopVar, loopReg: $loopRegister, iterable: $iterable, pos=$position)"
|
return "ForLoop(loopVar: $loopVar, loopReg: $loopRegister, iterable: $iterable, pos=$position)"
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val iteratorLoopcounterVarname = "prog8forloopcounter"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class WhileLoop(var condition: IExpression,
|
class WhileLoop(var condition: Expression,
|
||||||
var body: AnonymousScope,
|
var body: AnonymousScope,
|
||||||
override val position: Position) : IStatement {
|
override val position: Position) : Statement() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline = true
|
override val expensiveToInline = true
|
||||||
|
|
||||||
@ -653,8 +685,8 @@ class WhileLoop(var condition: IExpression,
|
|||||||
}
|
}
|
||||||
|
|
||||||
class RepeatLoop(var body: AnonymousScope,
|
class RepeatLoop(var body: AnonymousScope,
|
||||||
var untilCondition: IExpression,
|
var untilCondition: Expression,
|
||||||
override val position: Position) : IStatement {
|
override val position: Position) : Statement() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline = true
|
override val expensiveToInline = true
|
||||||
|
|
||||||
@ -668,9 +700,9 @@ class RepeatLoop(var body: AnonymousScope,
|
|||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
class WhenStatement(val condition: IExpression,
|
class WhenStatement(var condition: Expression,
|
||||||
val choices: MutableList<WhenChoice>,
|
var choices: MutableList<WhenChoice>,
|
||||||
override val position: Position): IStatement {
|
override val position: Position): Statement() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline: Boolean = true
|
override val expensiveToInline: Boolean = true
|
||||||
|
|
||||||
@ -687,7 +719,7 @@ class WhenStatement(val condition: IExpression,
|
|||||||
if(choice.values==null)
|
if(choice.values==null)
|
||||||
result.add(null to choice)
|
result.add(null to choice)
|
||||||
else {
|
else {
|
||||||
val values = choice.values.map { it.constValue(program)?.asNumericValue?.toInt() }
|
val values = choice.values!!.map { it.constValue(program)?.number?.toInt() }
|
||||||
if(values.contains(null))
|
if(values.contains(null))
|
||||||
result.add(null to choice)
|
result.add(null to choice)
|
||||||
else
|
else
|
||||||
@ -701,8 +733,8 @@ class WhenStatement(val condition: IExpression,
|
|||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
class WhenChoice(val values: List<IExpression>?, // if null, this is the 'else' part
|
class WhenChoice(var values: List<Expression>?, // if null, this is the 'else' part
|
||||||
val statements: AnonymousScope,
|
var statements: AnonymousScope,
|
||||||
override val position: Position) : Node {
|
override val position: Position) : Node {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
|
|
||||||
@ -721,7 +753,28 @@ class WhenChoice(val values: List<IExpression>?, // if null, this is
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class DirectMemoryWrite(var addressExpression: IExpression, override val position: Position) : Node {
|
class StructDecl(override val name: String,
|
||||||
|
override var statements: MutableList<Statement>, // actually, only vardecls here
|
||||||
|
override val position: Position): Statement(), INameScope {
|
||||||
|
|
||||||
|
override lateinit var parent: Node
|
||||||
|
override val expensiveToInline: Boolean = true
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
this.statements.forEach { it.linkParents(this) }
|
||||||
|
}
|
||||||
|
|
||||||
|
val numberOfElements: Int
|
||||||
|
get() = this.statements.size
|
||||||
|
|
||||||
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||||
|
|
||||||
|
fun nameOfFirstMember() = (statements.first() as VarDecl).name
|
||||||
|
}
|
||||||
|
|
||||||
|
class DirectMemoryWrite(var addressExpression: Expression, override val position: Position) : Node {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
@ -736,4 +789,3 @@ class DirectMemoryWrite(var addressExpression: IExpression, override val positio
|
|||||||
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,13 +1,11 @@
|
|||||||
package prog8.compiler
|
package prog8.compiler
|
||||||
|
|
||||||
|
import prog8.ast.AstToSourceCode
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.base.checkIdentifiers
|
|
||||||
import prog8.ast.base.checkValid
|
|
||||||
import prog8.ast.base.reorderStatements
|
|
||||||
import prog8.ast.statements.Directive
|
import prog8.ast.statements.Directive
|
||||||
import prog8.compiler.target.c64.AsmGen
|
import prog8.compiler.target.c64.MachineDefinition
|
||||||
import prog8.compiler.target.c64.C64Zeropage
|
import prog8.compiler.target.c64.codegen.AsmGen
|
||||||
import prog8.optimizer.constantFold
|
import prog8.optimizer.constantFold
|
||||||
import prog8.optimizer.optimizeStatements
|
import prog8.optimizer.optimizeStatements
|
||||||
import prog8.optimizer.simplifyExpressions
|
import prog8.optimizer.simplifyExpressions
|
||||||
@ -15,20 +13,26 @@ import prog8.parser.ParsingFailedError
|
|||||||
import prog8.parser.importLibraryModule
|
import prog8.parser.importLibraryModule
|
||||||
import prog8.parser.importModule
|
import prog8.parser.importModule
|
||||||
import prog8.parser.moduleName
|
import prog8.parser.moduleName
|
||||||
import java.io.File
|
|
||||||
import java.io.PrintStream
|
|
||||||
import java.lang.Exception
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.system.exitProcess
|
|
||||||
import kotlin.system.measureTimeMillis
|
import kotlin.system.measureTimeMillis
|
||||||
|
|
||||||
|
|
||||||
|
class CompilationResult(val success: Boolean,
|
||||||
|
val programAst: Program,
|
||||||
|
val programName: String,
|
||||||
|
val importedFiles: List<Path>)
|
||||||
|
|
||||||
|
|
||||||
fun compileProgram(filepath: Path,
|
fun compileProgram(filepath: Path,
|
||||||
optimize: Boolean, optimizeInlining: Boolean,
|
optimize: Boolean,
|
||||||
generateVmCode: Boolean, writeVmCode: Boolean,
|
writeAssembly: Boolean,
|
||||||
writeAssembly: Boolean): Pair<Program, String?> {
|
outputDir: Path): CompilationResult {
|
||||||
lateinit var programAst: Program
|
lateinit var programAst: Program
|
||||||
var programName: String? = null
|
var programName: String? = null
|
||||||
|
|
||||||
|
var importedFiles: List<Path> = emptyList()
|
||||||
|
var success=false
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val totalTime = measureTimeMillis {
|
val totalTime = measureTimeMillis {
|
||||||
// import main module and everything it needs
|
// import main module and everything it needs
|
||||||
@ -36,6 +40,8 @@ fun compileProgram(filepath: Path,
|
|||||||
programAst = Program(moduleName(filepath.fileName), mutableListOf())
|
programAst = Program(moduleName(filepath.fileName), mutableListOf())
|
||||||
importModule(programAst, filepath)
|
importModule(programAst, filepath)
|
||||||
|
|
||||||
|
importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map{ it.source }
|
||||||
|
|
||||||
val compilerOptions = determineCompilationOptions(programAst)
|
val compilerOptions = determineCompilationOptions(programAst)
|
||||||
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
|
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
|
||||||
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
|
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
|
||||||
@ -63,7 +69,9 @@ fun compileProgram(filepath: Path,
|
|||||||
}
|
}
|
||||||
//println(" time2: $time2")
|
//println(" time2: $time2")
|
||||||
val time3 = measureTimeMillis {
|
val time3 = measureTimeMillis {
|
||||||
programAst.reorderStatements() // reorder statements and add type casts, to please the compiler later
|
programAst.removeNopsFlattenAnonScopes()
|
||||||
|
programAst.reorderStatements()
|
||||||
|
programAst.addTypecasts()
|
||||||
}
|
}
|
||||||
//println(" time3: $time3")
|
//println(" time3: $time3")
|
||||||
val time4 = measureTimeMillis {
|
val time4 = measureTimeMillis {
|
||||||
@ -78,42 +86,28 @@ fun compileProgram(filepath: Path,
|
|||||||
while (true) {
|
while (true) {
|
||||||
// keep optimizing expressions and statements until no more steps remain
|
// keep optimizing expressions and statements until no more steps remain
|
||||||
val optsDone1 = programAst.simplifyExpressions()
|
val optsDone1 = programAst.simplifyExpressions()
|
||||||
val optsDone2 = programAst.optimizeStatements(optimizeInlining)
|
val optsDone2 = programAst.optimizeStatements()
|
||||||
if (optsDone1 + optsDone2 == 0)
|
if (optsDone1 + optsDone2 == 0)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
programAst.removeNops()
|
programAst.addTypecasts()
|
||||||
|
programAst.removeNopsFlattenAnonScopes()
|
||||||
programAst.checkValid(compilerOptions) // check if final tree is valid
|
programAst.checkValid(compilerOptions) // check if final tree is valid
|
||||||
programAst.checkRecursion() // check if there are recursive subroutine calls
|
programAst.checkRecursion() // check if there are recursive subroutine calls
|
||||||
|
|
||||||
// printAst(programAst)
|
// printAst(programAst)
|
||||||
// namespace.debugPrint()
|
|
||||||
|
|
||||||
if(generateVmCode) {
|
if(writeAssembly) {
|
||||||
// compile the syntax tree into stackvmProg form, and optimize that
|
// asm generation directly from the Ast, no need for intermediate code
|
||||||
val compiler = Compiler(programAst)
|
val zeropage = MachineDefinition.C64Zeropage(compilerOptions)
|
||||||
val intermediate = compiler.compile(compilerOptions)
|
programAst.anonscopeVarsCleanup()
|
||||||
if (optimize)
|
val assembly = AsmGen(programAst, zeropage, compilerOptions, outputDir).compileToAssembly(optimize)
|
||||||
intermediate.optimize()
|
assembly.assemble(compilerOptions)
|
||||||
|
programName = assembly.name
|
||||||
if (writeVmCode) {
|
|
||||||
val stackVmFilename = intermediate.name + ".vm.txt"
|
|
||||||
val stackvmFile = PrintStream(File(stackVmFilename), "utf-8")
|
|
||||||
intermediate.writeCode(stackvmFile)
|
|
||||||
stackvmFile.close()
|
|
||||||
println("StackVM program code written to '$stackVmFilename'")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (writeAssembly) {
|
|
||||||
val zeropage = C64Zeropage(compilerOptions)
|
|
||||||
intermediate.allocateZeropage(zeropage)
|
|
||||||
val assembly = AsmGen(compilerOptions, intermediate, programAst.heap, zeropage).compileToAssembly(optimize)
|
|
||||||
assembly.assemble(compilerOptions)
|
|
||||||
programName = assembly.name
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
success = true
|
||||||
}
|
}
|
||||||
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
|
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
|
||||||
|
|
||||||
@ -121,12 +115,10 @@ fun compileProgram(filepath: Path,
|
|||||||
System.err.print("\u001b[91m") // bright red
|
System.err.print("\u001b[91m") // bright red
|
||||||
System.err.println(px.message)
|
System.err.println(px.message)
|
||||||
System.err.print("\u001b[0m") // reset
|
System.err.print("\u001b[0m") // reset
|
||||||
exitProcess(1)
|
|
||||||
} catch (ax: AstException) {
|
} catch (ax: AstException) {
|
||||||
System.err.print("\u001b[91m") // bright red
|
System.err.print("\u001b[91m") // bright red
|
||||||
System.err.println(ax.toString())
|
System.err.println(ax.toString())
|
||||||
System.err.print("\u001b[0m") // reset
|
System.err.print("\u001b[0m") // reset
|
||||||
exitProcess(1)
|
|
||||||
} catch (x: Exception) {
|
} catch (x: Exception) {
|
||||||
print("\u001b[91m") // bright red
|
print("\u001b[91m") // bright red
|
||||||
println("\n* internal error *")
|
println("\n* internal error *")
|
||||||
@ -140,12 +132,12 @@ fun compileProgram(filepath: Path,
|
|||||||
System.out.flush()
|
System.out.flush()
|
||||||
throw x
|
throw x
|
||||||
}
|
}
|
||||||
return Pair(programAst, programName)
|
return CompilationResult(success, programAst, programName ?: "", importedFiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun printAst(programAst: Program) {
|
fun printAst(programAst: Program) {
|
||||||
println()
|
println()
|
||||||
val printer = AstToSourceCode(::print)
|
val printer = AstToSourceCode(::print, programAst)
|
||||||
printer.visit(programAst)
|
printer.visit(programAst)
|
||||||
println()
|
println()
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package prog8.compiler
|
package prog8.compiler
|
||||||
|
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.base.printWarning
|
|
||||||
|
|
||||||
|
|
||||||
class ZeropageDepletedError(message: String) : Exception(message)
|
class ZeropageDepletedError(message: String) : Exception(message)
|
||||||
@ -14,11 +13,14 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
|||||||
|
|
||||||
val allowedDatatypes = NumericDatatypes
|
val allowedDatatypes = NumericDatatypes
|
||||||
|
|
||||||
fun available() = free.size
|
fun available() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size
|
||||||
|
|
||||||
fun allocate(scopedname: String, datatype: DataType, position: Position?): Int {
|
fun allocate(scopedname: String, datatype: DataType, position: Position?): Int {
|
||||||
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"isSameAs scopedname can't be allocated twice"}
|
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"isSameAs scopedname can't be allocated twice"}
|
||||||
|
|
||||||
|
if(options.zeropage==ZeropageType.DONTUSE)
|
||||||
|
throw CompilerException("zero page usage has been disabled")
|
||||||
|
|
||||||
val size =
|
val size =
|
||||||
when (datatype) {
|
when (datatype) {
|
||||||
in ByteDatatypes -> 1
|
in ByteDatatypes -> 1
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
package prog8.compiler.intermediate
|
|
||||||
|
|
||||||
import prog8.vm.RuntimeValue
|
|
||||||
import prog8.vm.stackvm.Syscall
|
|
||||||
|
|
||||||
open class Instruction(val opcode: Opcode,
|
|
||||||
val arg: RuntimeValue? = null,
|
|
||||||
val arg2: RuntimeValue? = null,
|
|
||||||
val callLabel: String? = null,
|
|
||||||
val callLabel2: String? = null)
|
|
||||||
{
|
|
||||||
var branchAddress: Int? = null
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
val argStr = arg?.toString() ?: ""
|
|
||||||
val result =
|
|
||||||
when {
|
|
||||||
opcode==Opcode.LINE -> "_line $callLabel"
|
|
||||||
opcode==Opcode.INLINE_ASSEMBLY -> {
|
|
||||||
// inline assembly is not written out (it can't be processed as intermediate language)
|
|
||||||
// instead, it is converted into a system call that can be intercepted by the vm
|
|
||||||
if(callLabel!=null)
|
|
||||||
"syscall SYSASM.$callLabel\n return"
|
|
||||||
else
|
|
||||||
"inline_assembly"
|
|
||||||
}
|
|
||||||
opcode==Opcode.INCLUDE_FILE -> {
|
|
||||||
"include_file \"$callLabel\" $arg $arg2"
|
|
||||||
}
|
|
||||||
opcode==Opcode.SYSCALL -> {
|
|
||||||
val syscall = Syscall.values().find { it.callNr==arg!!.numericValue() }
|
|
||||||
"syscall $syscall"
|
|
||||||
}
|
|
||||||
opcode in opcodesWithVarArgument -> {
|
|
||||||
// opcodes that manipulate a variable
|
|
||||||
"${opcode.name.toLowerCase()} ${callLabel?:""} ${callLabel2?:""}".trimEnd()
|
|
||||||
}
|
|
||||||
callLabel==null -> "${opcode.name.toLowerCase()} $argStr"
|
|
||||||
else -> "${opcode.name.toLowerCase()} $callLabel $argStr"
|
|
||||||
}
|
|
||||||
.trimEnd()
|
|
||||||
|
|
||||||
return " $result"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class LabelInstr(val name: String, val asmProc: Boolean) : Instruction(Opcode.NOP, null, null) {
|
|
||||||
override fun toString(): String {
|
|
||||||
return "\n$name:"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,536 +0,0 @@
|
|||||||
package prog8.compiler.intermediate
|
|
||||||
|
|
||||||
import prog8.ast.antlr.escape
|
|
||||||
import prog8.ast.base.*
|
|
||||||
import prog8.ast.base.printWarning
|
|
||||||
import prog8.ast.expressions.LiteralValue
|
|
||||||
import prog8.ast.statements.VarDecl
|
|
||||||
import prog8.vm.RuntimeValue
|
|
||||||
import prog8.compiler.CompilerException
|
|
||||||
import prog8.compiler.HeapValues
|
|
||||||
import prog8.compiler.Zeropage
|
|
||||||
import prog8.compiler.ZeropageDepletedError
|
|
||||||
import java.io.PrintStream
|
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
|
|
||||||
class IntermediateProgram(val name: String, var loadAddress: Int, val heap: HeapValues, val source: Path) {
|
|
||||||
|
|
||||||
class ProgramBlock(val name: String,
|
|
||||||
var address: Int?,
|
|
||||||
val instructions: MutableList<Instruction> = mutableListOf(),
|
|
||||||
val variables: MutableMap<String, RuntimeValue> = mutableMapOf(), // names are fully scoped
|
|
||||||
val memoryPointers: MutableMap<String, Pair<Int, DataType>> = mutableMapOf(),
|
|
||||||
val labels: MutableMap<String, Instruction> = mutableMapOf(), // names are fully scoped
|
|
||||||
val force_output: Boolean)
|
|
||||||
{
|
|
||||||
val numVariables: Int
|
|
||||||
get() { return variables.size }
|
|
||||||
val numInstructions: Int
|
|
||||||
get() { return instructions.filter { it.opcode!= Opcode.LINE }.size }
|
|
||||||
val variablesMarkedForZeropage: MutableSet<String> = mutableSetOf()
|
|
||||||
}
|
|
||||||
|
|
||||||
val allocatedZeropageVariables = mutableMapOf<String, Pair<Int, DataType>>()
|
|
||||||
val blocks = mutableListOf<ProgramBlock>()
|
|
||||||
val memory = mutableMapOf<Int, List<RuntimeValue>>()
|
|
||||||
private lateinit var currentBlock: ProgramBlock
|
|
||||||
|
|
||||||
val numVariables: Int
|
|
||||||
get() = blocks.sumBy { it.numVariables }
|
|
||||||
val numInstructions: Int
|
|
||||||
get() = blocks.sumBy { it.numInstructions }
|
|
||||||
|
|
||||||
fun allocateZeropage(zeropage: Zeropage) {
|
|
||||||
// allocates all @zp marked variables on the zeropage (for all blocks, as long as there is space in the ZP)
|
|
||||||
var notAllocated = 0
|
|
||||||
for(block in blocks) {
|
|
||||||
val zpVariables = block.variables.filter { it.key in block.variablesMarkedForZeropage }
|
|
||||||
if (zpVariables.isNotEmpty()) {
|
|
||||||
for (variable in zpVariables) {
|
|
||||||
try {
|
|
||||||
val address = zeropage.allocate(variable.key, variable.value.type, null)
|
|
||||||
allocatedZeropageVariables[variable.key] = Pair(address, variable.value.type)
|
|
||||||
} catch (x: ZeropageDepletedError) {
|
|
||||||
printWarning(x.toString() + " variable ${variable.key} type ${variable.value.type}")
|
|
||||||
notAllocated++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(notAllocated>0)
|
|
||||||
printWarning("$notAllocated variables marked for Zeropage could not be allocated there")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun optimize() {
|
|
||||||
println("Optimizing stackVM code...")
|
|
||||||
// remove nops (that are not a label)
|
|
||||||
for (blk in blocks) {
|
|
||||||
blk.instructions.removeIf { it.opcode== Opcode.NOP && it !is LabelInstr }
|
|
||||||
}
|
|
||||||
|
|
||||||
optimizeDataConversionAndUselessDiscards()
|
|
||||||
optimizeVariableCopying()
|
|
||||||
optimizeMultipleSequentialLineInstrs()
|
|
||||||
optimizeCallReturnIntoJump()
|
|
||||||
optimizeConditionalBranches()
|
|
||||||
// todo: add more optimizations to intermediate code!
|
|
||||||
|
|
||||||
optimizeRemoveNops() // must be done as the last step
|
|
||||||
optimizeMultipleSequentialLineInstrs() // once more
|
|
||||||
optimizeRemoveNops() // once more
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun optimizeConditionalBranches() {
|
|
||||||
// conditional branches that consume the value on the stack
|
|
||||||
// sometimes these are just constant values, so we can statically determine the branch
|
|
||||||
// or, they are preceded by a NOT instruction so we can simply remove that and flip the branch condition
|
|
||||||
val pushvalue = setOf(Opcode.PUSH_BYTE, Opcode.PUSH_WORD)
|
|
||||||
val notvalue = setOf(Opcode.NOT_BYTE, Opcode.NOT_WORD)
|
|
||||||
val branchOpcodes = setOf(Opcode.JZ, Opcode.JNZ, Opcode.JZW, Opcode.JNZW)
|
|
||||||
for(blk in blocks) {
|
|
||||||
val instructionsToReplace = mutableMapOf<Int, Instruction>()
|
|
||||||
blk.instructions.asSequence().withIndex().filter {it.value.opcode!=Opcode.LINE}.windowed(2).toList().forEach {
|
|
||||||
if (it[1].value.opcode in branchOpcodes) {
|
|
||||||
if (it[0].value.opcode in pushvalue) {
|
|
||||||
val value = it[0].value.arg!!.asBoolean
|
|
||||||
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
|
|
||||||
val replacement: Instruction =
|
|
||||||
if (value) {
|
|
||||||
when (it[1].value.opcode) {
|
|
||||||
Opcode.JNZ -> Instruction(Opcode.JUMP, callLabel = it[1].value.callLabel)
|
|
||||||
Opcode.JNZW -> Instruction(Opcode.JUMP, callLabel = it[1].value.callLabel)
|
|
||||||
else -> Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
when (it[1].value.opcode) {
|
|
||||||
Opcode.JZ -> Instruction(Opcode.JUMP, callLabel = it[1].value.callLabel)
|
|
||||||
Opcode.JZW -> Instruction(Opcode.JUMP, callLabel = it[1].value.callLabel)
|
|
||||||
else -> Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
instructionsToReplace[it[1].index] = replacement
|
|
||||||
}
|
|
||||||
else if (it[0].value.opcode in notvalue) {
|
|
||||||
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
|
|
||||||
val replacement: Instruction =
|
|
||||||
when (it[1].value.opcode) {
|
|
||||||
Opcode.JZ -> Instruction(Opcode.JNZ, callLabel = it[1].value.callLabel)
|
|
||||||
Opcode.JZW -> Instruction(Opcode.JNZW, callLabel = it[1].value.callLabel)
|
|
||||||
Opcode.JNZ -> Instruction(Opcode.JZ, callLabel = it[1].value.callLabel)
|
|
||||||
Opcode.JNZW -> Instruction(Opcode.JZW, callLabel = it[1].value.callLabel)
|
|
||||||
else -> Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
instructionsToReplace[it[1].index] = replacement
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (rins in instructionsToReplace) {
|
|
||||||
blk.instructions[rins.key] = rins.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun optimizeRemoveNops() {
|
|
||||||
// remove nops (that are not a label)
|
|
||||||
for (blk in blocks)
|
|
||||||
blk.instructions.removeIf { it.opcode== Opcode.NOP && it !is LabelInstr }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun optimizeCallReturnIntoJump() {
|
|
||||||
// replaces call X followed by return, by jump X
|
|
||||||
for(blk in blocks) {
|
|
||||||
val instructionsToReplace = mutableMapOf<Int, Instruction>()
|
|
||||||
|
|
||||||
blk.instructions.asSequence().withIndex().filter {it.value.opcode!=Opcode.LINE}.windowed(2).toList().forEach {
|
|
||||||
if(it[0].value.opcode==Opcode.CALL && it[1].value.opcode==Opcode.RETURN) {
|
|
||||||
instructionsToReplace[it[1].index] = Instruction(Opcode.JUMP, callLabel = it[0].value.callLabel)
|
|
||||||
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (rins in instructionsToReplace) {
|
|
||||||
blk.instructions[rins.key] = rins.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun optimizeMultipleSequentialLineInstrs() {
|
|
||||||
for(blk in blocks) {
|
|
||||||
val instructionsToReplace = mutableMapOf<Int, Instruction>()
|
|
||||||
|
|
||||||
blk.instructions.asSequence().withIndex().windowed(2).toList().forEach {
|
|
||||||
if (it[0].value.opcode == Opcode.LINE && it[1].value.opcode == Opcode.LINE)
|
|
||||||
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (rins in instructionsToReplace) {
|
|
||||||
blk.instructions[rins.key] = rins.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun optimizeVariableCopying() {
|
|
||||||
for(blk in blocks) {
|
|
||||||
|
|
||||||
val instructionsToReplace = mutableMapOf<Int, Instruction>()
|
|
||||||
|
|
||||||
blk.instructions.asSequence().withIndex().windowed(2).toList().forEach {
|
|
||||||
when (it[0].value.opcode) {
|
|
||||||
Opcode.PUSH_VAR_BYTE ->
|
|
||||||
if (it[1].value.opcode == Opcode.POP_VAR_BYTE) {
|
|
||||||
if (it[0].value.callLabel == it[1].value.callLabel) {
|
|
||||||
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
|
|
||||||
instructionsToReplace[it[1].index] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Opcode.PUSH_VAR_WORD ->
|
|
||||||
if (it[1].value.opcode == Opcode.POP_VAR_WORD) {
|
|
||||||
if (it[0].value.callLabel == it[1].value.callLabel) {
|
|
||||||
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
|
|
||||||
instructionsToReplace[it[1].index] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Opcode.PUSH_VAR_FLOAT ->
|
|
||||||
if (it[1].value.opcode == Opcode.POP_VAR_FLOAT) {
|
|
||||||
if (it[0].value.callLabel == it[1].value.callLabel) {
|
|
||||||
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
|
|
||||||
instructionsToReplace[it[1].index] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Opcode.PUSH_MEM_B, Opcode.PUSH_MEM_UB ->
|
|
||||||
if(it[1].value.opcode == Opcode.POP_MEM_BYTE) {
|
|
||||||
if(it[0].value.arg == it[1].value.arg) {
|
|
||||||
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
|
|
||||||
instructionsToReplace[it[1].index] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Opcode.PUSH_MEM_W, Opcode.PUSH_MEM_UW ->
|
|
||||||
if(it[1].value.opcode == Opcode.POP_MEM_WORD) {
|
|
||||||
if(it[0].value.arg == it[1].value.arg) {
|
|
||||||
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
|
|
||||||
instructionsToReplace[it[1].index] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Opcode.PUSH_MEM_FLOAT ->
|
|
||||||
if(it[1].value.opcode == Opcode.POP_MEM_FLOAT) {
|
|
||||||
if(it[0].value.arg == it[1].value.arg) {
|
|
||||||
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
|
|
||||||
instructionsToReplace[it[1].index] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (rins in instructionsToReplace) {
|
|
||||||
blk.instructions[rins.key] = rins.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun optimizeDataConversionAndUselessDiscards() {
|
|
||||||
// - push value followed by a data type conversion -> push the value in the correct type and remove the conversion
|
|
||||||
// - push something followed by a discard -> remove both
|
|
||||||
val instructionsToReplace = mutableMapOf<Int, Instruction>()
|
|
||||||
|
|
||||||
fun optimizeDiscardAfterPush(index0: Int, index1: Int, ins1: Instruction) {
|
|
||||||
if (ins1.opcode == Opcode.DISCARD_FLOAT || ins1.opcode == Opcode.DISCARD_WORD || ins1.opcode == Opcode.DISCARD_BYTE) {
|
|
||||||
instructionsToReplace[index0] = Instruction(Opcode.NOP)
|
|
||||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun optimizeFloatConversion(index0: Int, index1: Int, ins1: Instruction) {
|
|
||||||
when (ins1.opcode) {
|
|
||||||
Opcode.DISCARD_FLOAT -> {
|
|
||||||
instructionsToReplace[index0] = Instruction(Opcode.NOP)
|
|
||||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
Opcode.DISCARD_BYTE, Opcode.DISCARD_WORD -> throw CompilerException("invalid discard type following a float")
|
|
||||||
else -> throw CompilerException("invalid conversion opcode ${ins1.opcode} following a float")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun optimizeWordConversion(index0: Int, ins0: Instruction, index1: Int, ins1: Instruction) {
|
|
||||||
when (ins1.opcode) {
|
|
||||||
Opcode.CAST_UW_TO_B, Opcode.CAST_W_TO_B -> {
|
|
||||||
val ins = Instruction(Opcode.PUSH_BYTE, ins0.arg!!.cast(DataType.BYTE))
|
|
||||||
instructionsToReplace[index0] = ins
|
|
||||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
Opcode.CAST_W_TO_UB, Opcode.CAST_UW_TO_UB -> {
|
|
||||||
val ins = Instruction(Opcode.PUSH_BYTE, RuntimeValue(DataType.UBYTE, ins0.arg!!.integerValue() and 255))
|
|
||||||
instructionsToReplace[index0] = ins
|
|
||||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
Opcode.MSB -> {
|
|
||||||
val ins = Instruction(Opcode.PUSH_BYTE, RuntimeValue(DataType.UBYTE, ins0.arg!!.integerValue() ushr 8 and 255))
|
|
||||||
instructionsToReplace[index0] = ins
|
|
||||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
Opcode.CAST_W_TO_F, Opcode.CAST_UW_TO_F -> {
|
|
||||||
val ins = Instruction(Opcode.PUSH_FLOAT, RuntimeValue(DataType.FLOAT, ins0.arg!!.integerValue().toDouble()))
|
|
||||||
instructionsToReplace[index0] = ins
|
|
||||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
Opcode.CAST_UW_TO_W -> {
|
|
||||||
val cv = ins0.arg!!.cast(DataType.WORD)
|
|
||||||
instructionsToReplace[index0] = Instruction(Opcode.PUSH_WORD, cv)
|
|
||||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
Opcode.CAST_W_TO_UW -> {
|
|
||||||
val cv = ins0.arg!!.cast(DataType.UWORD)
|
|
||||||
instructionsToReplace[index0] = Instruction(Opcode.PUSH_WORD, cv)
|
|
||||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
Opcode.DISCARD_WORD -> {
|
|
||||||
instructionsToReplace[index0] = Instruction(Opcode.NOP)
|
|
||||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
Opcode.DISCARD_BYTE, Opcode.DISCARD_FLOAT -> throw CompilerException("invalid discard type following a byte")
|
|
||||||
else -> throw CompilerException("invalid conversion opcode ${ins1.opcode} following a word")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun optimizeByteConversion(index0: Int, ins0: Instruction, index1: Int, ins1: Instruction) {
|
|
||||||
when (ins1.opcode) {
|
|
||||||
Opcode.CAST_B_TO_UB, Opcode.CAST_UB_TO_B,
|
|
||||||
Opcode.CAST_W_TO_B, Opcode.CAST_W_TO_UB,
|
|
||||||
Opcode.CAST_UW_TO_B, Opcode.CAST_UW_TO_UB -> instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
|
||||||
Opcode.MSB -> throw CompilerException("msb of a byte")
|
|
||||||
Opcode.CAST_UB_TO_UW -> {
|
|
||||||
val ins = Instruction(Opcode.PUSH_WORD, RuntimeValue(DataType.UWORD, ins0.arg!!.integerValue()))
|
|
||||||
instructionsToReplace[index0] = ins
|
|
||||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
Opcode.CAST_B_TO_W -> {
|
|
||||||
val ins = Instruction(Opcode.PUSH_WORD, RuntimeValue(DataType.WORD, ins0.arg!!.integerValue()))
|
|
||||||
instructionsToReplace[index0] = ins
|
|
||||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
Opcode.CAST_B_TO_UW -> {
|
|
||||||
val ins = Instruction(Opcode.PUSH_WORD, ins0.arg!!.cast(DataType.UWORD))
|
|
||||||
instructionsToReplace[index0] = ins
|
|
||||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
Opcode.CAST_UB_TO_W -> {
|
|
||||||
val ins = Instruction(Opcode.PUSH_WORD, ins0.arg!!.cast(DataType.WORD))
|
|
||||||
instructionsToReplace[index0] = ins
|
|
||||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
Opcode.CAST_B_TO_F, Opcode.CAST_UB_TO_F-> {
|
|
||||||
val ins = Instruction(Opcode.PUSH_FLOAT, RuntimeValue(DataType.FLOAT, ins0.arg!!.integerValue().toDouble()))
|
|
||||||
instructionsToReplace[index0] = ins
|
|
||||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
Opcode.CAST_W_TO_F, Opcode.CAST_UW_TO_F-> throw CompilerException("invalid conversion following a byte")
|
|
||||||
Opcode.DISCARD_BYTE -> {
|
|
||||||
instructionsToReplace[index0] = Instruction(Opcode.NOP)
|
|
||||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
Opcode.DISCARD_WORD, Opcode.DISCARD_FLOAT -> throw CompilerException("invalid discard type following a byte")
|
|
||||||
Opcode.MKWORD -> {}
|
|
||||||
else -> throw CompilerException("invalid conversion opcode ${ins1.opcode}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for(blk in blocks) {
|
|
||||||
instructionsToReplace.clear()
|
|
||||||
|
|
||||||
val typeConversionOpcodes = setOf(
|
|
||||||
Opcode.MSB,
|
|
||||||
Opcode.MKWORD,
|
|
||||||
Opcode.CAST_UB_TO_B,
|
|
||||||
Opcode.CAST_UB_TO_UW,
|
|
||||||
Opcode.CAST_UB_TO_W,
|
|
||||||
Opcode.CAST_UB_TO_F,
|
|
||||||
Opcode.CAST_B_TO_UB,
|
|
||||||
Opcode.CAST_B_TO_UW,
|
|
||||||
Opcode.CAST_B_TO_W,
|
|
||||||
Opcode.CAST_B_TO_F,
|
|
||||||
Opcode.CAST_UW_TO_UB,
|
|
||||||
Opcode.CAST_UW_TO_B,
|
|
||||||
Opcode.CAST_UW_TO_W,
|
|
||||||
Opcode.CAST_UW_TO_F,
|
|
||||||
Opcode.CAST_W_TO_UB,
|
|
||||||
Opcode.CAST_W_TO_B,
|
|
||||||
Opcode.CAST_W_TO_UW,
|
|
||||||
Opcode.CAST_W_TO_F,
|
|
||||||
Opcode.CAST_F_TO_UB,
|
|
||||||
Opcode.CAST_F_TO_B,
|
|
||||||
Opcode.CAST_F_TO_UW,
|
|
||||||
Opcode.CAST_F_TO_W,
|
|
||||||
Opcode.DISCARD_BYTE,
|
|
||||||
Opcode.DISCARD_WORD,
|
|
||||||
Opcode.DISCARD_FLOAT
|
|
||||||
)
|
|
||||||
blk.instructions.asSequence().withIndex().windowed(2).toList().forEach {
|
|
||||||
if (it[1].value.opcode in typeConversionOpcodes) {
|
|
||||||
when (it[0].value.opcode) {
|
|
||||||
Opcode.PUSH_BYTE -> optimizeByteConversion(it[0].index, it[0].value, it[1].index, it[1].value)
|
|
||||||
Opcode.PUSH_WORD -> optimizeWordConversion(it[0].index, it[0].value, it[1].index, it[1].value)
|
|
||||||
Opcode.PUSH_FLOAT -> optimizeFloatConversion(it[0].index, it[1].index, it[1].value)
|
|
||||||
Opcode.PUSH_VAR_FLOAT,
|
|
||||||
Opcode.PUSH_VAR_WORD,
|
|
||||||
Opcode.PUSH_VAR_BYTE,
|
|
||||||
Opcode.PUSH_MEM_B, Opcode.PUSH_MEM_UB,
|
|
||||||
Opcode.PUSH_MEM_W, Opcode.PUSH_MEM_UW,
|
|
||||||
Opcode.PUSH_MEM_FLOAT -> optimizeDiscardAfterPush(it[0].index, it[1].index, it[1].value)
|
|
||||||
else -> {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (rins in instructionsToReplace) {
|
|
||||||
blk.instructions[rins.key] = rins.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun variable(scopedname: String, decl: VarDecl) {
|
|
||||||
when(decl.type) {
|
|
||||||
VarDeclType.VAR -> {
|
|
||||||
val value = when(decl.datatype) {
|
|
||||||
in NumericDatatypes -> RuntimeValue(decl.datatype, (decl.value as LiteralValue).asNumericValue!!)
|
|
||||||
in StringDatatypes -> {
|
|
||||||
val litval = (decl.value as LiteralValue)
|
|
||||||
if(litval.heapId==null)
|
|
||||||
throw CompilerException("string should already be in the heap")
|
|
||||||
RuntimeValue(decl.datatype, heapId = litval.heapId)
|
|
||||||
}
|
|
||||||
in ArrayDatatypes -> {
|
|
||||||
val litval = (decl.value as LiteralValue)
|
|
||||||
if(litval.heapId==null)
|
|
||||||
throw CompilerException("array should already be in the heap")
|
|
||||||
RuntimeValue(decl.datatype, heapId = litval.heapId)
|
|
||||||
}
|
|
||||||
else -> throw CompilerException("weird datatype")
|
|
||||||
}
|
|
||||||
currentBlock.variables[scopedname] = value
|
|
||||||
if(decl.zeropage)
|
|
||||||
currentBlock.variablesMarkedForZeropage.add(scopedname)
|
|
||||||
}
|
|
||||||
VarDeclType.MEMORY -> {
|
|
||||||
// note that constants are all folded away, but assembly code may still refer to them
|
|
||||||
val lv = decl.value as LiteralValue
|
|
||||||
if(lv.type!= DataType.UWORD && lv.type!= DataType.UBYTE)
|
|
||||||
throw CompilerException("expected integer memory address $lv")
|
|
||||||
currentBlock.memoryPointers[scopedname] = Pair(lv.asIntegerValue!!, decl.datatype)
|
|
||||||
}
|
|
||||||
VarDeclType.CONST -> {
|
|
||||||
// note that constants are all folded away, but assembly code may still refer to them (if their integers)
|
|
||||||
// floating point constants are not generated at all!!
|
|
||||||
val lv = decl.value as LiteralValue
|
|
||||||
if(lv.type in IntegerDatatypes)
|
|
||||||
currentBlock.memoryPointers[scopedname] = Pair(lv.asIntegerValue!!, decl.datatype)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun instr(opcode: Opcode, arg: RuntimeValue? = null, arg2: RuntimeValue? = null, callLabel: String? = null, callLabel2: String? = null) {
|
|
||||||
currentBlock.instructions.add(Instruction(opcode, arg, arg2, callLabel, callLabel2))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun label(labelname: String, asmProc: Boolean=false) {
|
|
||||||
val instr = LabelInstr(labelname, asmProc)
|
|
||||||
currentBlock.instructions.add(instr)
|
|
||||||
currentBlock.labels[labelname] = instr
|
|
||||||
}
|
|
||||||
|
|
||||||
fun line(position: Position) {
|
|
||||||
currentBlock.instructions.add(Instruction(Opcode.LINE, callLabel = "${position.line} ${position.file}"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun removeLastInstruction() {
|
|
||||||
currentBlock.instructions.removeAt(currentBlock.instructions.lastIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun memoryPointer(name: String, address: Int, datatype: DataType) {
|
|
||||||
currentBlock.memoryPointers[name] = Pair(address, datatype)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun newBlock(name: String, address: Int?, options: Set<String>) {
|
|
||||||
currentBlock = ProgramBlock(name, address, force_output="force_output" in options)
|
|
||||||
blocks.add(currentBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun writeCode(out: PrintStream, embeddedLabels: Boolean=true) {
|
|
||||||
out.println("; stackVM program code for '$name'")
|
|
||||||
writeMemory(out)
|
|
||||||
writeHeap(out)
|
|
||||||
for(blk in blocks) {
|
|
||||||
writeBlock(out, blk, embeddedLabels)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun writeHeap(out: PrintStream) {
|
|
||||||
out.println("%heap")
|
|
||||||
heap.allEntries().forEach {
|
|
||||||
out.print("${it.key} ${it.value.type.name.toLowerCase()} ")
|
|
||||||
when {
|
|
||||||
it.value.str!=null ->
|
|
||||||
out.println("\"${escape(it.value.str!!)}\"")
|
|
||||||
it.value.array!=null -> {
|
|
||||||
// this array can contain both normal integers, and pointer values
|
|
||||||
val arrayvalues = it.value.array!!.map { av ->
|
|
||||||
when {
|
|
||||||
av.integer!=null -> av.integer.toString()
|
|
||||||
av.addressOf!=null -> {
|
|
||||||
if(av.addressOf.scopedname==null)
|
|
||||||
throw CompilerException("AddressOf scopedname should have been set")
|
|
||||||
else
|
|
||||||
"&${av.addressOf.scopedname}"
|
|
||||||
}
|
|
||||||
else -> throw CompilerException("weird array value")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out.println(arrayvalues)
|
|
||||||
}
|
|
||||||
it.value.doubleArray!=null ->
|
|
||||||
out.println(it.value.doubleArray!!.toList())
|
|
||||||
else -> throw CompilerException("invalid heap entry $it")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out.println("%end_heap")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun writeBlock(out: PrintStream, blk: ProgramBlock, embeddedLabels: Boolean) {
|
|
||||||
out.println("\n%block ${blk.name} ${blk.address?.toString(16) ?: ""}")
|
|
||||||
|
|
||||||
out.println("%variables")
|
|
||||||
for (variable in blk.variables) {
|
|
||||||
val valuestr = variable.value.toString()
|
|
||||||
out.println("${variable.key} ${variable.value.type.name.toLowerCase()} $valuestr")
|
|
||||||
}
|
|
||||||
out.println("%end_variables")
|
|
||||||
out.println("%memorypointers")
|
|
||||||
for (iconst in blk.memoryPointers) {
|
|
||||||
out.println("${iconst.key} ${iconst.value.second.name.toLowerCase()} uw:${iconst.value.first.toString(16)}")
|
|
||||||
}
|
|
||||||
out.println("%end_memorypointers")
|
|
||||||
out.println("%instructions")
|
|
||||||
val labels = blk.labels.entries.associateBy({ it.value }) { it.key }
|
|
||||||
for (instr in blk.instructions) {
|
|
||||||
if (!embeddedLabels) {
|
|
||||||
val label = labels[instr]
|
|
||||||
if (label != null)
|
|
||||||
out.println("$label:")
|
|
||||||
} else {
|
|
||||||
out.println(instr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out.println("%end_instructions")
|
|
||||||
|
|
||||||
out.println("%end_block")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun writeMemory(out: PrintStream) {
|
|
||||||
out.println("%memory")
|
|
||||||
if (memory.isNotEmpty())
|
|
||||||
TODO("add support for writing/reading initial memory values")
|
|
||||||
out.println("%end_memory")
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,291 +0,0 @@
|
|||||||
package prog8.compiler.intermediate
|
|
||||||
|
|
||||||
enum class Opcode {
|
|
||||||
|
|
||||||
// pushing values on the (evaluation) stack
|
|
||||||
PUSH_BYTE, // push byte value
|
|
||||||
PUSH_WORD, // push word value (or 'address' of string / array)
|
|
||||||
PUSH_FLOAT, // push float value
|
|
||||||
PUSH_MEM_B, // push byte value from memory to stack
|
|
||||||
PUSH_MEM_UB, // push unsigned byte value from memory to stack
|
|
||||||
PUSH_MEM_W, // push word value from memory to stack
|
|
||||||
PUSH_MEM_UW, // push unsigned word value from memory to stack
|
|
||||||
PUSH_MEM_FLOAT, // push float value from memory to stack
|
|
||||||
PUSH_MEMREAD, // push memory value from address that's on the stack
|
|
||||||
PUSH_VAR_BYTE, // push byte variable (ubyte, byte)
|
|
||||||
PUSH_VAR_WORD, // push word variable (uword, word)
|
|
||||||
PUSH_VAR_FLOAT, // push float variable
|
|
||||||
PUSH_REGAX_WORD, // push registers A/X as a 16-bit word
|
|
||||||
PUSH_REGAY_WORD, // push registers A/Y as a 16-bit word
|
|
||||||
PUSH_REGXY_WORD, // push registers X/Y as a 16-bit word
|
|
||||||
PUSH_ADDR_HEAPVAR, // push the address of the variable that's on the heap (string or array)
|
|
||||||
DUP_B, // duplicate the top byte on the stack
|
|
||||||
DUP_W, // duplicate the top word on the stack
|
|
||||||
|
|
||||||
// popping values off the (evaluation) stack, possibly storing them in another location
|
|
||||||
DISCARD_BYTE, // discard top byte value
|
|
||||||
DISCARD_WORD, // discard top word value
|
|
||||||
DISCARD_FLOAT, // discard top float value
|
|
||||||
POP_MEM_BYTE, // pop (u)byte value into destination memory address
|
|
||||||
POP_MEM_WORD, // pop (u)word value into destination memory address
|
|
||||||
POP_MEM_FLOAT, // pop float value into destination memory address
|
|
||||||
POP_MEMWRITE, // pop address and byte stack and write the byte to the memory address
|
|
||||||
POP_VAR_BYTE, // pop (u)byte value into variable
|
|
||||||
POP_VAR_WORD, // pop (u)word value into variable
|
|
||||||
POP_VAR_FLOAT, // pop float value into variable
|
|
||||||
POP_REGAX_WORD, // pop uword from stack into A/X registers
|
|
||||||
POP_REGAY_WORD, // pop uword from stack into A/Y registers
|
|
||||||
POP_REGXY_WORD, // pop uword from stack into X/Y registers
|
|
||||||
|
|
||||||
// numeric arithmetic
|
|
||||||
ADD_UB,
|
|
||||||
ADD_B,
|
|
||||||
ADD_UW,
|
|
||||||
ADD_W,
|
|
||||||
ADD_F,
|
|
||||||
SUB_UB,
|
|
||||||
SUB_B,
|
|
||||||
SUB_UW,
|
|
||||||
SUB_W,
|
|
||||||
SUB_F,
|
|
||||||
MUL_UB,
|
|
||||||
MUL_B,
|
|
||||||
MUL_UW,
|
|
||||||
MUL_W,
|
|
||||||
MUL_F,
|
|
||||||
IDIV_UB,
|
|
||||||
IDIV_B,
|
|
||||||
IDIV_UW,
|
|
||||||
IDIV_W,
|
|
||||||
DIV_F,
|
|
||||||
REMAINDER_UB, // signed remainder is undefined/unimplemented
|
|
||||||
REMAINDER_UW, // signed remainder is undefined/unimplemented
|
|
||||||
POW_F,
|
|
||||||
NEG_B,
|
|
||||||
NEG_W,
|
|
||||||
NEG_F,
|
|
||||||
ABS_B,
|
|
||||||
ABS_W,
|
|
||||||
ABS_F,
|
|
||||||
|
|
||||||
// bit shifts and bitwise arithmetic
|
|
||||||
SHIFTEDL_BYTE, // shifts stack value rather than in-place mem/var
|
|
||||||
SHIFTEDL_WORD, // shifts stack value rather than in-place mem/var
|
|
||||||
SHIFTEDR_UBYTE, // shifts stack value rather than in-place mem/var
|
|
||||||
SHIFTEDR_SBYTE, // shifts stack value rather than in-place mem/var
|
|
||||||
SHIFTEDR_UWORD, // shifts stack value rather than in-place mem/var
|
|
||||||
SHIFTEDR_SWORD, // shifts stack value rather than in-place mem/var
|
|
||||||
SHL_BYTE,
|
|
||||||
SHL_WORD,
|
|
||||||
SHL_MEM_BYTE,
|
|
||||||
SHL_MEM_WORD,
|
|
||||||
SHL_VAR_BYTE,
|
|
||||||
SHL_VAR_WORD,
|
|
||||||
SHR_UBYTE,
|
|
||||||
SHR_SBYTE,
|
|
||||||
SHR_UWORD,
|
|
||||||
SHR_SWORD,
|
|
||||||
SHR_MEM_UBYTE,
|
|
||||||
SHR_MEM_SBYTE,
|
|
||||||
SHR_MEM_UWORD,
|
|
||||||
SHR_MEM_SWORD,
|
|
||||||
SHR_VAR_UBYTE,
|
|
||||||
SHR_VAR_SBYTE,
|
|
||||||
SHR_VAR_UWORD,
|
|
||||||
SHR_VAR_SWORD,
|
|
||||||
ROL_BYTE,
|
|
||||||
ROL_WORD,
|
|
||||||
ROL_MEM_BYTE,
|
|
||||||
ROL_MEM_WORD,
|
|
||||||
ROL_VAR_BYTE,
|
|
||||||
ROL_VAR_WORD,
|
|
||||||
ROR_BYTE,
|
|
||||||
ROR_WORD,
|
|
||||||
ROR_MEM_BYTE,
|
|
||||||
ROR_MEM_WORD,
|
|
||||||
ROR_VAR_BYTE,
|
|
||||||
ROR_VAR_WORD,
|
|
||||||
ROL2_BYTE,
|
|
||||||
ROL2_WORD,
|
|
||||||
ROL2_MEM_BYTE,
|
|
||||||
ROL2_MEM_WORD,
|
|
||||||
ROL2_VAR_BYTE,
|
|
||||||
ROL2_VAR_WORD,
|
|
||||||
ROR2_BYTE,
|
|
||||||
ROR2_WORD,
|
|
||||||
ROR2_MEM_BYTE,
|
|
||||||
ROR2_MEM_WORD,
|
|
||||||
ROR2_VAR_BYTE,
|
|
||||||
ROR2_VAR_WORD,
|
|
||||||
BITAND_BYTE,
|
|
||||||
BITAND_WORD,
|
|
||||||
BITOR_BYTE,
|
|
||||||
BITOR_WORD,
|
|
||||||
BITXOR_BYTE,
|
|
||||||
BITXOR_WORD,
|
|
||||||
INV_BYTE,
|
|
||||||
INV_WORD,
|
|
||||||
|
|
||||||
// numeric type conversions
|
|
||||||
MSB, // note: lsb is equivalent to CAST_UW_TO_UB or CAST_W_TO_UB
|
|
||||||
MKWORD, // create a word from lsb + msb
|
|
||||||
CAST_UB_TO_B,
|
|
||||||
CAST_UB_TO_UW,
|
|
||||||
CAST_UB_TO_W,
|
|
||||||
CAST_UB_TO_F,
|
|
||||||
CAST_B_TO_UB,
|
|
||||||
CAST_B_TO_UW,
|
|
||||||
CAST_B_TO_W,
|
|
||||||
CAST_B_TO_F,
|
|
||||||
CAST_W_TO_UB,
|
|
||||||
CAST_W_TO_B,
|
|
||||||
CAST_W_TO_UW,
|
|
||||||
CAST_W_TO_F,
|
|
||||||
CAST_UW_TO_UB,
|
|
||||||
CAST_UW_TO_B,
|
|
||||||
CAST_UW_TO_W,
|
|
||||||
CAST_UW_TO_F,
|
|
||||||
CAST_F_TO_UB,
|
|
||||||
CAST_F_TO_B,
|
|
||||||
CAST_F_TO_UW,
|
|
||||||
CAST_F_TO_W,
|
|
||||||
|
|
||||||
// logical operations
|
|
||||||
AND_BYTE,
|
|
||||||
AND_WORD,
|
|
||||||
OR_BYTE,
|
|
||||||
OR_WORD,
|
|
||||||
XOR_BYTE,
|
|
||||||
XOR_WORD,
|
|
||||||
NOT_BYTE,
|
|
||||||
NOT_WORD,
|
|
||||||
|
|
||||||
// increment, decrement
|
|
||||||
INC_VAR_B,
|
|
||||||
INC_VAR_UB,
|
|
||||||
INC_VAR_W,
|
|
||||||
INC_VAR_UW,
|
|
||||||
INC_VAR_F,
|
|
||||||
DEC_VAR_B,
|
|
||||||
DEC_VAR_UB,
|
|
||||||
DEC_VAR_W,
|
|
||||||
DEC_VAR_UW,
|
|
||||||
DEC_VAR_F,
|
|
||||||
INC_MEMORY, // increment direct address
|
|
||||||
DEC_MEMORY, // decrement direct address
|
|
||||||
POP_INC_MEMORY, // increment address from stack
|
|
||||||
POP_DEC_MEMORY, // decrement address from address
|
|
||||||
|
|
||||||
// comparisons
|
|
||||||
LESS_B,
|
|
||||||
LESS_UB,
|
|
||||||
LESS_W,
|
|
||||||
LESS_UW,
|
|
||||||
LESS_F,
|
|
||||||
GREATER_B,
|
|
||||||
GREATER_UB,
|
|
||||||
GREATER_W,
|
|
||||||
GREATER_UW,
|
|
||||||
GREATER_F,
|
|
||||||
LESSEQ_B,
|
|
||||||
LESSEQ_UB,
|
|
||||||
LESSEQ_W,
|
|
||||||
LESSEQ_UW,
|
|
||||||
LESSEQ_F,
|
|
||||||
GREATEREQ_B,
|
|
||||||
GREATEREQ_UB,
|
|
||||||
GREATEREQ_W,
|
|
||||||
GREATEREQ_UW,
|
|
||||||
GREATEREQ_F,
|
|
||||||
EQUAL_BYTE,
|
|
||||||
EQUAL_WORD,
|
|
||||||
EQUAL_F,
|
|
||||||
NOTEQUAL_BYTE,
|
|
||||||
NOTEQUAL_WORD,
|
|
||||||
NOTEQUAL_F,
|
|
||||||
CMP_B, // sets processor status flags based on comparison, instead of pushing a result value
|
|
||||||
CMP_UB, // sets processor status flags based on comparison, instead of pushing a result value
|
|
||||||
CMP_W, // sets processor status flags based on comparison, instead of pushing a result value
|
|
||||||
CMP_UW, // sets processor status flags based on comparison, instead of pushing a result value
|
|
||||||
|
|
||||||
// array access and simple manipulations
|
|
||||||
READ_INDEXED_VAR_BYTE,
|
|
||||||
READ_INDEXED_VAR_WORD,
|
|
||||||
READ_INDEXED_VAR_FLOAT,
|
|
||||||
WRITE_INDEXED_VAR_BYTE,
|
|
||||||
WRITE_INDEXED_VAR_WORD,
|
|
||||||
WRITE_INDEXED_VAR_FLOAT,
|
|
||||||
INC_INDEXED_VAR_B,
|
|
||||||
INC_INDEXED_VAR_UB,
|
|
||||||
INC_INDEXED_VAR_W,
|
|
||||||
INC_INDEXED_VAR_UW,
|
|
||||||
INC_INDEXED_VAR_FLOAT,
|
|
||||||
DEC_INDEXED_VAR_B,
|
|
||||||
DEC_INDEXED_VAR_UB,
|
|
||||||
DEC_INDEXED_VAR_W,
|
|
||||||
DEC_INDEXED_VAR_UW,
|
|
||||||
DEC_INDEXED_VAR_FLOAT,
|
|
||||||
|
|
||||||
// branching, without consuming a value from the stack
|
|
||||||
JUMP,
|
|
||||||
BCS, // branch if carry set
|
|
||||||
BCC, // branch if carry clear
|
|
||||||
BZ, // branch if zero flag
|
|
||||||
BNZ, // branch if not zero flag
|
|
||||||
BNEG, // branch if negative flag
|
|
||||||
BPOS, // branch if not negative flag
|
|
||||||
BVS, // branch if overflow flag
|
|
||||||
BVC, // branch if not overflow flag
|
|
||||||
// branching, based on value on the stack (which is consumed)
|
|
||||||
JZ, // branch if value is zero (byte)
|
|
||||||
JNZ, // branch if value is not zero (byte)
|
|
||||||
JZW, // branch if value is zero (word)
|
|
||||||
JNZW, // branch if value is not zero (word)
|
|
||||||
|
|
||||||
// subroutines
|
|
||||||
CALL,
|
|
||||||
RETURN,
|
|
||||||
SYSCALL,
|
|
||||||
START_PROCDEF,
|
|
||||||
END_PROCDEF,
|
|
||||||
|
|
||||||
// misc
|
|
||||||
SEC, // set carry status flag NOTE: is mostly fake, carry flag is not affected by any numeric operations
|
|
||||||
CLC, // clear carry status flag NOTE: is mostly fake, carry flag is not affected by any numeric operations
|
|
||||||
SEI, // set irq-disable status flag
|
|
||||||
CLI, // clear irq-disable status flag
|
|
||||||
CARRY_TO_A, // load var/register A with carry status bit
|
|
||||||
RSAVE, // save all internal registers and status flags
|
|
||||||
RSAVEX, // save just X (the evaluation stack pointer)
|
|
||||||
RRESTORE, // restore all internal registers and status flags
|
|
||||||
RRESTOREX, // restore just X (the evaluation stack pointer)
|
|
||||||
|
|
||||||
NOP, // do nothing
|
|
||||||
BREAKPOINT, // breakpoint
|
|
||||||
TERMINATE, // end the program
|
|
||||||
LINE, // track source file line number
|
|
||||||
INLINE_ASSEMBLY, // container to hold inline raw assembly code
|
|
||||||
INCLUDE_FILE // directive to include a file at this position in the memory of the program
|
|
||||||
}
|
|
||||||
|
|
||||||
val opcodesWithVarArgument = setOf(
|
|
||||||
Opcode.INC_VAR_B, Opcode.INC_VAR_W, Opcode.DEC_VAR_B, Opcode.DEC_VAR_W,
|
|
||||||
Opcode.INC_VAR_UB, Opcode.INC_VAR_UW, Opcode.DEC_VAR_UB, Opcode.DEC_VAR_UW,
|
|
||||||
Opcode.SHR_VAR_SBYTE, Opcode.SHR_VAR_UBYTE, Opcode.SHR_VAR_SWORD, Opcode.SHR_VAR_UWORD,
|
|
||||||
Opcode.SHL_VAR_BYTE, Opcode.SHL_VAR_WORD,
|
|
||||||
Opcode.ROL_VAR_BYTE, Opcode.ROL_VAR_WORD, Opcode.ROR_VAR_BYTE, Opcode.ROR_VAR_WORD,
|
|
||||||
Opcode.ROL2_VAR_BYTE, Opcode.ROL2_VAR_WORD, Opcode.ROR2_VAR_BYTE, Opcode.ROR2_VAR_WORD,
|
|
||||||
Opcode.POP_VAR_BYTE, Opcode.POP_VAR_WORD, Opcode.POP_VAR_FLOAT,
|
|
||||||
Opcode.PUSH_VAR_BYTE, Opcode.PUSH_VAR_WORD, Opcode.PUSH_VAR_FLOAT, Opcode.PUSH_ADDR_HEAPVAR,
|
|
||||||
Opcode.READ_INDEXED_VAR_BYTE, Opcode.READ_INDEXED_VAR_WORD, Opcode.READ_INDEXED_VAR_FLOAT,
|
|
||||||
Opcode.WRITE_INDEXED_VAR_BYTE, Opcode.WRITE_INDEXED_VAR_WORD, Opcode.WRITE_INDEXED_VAR_FLOAT,
|
|
||||||
Opcode.INC_INDEXED_VAR_UB, Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UW,
|
|
||||||
Opcode.INC_INDEXED_VAR_W, Opcode.INC_INDEXED_VAR_FLOAT,
|
|
||||||
Opcode.DEC_INDEXED_VAR_UB, Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UW,
|
|
||||||
Opcode.DEC_INDEXED_VAR_W, Opcode.DEC_INDEXED_VAR_FLOAT
|
|
||||||
)
|
|
||||||
|
|
||||||
val branchOpcodes = setOf(
|
|
||||||
Opcode.BCS, Opcode.BCC, Opcode.BZ, Opcode.BNZ,
|
|
||||||
Opcode.BNEG, Opcode.BPOS, Opcode.BVS, Opcode.BVC
|
|
||||||
)
|
|
File diff suppressed because it is too large
Load Diff
@ -2,31 +2,48 @@ package prog8.compiler.target.c64
|
|||||||
|
|
||||||
import prog8.compiler.CompilationOptions
|
import prog8.compiler.CompilationOptions
|
||||||
import prog8.compiler.OutputType
|
import prog8.compiler.OutputType
|
||||||
import java.io.File
|
import java.nio.file.Path
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
class AssemblyProgram(val name: String) {
|
class AssemblyProgram(val name: String, outputDir: Path) {
|
||||||
private val assemblyFile = "$name.asm"
|
private val assemblyFile = outputDir.resolve("$name.asm")
|
||||||
private val viceMonListFile = "$name.vice-mon-list"
|
private val prgFile = outputDir.resolve("$name.prg")
|
||||||
|
private val binFile = outputDir.resolve("$name.bin")
|
||||||
|
private val viceMonListFile = outputDir.resolve("$name.vice-mon-list")
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
// 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names
|
||||||
|
val opcodeNames = setOf("adc", "ahx", "alr", "anc", "and", "ane", "arr", "asl", "asr", "axs", "bcc", "bcs",
|
||||||
|
"beq", "bge", "bit", "blt", "bmi", "bne", "bpl", "brk", "bvc", "bvs", "clc",
|
||||||
|
"cld", "cli", "clv", "cmp", "cpx", "cpy", "dcm", "dcp", "dec", "dex", "dey",
|
||||||
|
"eor", "gcc", "gcs", "geq", "gge", "glt", "gmi", "gne", "gpl", "gvc", "gvs",
|
||||||
|
"inc", "ins", "inx", "iny", "isb", "isc", "jam", "jmp", "jsr", "lae", "las",
|
||||||
|
"lax", "lda", "lds", "ldx", "ldy", "lsr", "lxa", "nop", "ora", "pha", "php",
|
||||||
|
"pla", "plp", "rla", "rol", "ror", "rra", "rti", "rts", "sax", "sbc", "sbx",
|
||||||
|
"sec", "sed", "sei", "sha", "shl", "shr", "shs", "shx", "shy", "slo", "sre",
|
||||||
|
"sta", "stx", "sty", "tas", "tax", "tay", "tsx", "txa", "txs", "tya", "xaa")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun assemble(options: CompilationOptions) {
|
fun assemble(options: CompilationOptions) {
|
||||||
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps
|
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps
|
||||||
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch", "-Wall", "-Wno-strict-bool",
|
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
|
||||||
"-Werror", "-Wno-error=long-branch", "--dump-labels", "--vice-labels", "-l", viceMonListFile, "--no-monitor")
|
"-Wall", "-Wno-strict-bool", "-Wno-shadow", "-Werror", "-Wno-error=long-branch",
|
||||||
|
"--dump-labels", "--vice-labels", "-l", viceMonListFile.toString(), "--no-monitor")
|
||||||
|
|
||||||
val outFile = when(options.output) {
|
val outFile = when(options.output) {
|
||||||
OutputType.PRG -> {
|
OutputType.PRG -> {
|
||||||
command.add("--cbm-prg")
|
command.add("--cbm-prg")
|
||||||
println("\nCreating C-64 prg.")
|
println("\nCreating C-64 prg.")
|
||||||
"$name.prg"
|
prgFile
|
||||||
}
|
}
|
||||||
OutputType.RAW -> {
|
OutputType.RAW -> {
|
||||||
command.add("--nostart")
|
command.add("--nostart")
|
||||||
println("\nCreating raw binary.")
|
println("\nCreating raw binary.")
|
||||||
"$name.bin"
|
binFile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
command.addAll(listOf("--output", outFile, assemblyFile))
|
command.addAll(listOf("--output", outFile.toString(), assemblyFile.toString()))
|
||||||
|
|
||||||
val proc = ProcessBuilder(command).inheritIO().start()
|
val proc = ProcessBuilder(command).inheritIO().start()
|
||||||
val result = proc.waitFor()
|
val result = proc.waitFor()
|
||||||
@ -41,8 +58,8 @@ class AssemblyProgram(val name: String) {
|
|||||||
private fun generateBreakpointList() {
|
private fun generateBreakpointList() {
|
||||||
// builds list of breakpoints, appends to monitor list file
|
// builds list of breakpoints, appends to monitor list file
|
||||||
val breakpoints = mutableListOf<String>()
|
val breakpoints = mutableListOf<String>()
|
||||||
val pattern = Regex("""al (\w+) \S+_prog8_breakpoint_\d+.?""") // gather breakpoints by the source label that's generated for them
|
val pattern = Regex("""al (\w+) \S+_prog8_breakpoint_\d+.?""") // gather breakpoints by the source label that"s generated for them
|
||||||
for(line in File(viceMonListFile).readLines()) {
|
for(line in viceMonListFile.toFile().readLines()) {
|
||||||
val match = pattern.matchEntire(line)
|
val match = pattern.matchEntire(line)
|
||||||
if(match!=null)
|
if(match!=null)
|
||||||
breakpoints.add("break \$" + match.groupValues[1])
|
breakpoints.add("break \$" + match.groupValues[1])
|
||||||
@ -51,6 +68,6 @@ class AssemblyProgram(val name: String) {
|
|||||||
breakpoints.add(0, "; vice monitor breakpoint list now follows")
|
breakpoints.add(0, "; vice monitor breakpoint list now follows")
|
||||||
breakpoints.add(1, "; $num breakpoints have been defined")
|
breakpoints.add(1, "; $num breakpoints have been defined")
|
||||||
breakpoints.add(2, "del")
|
breakpoints.add(2, "del")
|
||||||
File(viceMonListFile).appendText(breakpoints.joinToString("\n")+"\n")
|
viceMonListFile.toFile().appendText(breakpoints.joinToString("\n")+"\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,242 +0,0 @@
|
|||||||
package prog8.compiler.target.c64
|
|
||||||
|
|
||||||
import prog8.compiler.CompilationOptions
|
|
||||||
import prog8.compiler.CompilerException
|
|
||||||
import prog8.compiler.Zeropage
|
|
||||||
import prog8.compiler.ZeropageType
|
|
||||||
import java.awt.Color
|
|
||||||
import java.awt.image.BufferedImage
|
|
||||||
import javax.imageio.ImageIO
|
|
||||||
import kotlin.math.absoluteValue
|
|
||||||
import kotlin.math.pow
|
|
||||||
|
|
||||||
|
|
||||||
// 5-byte cbm MFLPT format limitations:
|
|
||||||
const val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255
|
|
||||||
const val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
|
|
||||||
|
|
||||||
const val BASIC_LOAD_ADDRESS = 0x0801
|
|
||||||
const val RAW_LOAD_ADDRESS = 0xc000
|
|
||||||
|
|
||||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
|
||||||
const val ESTACK_LO = 0xce00 // $ce00-$ceff inclusive
|
|
||||||
const val ESTACK_HI = 0xcf00 // $cf00-$cfff inclusive
|
|
||||||
|
|
||||||
|
|
||||||
class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val SCRATCH_B1 = 0x02
|
|
||||||
const val SCRATCH_REG = 0x03 // temp storage for a register
|
|
||||||
const val SCRATCH_REG_X = 0xfa // temp storage for register X (the evaluation stack pointer)
|
|
||||||
const val SCRATCH_W1 = 0xfb // $fb+$fc
|
|
||||||
const val SCRATCH_W2 = 0xfd // $fd+$fe
|
|
||||||
}
|
|
||||||
|
|
||||||
override val exitProgramStrategy: ExitProgramStrategy = when(options.zeropage) {
|
|
||||||
ZeropageType.BASICSAFE -> ExitProgramStrategy.CLEAN_EXIT
|
|
||||||
ZeropageType.FLOATSAFE, ZeropageType.KERNALSAFE, ZeropageType.FULL -> ExitProgramStrategy.SYSTEM_RESET
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
init {
|
|
||||||
if(options.floats && options.zeropage!=ZeropageType.FLOATSAFE && options.zeropage!=ZeropageType.BASICSAFE)
|
|
||||||
throw CompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe'")
|
|
||||||
|
|
||||||
if(options.zeropage == ZeropageType.FULL) {
|
|
||||||
free.addAll(0x04 .. 0xf9)
|
|
||||||
free.add(0xff)
|
|
||||||
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_REG_X, SCRATCH_W1, SCRATCH_W1+1, SCRATCH_W2, SCRATCH_W2+1))
|
|
||||||
free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ
|
|
||||||
} else {
|
|
||||||
if(options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
|
|
||||||
free.addAll(listOf(0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
|
|
||||||
0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
|
|
||||||
0x22, 0x23, 0x24, 0x25,
|
|
||||||
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
|
|
||||||
0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53,
|
|
||||||
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
|
|
||||||
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
|
||||||
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
|
|
||||||
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c,
|
|
||||||
0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
|
|
||||||
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
|
|
||||||
// 0x90-0xfa is 'kernel work storage area'
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
if(options.zeropage == ZeropageType.FLOATSAFE) {
|
|
||||||
// remove the zero page locations used for floating point operations from the free list
|
|
||||||
free.removeAll(listOf(
|
|
||||||
0x12, 0x26, 0x27, 0x28, 0x29, 0x2a,
|
|
||||||
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
|
|
||||||
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
|
||||||
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
|
|
||||||
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xf
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
// add the other free Zp addresses,
|
|
||||||
// these are valid for the C-64 (when no RS232 I/O is performed) but to keep BASIC running fully:
|
|
||||||
free.addAll(listOf(0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0d, 0x0e,
|
|
||||||
0x94, 0x95, 0xa7, 0xa8, 0xa9, 0xaa,
|
|
||||||
0xb5, 0xb6, 0xf7, 0xf8, 0xf9))
|
|
||||||
}
|
|
||||||
assert(SCRATCH_B1 !in free)
|
|
||||||
assert(SCRATCH_REG !in free)
|
|
||||||
assert(SCRATCH_REG_X !in free)
|
|
||||||
assert(SCRATCH_W1 !in free)
|
|
||||||
assert(SCRATCH_W2 !in free)
|
|
||||||
|
|
||||||
for(reserved in options.zpReserved)
|
|
||||||
reserve(reserved)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short) {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val MemorySize = 5
|
|
||||||
|
|
||||||
val zero = Mflpt5(0, 0,0,0,0)
|
|
||||||
fun fromNumber(num: Number): Mflpt5 {
|
|
||||||
// see https://en.wikipedia.org/wiki/Microsoft_Binary_Format
|
|
||||||
// and https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
|
|
||||||
// and https://en.wikipedia.org/wiki/IEEE_754-1985
|
|
||||||
|
|
||||||
val flt = num.toDouble()
|
|
||||||
if(flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE)
|
|
||||||
throw CompilerException("floating point number out of 5-byte mflpt range: $this")
|
|
||||||
if(flt==0.0)
|
|
||||||
return zero
|
|
||||||
|
|
||||||
val sign = if(flt<0.0) 0x80L else 0x00L
|
|
||||||
var exponent = 128 + 32 // 128 is cbm's bias, 32 is this algo's bias
|
|
||||||
var mantissa = flt.absoluteValue
|
|
||||||
|
|
||||||
// if mantissa is too large, shift right and adjust exponent
|
|
||||||
while(mantissa >= 0x100000000) {
|
|
||||||
mantissa /= 2.0
|
|
||||||
exponent ++
|
|
||||||
}
|
|
||||||
// if mantissa is too small, shift left and adjust exponent
|
|
||||||
while(mantissa < 0x80000000) {
|
|
||||||
mantissa *= 2.0
|
|
||||||
exponent --
|
|
||||||
}
|
|
||||||
|
|
||||||
return when {
|
|
||||||
exponent<0 -> zero // underflow, use zero instead
|
|
||||||
exponent>255 -> throw CompilerException("floating point overflow: $this")
|
|
||||||
exponent==0 -> zero
|
|
||||||
else -> {
|
|
||||||
val mantLong = mantissa.toLong()
|
|
||||||
Mflpt5(
|
|
||||||
exponent.toShort(),
|
|
||||||
(mantLong.and(0x7f000000L) ushr 24).or(sign).toShort(),
|
|
||||||
(mantLong.and(0x00ff0000L) ushr 16).toShort(),
|
|
||||||
(mantLong.and(0x0000ff00L) ushr 8).toShort(),
|
|
||||||
(mantLong.and(0x000000ffL)).toShort())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toDouble(): Double {
|
|
||||||
if(this == zero) return 0.0
|
|
||||||
val exp = b0 - 128
|
|
||||||
val sign = (b1.toInt() and 0x80) > 0
|
|
||||||
val number = 0x80000000L.or(b1.toLong() shl 24).or(b2.toLong() shl 16).or(b3.toLong() shl 8).or(b4.toLong())
|
|
||||||
val result = number.toDouble() * (2.0).pow(exp) / 0x100000000
|
|
||||||
return if(sign) -result else result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object Charset {
|
|
||||||
private val normalImg = ImageIO.read(javaClass.getResource("/charset/c64/charset-normal.png"))
|
|
||||||
private val shiftedImg = ImageIO.read(javaClass.getResource("/charset/c64/charset-shifted.png"))
|
|
||||||
|
|
||||||
private fun scanChars(img: BufferedImage): Array<BufferedImage> {
|
|
||||||
|
|
||||||
val transparent = BufferedImage(img.width, img.height, BufferedImage.TYPE_INT_ARGB)
|
|
||||||
transparent.createGraphics().drawImage(img, 0, 0, null)
|
|
||||||
|
|
||||||
val black = Color(0,0,0).rgb
|
|
||||||
val nopixel = Color(0,0,0,0).rgb
|
|
||||||
for(y in 0 until transparent.height) {
|
|
||||||
for(x in 0 until transparent.width) {
|
|
||||||
val col = transparent.getRGB(x, y)
|
|
||||||
if(col==black)
|
|
||||||
transparent.setRGB(x, y, nopixel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val numColumns = transparent.width / 8
|
|
||||||
val charImages = (0..255).map {
|
|
||||||
val charX = it % numColumns
|
|
||||||
val charY = it/ numColumns
|
|
||||||
transparent.getSubimage(charX*8, charY*8, 8, 8)
|
|
||||||
}
|
|
||||||
return charImages.toTypedArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
val normalChars = scanChars(normalImg)
|
|
||||||
val shiftedChars = scanChars(shiftedImg)
|
|
||||||
|
|
||||||
private val coloredNormalChars = mutableMapOf<Short, Array<BufferedImage>>()
|
|
||||||
|
|
||||||
fun getColoredChar(screenCode: Short, color: Short): BufferedImage {
|
|
||||||
val colorIdx = (color % Colors.palette.size).toShort()
|
|
||||||
val chars = coloredNormalChars[colorIdx]
|
|
||||||
if(chars!=null)
|
|
||||||
return chars[screenCode.toInt()]
|
|
||||||
|
|
||||||
val coloredChars = mutableListOf<BufferedImage>()
|
|
||||||
val transparent = Color(0,0,0,0).rgb
|
|
||||||
val rgb = Colors.palette[colorIdx.toInt()].rgb
|
|
||||||
for(c in normalChars) {
|
|
||||||
val colored = c.copy()
|
|
||||||
for(y in 0 until colored.height)
|
|
||||||
for(x in 0 until colored.width) {
|
|
||||||
if(colored.getRGB(x, y)!=transparent) {
|
|
||||||
colored.setRGB(x, y, rgb)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
coloredChars.add(colored)
|
|
||||||
}
|
|
||||||
coloredNormalChars[colorIdx] = coloredChars.toTypedArray()
|
|
||||||
return coloredNormalChars.getValue(colorIdx)[screenCode.toInt()]
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun BufferedImage.copy(): BufferedImage {
|
|
||||||
val bcopy = BufferedImage(this.width, this.height, this.type)
|
|
||||||
val g = bcopy.graphics
|
|
||||||
g.drawImage(this, 0, 0, null)
|
|
||||||
g.dispose()
|
|
||||||
return bcopy
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
object Colors {
|
|
||||||
val palette = listOf( // this is Pepto's Commodore-64 palette http://www.pepto.de/projects/colorvic/
|
|
||||||
Color(0x000000), // 0 = black
|
|
||||||
Color(0xFFFFFF), // 1 = white
|
|
||||||
Color(0x813338), // 2 = red
|
|
||||||
Color(0x75cec8), // 3 = cyan
|
|
||||||
Color(0x8e3c97), // 4 = purple
|
|
||||||
Color(0x56ac4d), // 5 = green
|
|
||||||
Color(0x2e2c9b), // 6 = blue
|
|
||||||
Color(0xedf171), // 7 = yellow
|
|
||||||
Color(0x8e5029), // 8 = orange
|
|
||||||
Color(0x553800), // 9 = brown
|
|
||||||
Color(0xc46c71), // 10 = light red
|
|
||||||
Color(0x4a4a4a), // 11 = dark grey
|
|
||||||
Color(0x7b7b7b), // 12 = medium grey
|
|
||||||
Color(0xa9ff9f), // 13 = light green
|
|
||||||
Color(0x706deb), // 14 = light blue
|
|
||||||
Color(0xb2b2b2) // 15 = light grey
|
|
||||||
)
|
|
||||||
}
|
|
255
compiler/src/prog8/compiler/target/c64/MachineDefinition.kt
Normal file
255
compiler/src/prog8/compiler/target/c64/MachineDefinition.kt
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
package prog8.compiler.target.c64
|
||||||
|
|
||||||
|
import prog8.compiler.CompilationOptions
|
||||||
|
import prog8.compiler.CompilerException
|
||||||
|
import prog8.compiler.Zeropage
|
||||||
|
import prog8.compiler.ZeropageType
|
||||||
|
import java.awt.Color
|
||||||
|
import java.awt.image.BufferedImage
|
||||||
|
import javax.imageio.ImageIO
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
import kotlin.math.pow
|
||||||
|
|
||||||
|
object MachineDefinition {
|
||||||
|
|
||||||
|
// 5-byte cbm MFLPT format limitations:
|
||||||
|
const val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255
|
||||||
|
const val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
|
||||||
|
|
||||||
|
const val BASIC_LOAD_ADDRESS = 0x0801
|
||||||
|
const val RAW_LOAD_ADDRESS = 0xc000
|
||||||
|
|
||||||
|
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
||||||
|
// and some heavily used string constants derived from the two values above
|
||||||
|
const val ESTACK_LO_VALUE = 0xce00 // $ce00-$ceff inclusive
|
||||||
|
const val ESTACK_HI_VALUE = 0xcf00 // $cf00-$cfff inclusive
|
||||||
|
const val ESTACK_LO_HEX = "\$ce00"
|
||||||
|
const val ESTACK_LO_PLUS1_HEX = "\$ce01"
|
||||||
|
const val ESTACK_LO_PLUS2_HEX = "\$ce02"
|
||||||
|
const val ESTACK_HI_HEX = "\$cf00"
|
||||||
|
const val ESTACK_HI_PLUS1_HEX = "\$cf01"
|
||||||
|
const val ESTACK_HI_PLUS2_HEX = "\$cf02"
|
||||||
|
|
||||||
|
|
||||||
|
class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val SCRATCH_B1 = 0x02
|
||||||
|
const val SCRATCH_REG = 0x03 // temp storage for a register
|
||||||
|
const val SCRATCH_REG_X = 0xfa // temp storage for register X (the evaluation stack pointer)
|
||||||
|
const val SCRATCH_W1 = 0xfb // $fb+$fc
|
||||||
|
const val SCRATCH_W2 = 0xfd // $fd+$fe
|
||||||
|
}
|
||||||
|
|
||||||
|
override val exitProgramStrategy: ExitProgramStrategy = when (options.zeropage) {
|
||||||
|
ZeropageType.BASICSAFE, ZeropageType.DONTUSE -> ExitProgramStrategy.CLEAN_EXIT
|
||||||
|
ZeropageType.FLOATSAFE, ZeropageType.KERNALSAFE, ZeropageType.FULL -> ExitProgramStrategy.SYSTEM_RESET
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (options.floats && options.zeropage !in setOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
|
||||||
|
throw CompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
|
||||||
|
|
||||||
|
if (options.zeropage == ZeropageType.FULL) {
|
||||||
|
free.addAll(0x04..0xf9)
|
||||||
|
free.add(0xff)
|
||||||
|
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_REG_X, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
|
||||||
|
free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ
|
||||||
|
} else {
|
||||||
|
if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
|
||||||
|
free.addAll(listOf(0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
|
||||||
|
0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
|
||||||
|
0x22, 0x23, 0x24, 0x25,
|
||||||
|
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
|
||||||
|
0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53,
|
||||||
|
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
|
||||||
|
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
||||||
|
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
|
||||||
|
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c,
|
||||||
|
0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
|
||||||
|
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
|
||||||
|
// 0x90-0xfa is 'kernel work storage area'
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.zeropage == ZeropageType.FLOATSAFE) {
|
||||||
|
// remove the zero page locations used for floating point operations from the free list
|
||||||
|
free.removeAll(listOf(
|
||||||
|
0x12, 0x26, 0x27, 0x28, 0x29, 0x2a,
|
||||||
|
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
|
||||||
|
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
||||||
|
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
|
||||||
|
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xf
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
if(options.zeropage!=ZeropageType.DONTUSE) {
|
||||||
|
// add the other free Zp addresses,
|
||||||
|
// these are valid for the C-64 (when no RS232 I/O is performed) but to keep BASIC running fully:
|
||||||
|
free.addAll(listOf(0x04, 0x05, 0x06, 0x0a, 0x0e,
|
||||||
|
0x94, 0x95, 0xa7, 0xa8, 0xa9, 0xaa,
|
||||||
|
0xb5, 0xb6, 0xf7, 0xf8, 0xf9))
|
||||||
|
} else {
|
||||||
|
// don't use the zeropage at all
|
||||||
|
free.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(SCRATCH_B1 !in free)
|
||||||
|
assert(SCRATCH_REG !in free)
|
||||||
|
assert(SCRATCH_REG_X !in free)
|
||||||
|
assert(SCRATCH_W1 !in free)
|
||||||
|
assert(SCRATCH_W2 !in free)
|
||||||
|
|
||||||
|
for (reserved in options.zpReserved)
|
||||||
|
reserve(reserved)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val MemorySize = 5
|
||||||
|
|
||||||
|
val zero = Mflpt5(0, 0, 0, 0, 0)
|
||||||
|
fun fromNumber(num: Number): Mflpt5 {
|
||||||
|
// see https://en.wikipedia.org/wiki/Microsoft_Binary_Format
|
||||||
|
// and https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
|
||||||
|
// and https://en.wikipedia.org/wiki/IEEE_754-1985
|
||||||
|
|
||||||
|
val flt = num.toDouble()
|
||||||
|
if (flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE)
|
||||||
|
throw CompilerException("floating point number out of 5-byte mflpt range: $this")
|
||||||
|
if (flt == 0.0)
|
||||||
|
return zero
|
||||||
|
|
||||||
|
val sign = if (flt < 0.0) 0x80L else 0x00L
|
||||||
|
var exponent = 128 + 32 // 128 is cbm's bias, 32 is this algo's bias
|
||||||
|
var mantissa = flt.absoluteValue
|
||||||
|
|
||||||
|
// if mantissa is too large, shift right and adjust exponent
|
||||||
|
while (mantissa >= 0x100000000) {
|
||||||
|
mantissa /= 2.0
|
||||||
|
exponent++
|
||||||
|
}
|
||||||
|
// if mantissa is too small, shift left and adjust exponent
|
||||||
|
while (mantissa < 0x80000000) {
|
||||||
|
mantissa *= 2.0
|
||||||
|
exponent--
|
||||||
|
}
|
||||||
|
|
||||||
|
return when {
|
||||||
|
exponent < 0 -> zero // underflow, use zero instead
|
||||||
|
exponent > 255 -> throw CompilerException("floating point overflow: $this")
|
||||||
|
exponent == 0 -> zero
|
||||||
|
else -> {
|
||||||
|
val mantLong = mantissa.toLong()
|
||||||
|
Mflpt5(
|
||||||
|
exponent.toShort(),
|
||||||
|
(mantLong.and(0x7f000000L) ushr 24).or(sign).toShort(),
|
||||||
|
(mantLong.and(0x00ff0000L) ushr 16).toShort(),
|
||||||
|
(mantLong.and(0x0000ff00L) ushr 8).toShort(),
|
||||||
|
(mantLong.and(0x000000ffL)).toShort())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toDouble(): Double {
|
||||||
|
if (this == zero) return 0.0
|
||||||
|
val exp = b0 - 128
|
||||||
|
val sign = (b1.toInt() and 0x80) > 0
|
||||||
|
val number = 0x80000000L.or(b1.toLong() shl 24).or(b2.toLong() shl 16).or(b3.toLong() shl 8).or(b4.toLong())
|
||||||
|
val result = number.toDouble() * (2.0).pow(exp) / 0x100000000
|
||||||
|
return if (sign) -result else result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object Charset {
|
||||||
|
private val normalImg = ImageIO.read(javaClass.getResource("/charset/c64/charset-normal.png"))
|
||||||
|
private val shiftedImg = ImageIO.read(javaClass.getResource("/charset/c64/charset-shifted.png"))
|
||||||
|
|
||||||
|
private fun scanChars(img: BufferedImage): Array<BufferedImage> {
|
||||||
|
|
||||||
|
val transparent = BufferedImage(img.width, img.height, BufferedImage.TYPE_INT_ARGB)
|
||||||
|
transparent.createGraphics().drawImage(img, 0, 0, null)
|
||||||
|
|
||||||
|
val black = Color(0, 0, 0).rgb
|
||||||
|
val nopixel = Color(0, 0, 0, 0).rgb
|
||||||
|
for (y in 0 until transparent.height) {
|
||||||
|
for (x in 0 until transparent.width) {
|
||||||
|
val col = transparent.getRGB(x, y)
|
||||||
|
if (col == black)
|
||||||
|
transparent.setRGB(x, y, nopixel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val numColumns = transparent.width / 8
|
||||||
|
val charImages = (0..255).map {
|
||||||
|
val charX = it % numColumns
|
||||||
|
val charY = it / numColumns
|
||||||
|
transparent.getSubimage(charX * 8, charY * 8, 8, 8)
|
||||||
|
}
|
||||||
|
return charImages.toTypedArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
val normalChars = scanChars(normalImg)
|
||||||
|
val shiftedChars = scanChars(shiftedImg)
|
||||||
|
|
||||||
|
private val coloredNormalChars = mutableMapOf<Short, Array<BufferedImage>>()
|
||||||
|
|
||||||
|
fun getColoredChar(screenCode: Short, color: Short): BufferedImage {
|
||||||
|
val colorIdx = (color % colorPalette.size).toShort()
|
||||||
|
val chars = coloredNormalChars[colorIdx]
|
||||||
|
if (chars != null)
|
||||||
|
return chars[screenCode.toInt()]
|
||||||
|
|
||||||
|
val coloredChars = mutableListOf<BufferedImage>()
|
||||||
|
val transparent = Color(0, 0, 0, 0).rgb
|
||||||
|
val rgb = colorPalette[colorIdx.toInt()].rgb
|
||||||
|
for (c in normalChars) {
|
||||||
|
val colored = c.copy()
|
||||||
|
for (y in 0 until colored.height)
|
||||||
|
for (x in 0 until colored.width) {
|
||||||
|
if (colored.getRGB(x, y) != transparent) {
|
||||||
|
colored.setRGB(x, y, rgb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
coloredChars.add(colored)
|
||||||
|
}
|
||||||
|
coloredNormalChars[colorIdx] = coloredChars.toTypedArray()
|
||||||
|
return coloredNormalChars.getValue(colorIdx)[screenCode.toInt()]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun BufferedImage.copy(): BufferedImage {
|
||||||
|
val bcopy = BufferedImage(this.width, this.height, this.type)
|
||||||
|
val g = bcopy.graphics
|
||||||
|
g.drawImage(this, 0, 0, null)
|
||||||
|
g.dispose()
|
||||||
|
return bcopy
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val colorPalette = listOf( // this is Pepto's Commodore-64 palette http://www.pepto.de/projects/colorvic/
|
||||||
|
Color(0x000000), // 0 = black
|
||||||
|
Color(0xFFFFFF), // 1 = white
|
||||||
|
Color(0x813338), // 2 = red
|
||||||
|
Color(0x75cec8), // 3 = cyan
|
||||||
|
Color(0x8e3c97), // 4 = purple
|
||||||
|
Color(0x56ac4d), // 5 = green
|
||||||
|
Color(0x2e2c9b), // 6 = blue
|
||||||
|
Color(0xedf171), // 7 = yellow
|
||||||
|
Color(0x8e5029), // 8 = orange
|
||||||
|
Color(0x553800), // 9 = brown
|
||||||
|
Color(0xc46c71), // 10 = light red
|
||||||
|
Color(0x4a4a4a), // 11 = dark grey
|
||||||
|
Color(0x7b7b7b), // 12 = medium grey
|
||||||
|
Color(0xa9ff9f), // 13 = light green
|
||||||
|
Color(0x706deb), // 14 = light blue
|
||||||
|
Color(0xb2b2b2) // 15 = light grey
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,48 @@
|
|||||||
|
package prog8.compiler.target.c64.codegen
|
||||||
|
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.AstException
|
||||||
|
import prog8.ast.base.NameError
|
||||||
|
import prog8.ast.processing.IAstModifyingVisitor
|
||||||
|
import prog8.ast.statements.AnonymousScope
|
||||||
|
import prog8.ast.statements.Statement
|
||||||
|
import prog8.ast.statements.VarDecl
|
||||||
|
|
||||||
|
class AnonymousScopeVarsCleanup(val program: Program): IAstModifyingVisitor {
|
||||||
|
private val checkResult: MutableList<AstException> = mutableListOf()
|
||||||
|
private val varsToMove: MutableMap<AnonymousScope, List<VarDecl>> = mutableMapOf()
|
||||||
|
|
||||||
|
fun result(): List<AstException> {
|
||||||
|
return checkResult
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(program: Program) {
|
||||||
|
varsToMove.clear()
|
||||||
|
super.visit(program)
|
||||||
|
for((scope, decls) in varsToMove) {
|
||||||
|
val sub = scope.definingSubroutine()!!
|
||||||
|
val existingVariables = sub.statements.filterIsInstance<VarDecl>().associateBy { it.name }
|
||||||
|
var conflicts = false
|
||||||
|
decls.forEach {
|
||||||
|
val existing = existingVariables[it.name]
|
||||||
|
if (existing!=null) {
|
||||||
|
checkResult.add(NameError("variable ${it.name} already defined in subroutine ${sub.name} at ${existing.position}", it.position))
|
||||||
|
conflicts = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!conflicts) {
|
||||||
|
decls.forEach { scope.remove(it) }
|
||||||
|
sub.statements.addAll(0, decls)
|
||||||
|
decls.forEach { it.parent = sub }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(scope: AnonymousScope): Statement {
|
||||||
|
val scope2 = super.visit(scope) as AnonymousScope
|
||||||
|
val vardecls = scope2.statements.filterIsInstance<VarDecl>()
|
||||||
|
varsToMove[scope2] = vardecls
|
||||||
|
return scope2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
882
compiler/src/prog8/compiler/target/c64/codegen/AsmGen.kt
Normal file
882
compiler/src/prog8/compiler/target/c64/codegen/AsmGen.kt
Normal file
@ -0,0 +1,882 @@
|
|||||||
|
package prog8.compiler.target.c64.codegen
|
||||||
|
|
||||||
|
import prog8.ast.Node
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.antlr.escape
|
||||||
|
import prog8.ast.base.*
|
||||||
|
import prog8.ast.expressions.*
|
||||||
|
import prog8.ast.statements.*
|
||||||
|
import prog8.compiler.*
|
||||||
|
import prog8.compiler.target.c64.AssemblyProgram
|
||||||
|
import prog8.compiler.target.c64.MachineDefinition
|
||||||
|
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_HEX
|
||||||
|
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX
|
||||||
|
import prog8.compiler.target.c64.Petscii
|
||||||
|
import prog8.functions.BuiltinFunctions
|
||||||
|
import prog8.functions.FunctionSignature
|
||||||
|
import java.math.RoundingMode
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.util.ArrayDeque
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
|
|
||||||
|
internal class AssemblyError(msg: String) : RuntimeException(msg)
|
||||||
|
|
||||||
|
|
||||||
|
internal class AsmGen(private val program: Program,
|
||||||
|
private val zeropage: Zeropage,
|
||||||
|
private val options: CompilationOptions,
|
||||||
|
private val outputDir: Path) {
|
||||||
|
|
||||||
|
private val assemblyLines = mutableListOf<String>()
|
||||||
|
private val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
|
||||||
|
private val allocatedZeropageVariables = mutableMapOf<String, Pair<Int, DataType>>()
|
||||||
|
private val breakpointLabels = mutableListOf<String>()
|
||||||
|
private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this)
|
||||||
|
private val forloopsAsmGen = ForLoopsAsmGen(program, this)
|
||||||
|
private val postincrdecrAsmGen = PostIncrDecrAsmGen(program, this)
|
||||||
|
private val functioncallAsmGen = FunctionCallAsmGen(program, this)
|
||||||
|
private val assignmentAsmGen = AssignmentAsmGen(program, this)
|
||||||
|
private val expressionsAsmGen = ExpressionsAsmGen(program, this)
|
||||||
|
internal val loopEndLabels = ArrayDeque<String>()
|
||||||
|
internal val loopContinueLabels = ArrayDeque<String>()
|
||||||
|
|
||||||
|
internal fun compileToAssembly(optimize: Boolean): AssemblyProgram {
|
||||||
|
assemblyLines.clear()
|
||||||
|
loopEndLabels.clear()
|
||||||
|
loopContinueLabels.clear()
|
||||||
|
|
||||||
|
println("Generating assembly code... ")
|
||||||
|
|
||||||
|
header()
|
||||||
|
val allBlocks = program.allBlocks()
|
||||||
|
if(allBlocks.first().name != "main")
|
||||||
|
throw AssemblyError("first block should be 'main'")
|
||||||
|
for(b in program.allBlocks())
|
||||||
|
block2asm(b)
|
||||||
|
footer()
|
||||||
|
|
||||||
|
if(optimize) {
|
||||||
|
var optimizationsDone = 1
|
||||||
|
while (optimizationsDone > 0) {
|
||||||
|
optimizationsDone = optimizeAssembly(assemblyLines)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val outputFile = outputDir.resolve("${program.name}.asm").toFile()
|
||||||
|
outputFile.printWriter().use {
|
||||||
|
for (line in assemblyLines) { it.println(line) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return AssemblyProgram(program.name, outputDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun header() {
|
||||||
|
val ourName = this.javaClass.name
|
||||||
|
out("; 6502 assembly code for '${program.name}'")
|
||||||
|
out("; generated by $ourName on ${LocalDateTime.now().withNano(0)}")
|
||||||
|
out("; assembler syntax is for the 64tasm cross-assembler")
|
||||||
|
out("; output options: output=${options.output} launcher=${options.launcher} zp=${options.zeropage}")
|
||||||
|
out("\n.cpu '6502'\n.enc 'none'\n")
|
||||||
|
|
||||||
|
program.actualLoadAddress = program.definedLoadAddress
|
||||||
|
if (program.actualLoadAddress == 0) // fix load address
|
||||||
|
program.actualLoadAddress = if (options.launcher == LauncherType.BASIC)
|
||||||
|
MachineDefinition.BASIC_LOAD_ADDRESS else MachineDefinition.RAW_LOAD_ADDRESS
|
||||||
|
|
||||||
|
when {
|
||||||
|
options.launcher == LauncherType.BASIC -> {
|
||||||
|
if (program.actualLoadAddress != 0x0801)
|
||||||
|
throw AssemblyError("BASIC output must have load address $0801")
|
||||||
|
out("; ---- basic program with sys call ----")
|
||||||
|
out("* = ${program.actualLoadAddress.toHex()}")
|
||||||
|
val year = LocalDate.now().year
|
||||||
|
out(" .word (+), $year")
|
||||||
|
out(" .null $9e, format(' %d ', _prog8_entrypoint), $3a, $8f, ' prog8 by idj'")
|
||||||
|
out("+\t.word 0")
|
||||||
|
out("_prog8_entrypoint\t; assembly code starts here\n")
|
||||||
|
out(" jsr prog8_lib.init_system")
|
||||||
|
}
|
||||||
|
options.output == OutputType.PRG -> {
|
||||||
|
out("; ---- program without basic sys call ----")
|
||||||
|
out("* = ${program.actualLoadAddress.toHex()}\n")
|
||||||
|
out(" jsr prog8_lib.init_system")
|
||||||
|
}
|
||||||
|
options.output == OutputType.RAW -> {
|
||||||
|
out("; ---- raw assembler program ----")
|
||||||
|
out("* = ${program.actualLoadAddress.toHex()}\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zeropage.exitProgramStrategy != Zeropage.ExitProgramStrategy.CLEAN_EXIT) {
|
||||||
|
// disable shift-commodore charset switching and run/stop key
|
||||||
|
out(" lda #$80")
|
||||||
|
out(" lda #$80")
|
||||||
|
out(" sta 657\t; disable charset switching")
|
||||||
|
out(" lda #239")
|
||||||
|
out(" sta 808\t; disable run/stop key")
|
||||||
|
}
|
||||||
|
|
||||||
|
out(" ldx #\$ff\t; init estack pointer")
|
||||||
|
|
||||||
|
out(" ; initialize the variables in each block")
|
||||||
|
for (block in program.allBlocks()) {
|
||||||
|
val initVarsSub = block.statements.singleOrNull { it is Subroutine && it.name == initvarsSubName }
|
||||||
|
if(initVarsSub!=null)
|
||||||
|
out(" jsr ${block.name}.$initvarsSubName")
|
||||||
|
}
|
||||||
|
|
||||||
|
out(" clc")
|
||||||
|
when (zeropage.exitProgramStrategy) {
|
||||||
|
Zeropage.ExitProgramStrategy.CLEAN_EXIT -> {
|
||||||
|
out(" jmp main.start\t; jump to program entrypoint")
|
||||||
|
}
|
||||||
|
Zeropage.ExitProgramStrategy.SYSTEM_RESET -> {
|
||||||
|
out(" jsr main.start\t; call program entrypoint")
|
||||||
|
out(" jmp (c64.RESET_VEC)\t; cold reset")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out("")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun footer() {
|
||||||
|
// the global list of all floating point constants for the whole program
|
||||||
|
out("; global float constants")
|
||||||
|
for (flt in globalFloatConsts) {
|
||||||
|
val mflpt5 = MachineDefinition.Mflpt5.fromNumber(flt.key)
|
||||||
|
val floatFill = makeFloatFill(mflpt5)
|
||||||
|
val floatvalue = flt.key
|
||||||
|
out("${flt.value}\t.byte $floatFill ; float $floatvalue")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun block2asm(block: Block) {
|
||||||
|
out("\n\n; ---- block: '${block.name}' ----")
|
||||||
|
out("${block.name}\t" + (if("force_output" in block.options()) ".block\n" else ".proc\n"))
|
||||||
|
|
||||||
|
if(block.address!=null) {
|
||||||
|
out(".cerror * > ${block.address.toHex()}, 'block address overlaps by ', *-${block.address.toHex()},' bytes'")
|
||||||
|
out("* = ${block.address.toHex()}")
|
||||||
|
}
|
||||||
|
|
||||||
|
outputSourceLine(block)
|
||||||
|
zeropagevars2asm(block.statements)
|
||||||
|
memdefs2asm(block.statements)
|
||||||
|
vardecls2asm(block.statements)
|
||||||
|
out("\n; subroutines in this block")
|
||||||
|
|
||||||
|
// first translate regular statements, and then put the subroutines at the end.
|
||||||
|
val (subroutine, stmts) = block.statements.partition { it is Subroutine }
|
||||||
|
stmts.forEach { translate(it) }
|
||||||
|
subroutine.forEach { translateSubroutine(it as Subroutine) }
|
||||||
|
|
||||||
|
out(if("force_output" in block.options()) "\n\t.bend\n" else "\n\t.pend\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
private var generatedLabelSequenceNumber: Int = 0
|
||||||
|
|
||||||
|
internal fun makeLabel(postfix: String): String {
|
||||||
|
generatedLabelSequenceNumber++
|
||||||
|
return "_prog8_label_${generatedLabelSequenceNumber}_$postfix"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun outputSourceLine(node: Node) {
|
||||||
|
out(" ;\tsrc line: ${node.position.file}:${node.position.line}")
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun out(str: String, splitlines: Boolean = true) {
|
||||||
|
val fragment = (if(" | " in str) str.replace("|", "\n") else str).trim('\n')
|
||||||
|
|
||||||
|
if (splitlines) {
|
||||||
|
for (line in fragment.split('\n')) {
|
||||||
|
val trimmed = if (line.startsWith(' ')) "\t" + line.trim() else line.trim()
|
||||||
|
// trimmed = trimmed.replace(Regex("^\\+\\s+"), "+\t") // sanitize local label indentation
|
||||||
|
assemblyLines.add(trimmed)
|
||||||
|
}
|
||||||
|
} else assemblyLines.add(fragment)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makeFloatFill(flt: MachineDefinition.Mflpt5): String {
|
||||||
|
val b0 = "$" + flt.b0.toString(16).padStart(2, '0')
|
||||||
|
val b1 = "$" + flt.b1.toString(16).padStart(2, '0')
|
||||||
|
val b2 = "$" + flt.b2.toString(16).padStart(2, '0')
|
||||||
|
val b3 = "$" + flt.b3.toString(16).padStart(2, '0')
|
||||||
|
val b4 = "$" + flt.b4.toString(16).padStart(2, '0')
|
||||||
|
return "$b0, $b1, $b2, $b3, $b4"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun encodeStr(str: String, dt: DataType): List<Short> {
|
||||||
|
return when(dt) {
|
||||||
|
DataType.STR -> {
|
||||||
|
val bytes = Petscii.encodePetscii(str, true)
|
||||||
|
bytes.plus(0)
|
||||||
|
}
|
||||||
|
DataType.STR_S -> {
|
||||||
|
val bytes = Petscii.encodeScreencode(str, true)
|
||||||
|
bytes.plus(0)
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("invalid str type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun zeropagevars2asm(statements: List<Statement>) {
|
||||||
|
out("; vars allocated on zeropage")
|
||||||
|
val variables = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR }
|
||||||
|
for(variable in variables) {
|
||||||
|
// should NOT allocate subroutine parameters on the zero page
|
||||||
|
val fullName = variable.scopedname
|
||||||
|
val zpVar = allocatedZeropageVariables[fullName]
|
||||||
|
if(zpVar==null) {
|
||||||
|
// This var is not on the ZP yet. Attempt to move it there (if it's not a float, those take up too much space)
|
||||||
|
if(variable.zeropage != ZeropageWish.NOT_IN_ZEROPAGE &&
|
||||||
|
variable.datatype in zeropage.allowedDatatypes
|
||||||
|
&& variable.datatype != DataType.FLOAT
|
||||||
|
&& options.zeropage != ZeropageType.DONTUSE) {
|
||||||
|
try {
|
||||||
|
val address = zeropage.allocate(fullName, variable.datatype, null)
|
||||||
|
out("${variable.name} = $address\t; auto zp ${variable.datatype}")
|
||||||
|
// make sure we add the var to the set of zpvars for this block
|
||||||
|
allocatedZeropageVariables[fullName] = Pair(address, variable.datatype)
|
||||||
|
} catch (x: ZeropageDepletedError) {
|
||||||
|
// leave it as it is.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun vardecl2asm(decl: VarDecl) {
|
||||||
|
when (decl.datatype) {
|
||||||
|
DataType.UBYTE -> out("${decl.name}\t.byte 0")
|
||||||
|
DataType.BYTE -> out("${decl.name}\t.char 0")
|
||||||
|
DataType.UWORD -> out("${decl.name}\t.word 0")
|
||||||
|
DataType.WORD -> out("${decl.name}\t.sint 0")
|
||||||
|
DataType.FLOAT -> out("${decl.name}\t.byte 0,0,0,0,0 ; float")
|
||||||
|
DataType.STRUCT -> {} // is flattened
|
||||||
|
DataType.STR, DataType.STR_S -> {
|
||||||
|
val string = (decl.value as StringLiteralValue).value
|
||||||
|
val encoded = encodeStr(string, decl.datatype)
|
||||||
|
outputStringvar(decl, encoded)
|
||||||
|
}
|
||||||
|
DataType.ARRAY_UB -> {
|
||||||
|
val data = makeArrayFillDataUnsigned(decl)
|
||||||
|
if (data.size <= 16)
|
||||||
|
out("${decl.name}\t.byte ${data.joinToString()}")
|
||||||
|
else {
|
||||||
|
out(decl.name)
|
||||||
|
for (chunk in data.chunked(16))
|
||||||
|
out(" .byte " + chunk.joinToString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.ARRAY_B -> {
|
||||||
|
val data = makeArrayFillDataSigned(decl)
|
||||||
|
if (data.size <= 16)
|
||||||
|
out("${decl.name}\t.char ${data.joinToString()}")
|
||||||
|
else {
|
||||||
|
out(decl.name)
|
||||||
|
for (chunk in data.chunked(16))
|
||||||
|
out(" .char " + chunk.joinToString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.ARRAY_UW -> {
|
||||||
|
val data = makeArrayFillDataUnsigned(decl)
|
||||||
|
if (data.size <= 16)
|
||||||
|
out("${decl.name}\t.word ${data.joinToString()}")
|
||||||
|
else {
|
||||||
|
out(decl.name)
|
||||||
|
for (chunk in data.chunked(16))
|
||||||
|
out(" .word " + chunk.joinToString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.ARRAY_W -> {
|
||||||
|
val data = makeArrayFillDataSigned(decl)
|
||||||
|
if (data.size <= 16)
|
||||||
|
out("${decl.name}\t.sint ${data.joinToString()}")
|
||||||
|
else {
|
||||||
|
out(decl.name)
|
||||||
|
for (chunk in data.chunked(16))
|
||||||
|
out(" .sint " + chunk.joinToString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.ARRAY_F -> {
|
||||||
|
val array = (decl.value as ArrayLiteralValue).value
|
||||||
|
val floatFills = array.map {
|
||||||
|
val number = (it as NumericLiteralValue).number
|
||||||
|
makeFloatFill(MachineDefinition.Mflpt5.fromNumber(number))
|
||||||
|
}
|
||||||
|
out(decl.name)
|
||||||
|
for (f in array.zip(floatFills))
|
||||||
|
out(" .byte ${f.second} ; float ${f.first}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun memdefs2asm(statements: List<Statement>) {
|
||||||
|
out("\n; memdefs and kernel subroutines")
|
||||||
|
val memvars = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.MEMORY || it.type==VarDeclType.CONST }
|
||||||
|
for(m in memvars) {
|
||||||
|
out(" ${m.name} = ${(m.value as NumericLiteralValue).number.toHex()}")
|
||||||
|
}
|
||||||
|
val asmSubs = statements.filterIsInstance<Subroutine>().filter { it.isAsmSubroutine }
|
||||||
|
for(sub in asmSubs) {
|
||||||
|
if(sub.asmAddress!=null) {
|
||||||
|
if(sub.statements.isNotEmpty())
|
||||||
|
throw AssemblyError("kernel subroutine cannot have statements")
|
||||||
|
out(" ${sub.name} = ${sub.asmAddress.toHex()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun vardecls2asm(statements: List<Statement>) {
|
||||||
|
out("\n; non-zeropage variables")
|
||||||
|
val vars = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR }
|
||||||
|
|
||||||
|
// first output the flattened struct member variables *in order*
|
||||||
|
// after that, the other variables sorted by their datatype
|
||||||
|
|
||||||
|
val (structMembers, normalVars) = vars.partition { it.struct!=null }
|
||||||
|
structMembers.forEach { vardecl2asm(it) }
|
||||||
|
|
||||||
|
// special treatment for string types: merge strings that are identical
|
||||||
|
val encodedstringVars = normalVars
|
||||||
|
.filter {it.datatype in StringDatatypes }
|
||||||
|
.map { it to encodeStr((it.value as StringLiteralValue).value, it.datatype) }
|
||||||
|
.groupBy({it.second}, {it.first})
|
||||||
|
for((encoded, variables) in encodedstringVars) {
|
||||||
|
variables.dropLast(1).forEach { out(it.name) }
|
||||||
|
val lastvar = variables.last()
|
||||||
|
outputStringvar(lastvar, encoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
// non-string variables
|
||||||
|
normalVars.filter{ it.datatype !in StringDatatypes}.sortedBy { it.datatype }.forEach {
|
||||||
|
if(it.scopedname !in allocatedZeropageVariables)
|
||||||
|
vardecl2asm(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun outputStringvar(lastvar: VarDecl, encoded: List<Short>) {
|
||||||
|
val string = (lastvar.value as StringLiteralValue).value
|
||||||
|
out("${lastvar.name}\t; ${lastvar.datatype} \"${escape(string).replace("\u0000", "<NULL>")}\"")
|
||||||
|
val outputBytes = encoded.map { "$" + it.toString(16).padStart(2, '0') }
|
||||||
|
for (chunk in outputBytes.chunked(16))
|
||||||
|
out(" .byte " + chunk.joinToString())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makeArrayFillDataUnsigned(decl: VarDecl): List<String> {
|
||||||
|
val array = (decl.value as ArrayLiteralValue).value
|
||||||
|
return when {
|
||||||
|
decl.datatype == DataType.ARRAY_UB ->
|
||||||
|
// byte array can never contain pointer-to types, so treat values as all integers
|
||||||
|
array.map {
|
||||||
|
val number = (it as NumericLiteralValue).number.toInt()
|
||||||
|
"$"+number.toString(16).padStart(2, '0')
|
||||||
|
}
|
||||||
|
decl.datatype== DataType.ARRAY_UW -> array.map {
|
||||||
|
if(it is NumericLiteralValue) {
|
||||||
|
"$" + it.number.toInt().toString(16).padStart(4, '0')
|
||||||
|
} else {
|
||||||
|
(it as AddressOf).identifier.nameInSource.joinToString(".")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("invalid arraysize type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makeArrayFillDataSigned(decl: VarDecl): List<String> {
|
||||||
|
val array = (decl.value as ArrayLiteralValue).value
|
||||||
|
|
||||||
|
return when {
|
||||||
|
decl.datatype == DataType.ARRAY_UB ->
|
||||||
|
// byte array can never contain pointer-to types, so treat values as all integers
|
||||||
|
array.map {
|
||||||
|
val number = (it as NumericLiteralValue).number.toInt()
|
||||||
|
val hexnum = number.toString(16).padStart(2, '0')
|
||||||
|
"$$hexnum"
|
||||||
|
}
|
||||||
|
decl.datatype == DataType.ARRAY_B ->
|
||||||
|
// byte array can never contain pointer-to types, so treat values as all integers
|
||||||
|
array.map {
|
||||||
|
val number = (it as NumericLiteralValue).number.toInt()
|
||||||
|
val hexnum = number.absoluteValue.toString(16).padStart(2, '0')
|
||||||
|
if(number>=0)
|
||||||
|
"$$hexnum"
|
||||||
|
else
|
||||||
|
"-$$hexnum"
|
||||||
|
}
|
||||||
|
decl.datatype== DataType.ARRAY_UW -> array.map {
|
||||||
|
val number = (it as NumericLiteralValue).number.toInt()
|
||||||
|
val hexnum = number.toString(16).padStart(4, '0')
|
||||||
|
"$$hexnum"
|
||||||
|
}
|
||||||
|
decl.datatype== DataType.ARRAY_W -> array.map {
|
||||||
|
val number = (it as NumericLiteralValue).number.toInt()
|
||||||
|
val hexnum = number.absoluteValue.toString(16).padStart(4, '0')
|
||||||
|
if(number>=0)
|
||||||
|
"$$hexnum"
|
||||||
|
else
|
||||||
|
"-$$hexnum"
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("invalid arraysize type ${decl.datatype}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun getFloatConst(number: Double): String {
|
||||||
|
// try to match the ROM float constants to save memory
|
||||||
|
val mflpt5 = MachineDefinition.Mflpt5.fromNumber(number)
|
||||||
|
val floatbytes = shortArrayOf(mflpt5.b0, mflpt5.b1, mflpt5.b2, mflpt5.b3, mflpt5.b4)
|
||||||
|
when {
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x00, 0x00, 0x00, 0x00, 0x00)) -> return "c64flt.FL_ZERO"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x82, 0x49, 0x0f, 0xda, 0xa1)) -> return "c64flt.FL_PIVAL"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x90, 0x80, 0x00, 0x00, 0x00)) -> return "c64flt.FL_N32768"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x81, 0x00, 0x00, 0x00, 0x00)) -> return "c64flt.FL_FONE"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x80, 0x35, 0x04, 0xf3, 0x34)) -> return "c64flt.FL_SQRHLF"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x81, 0x35, 0x04, 0xf3, 0x34)) -> return "c64flt.FL_SQRTWO"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x80, 0x80, 0x00, 0x00, 0x00)) -> return "c64flt.FL_NEGHLF"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x80, 0x31, 0x72, 0x17, 0xf8)) -> return "c64flt.FL_LOG2"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x84, 0x20, 0x00, 0x00, 0x00)) -> return "c64flt.FL_TENC"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x9e, 0x6e, 0x6b, 0x28, 0x00)) -> return "c64flt.FL_NZMIL"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x80, 0x00, 0x00, 0x00, 0x00)) -> return "c64flt.FL_FHALF"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x81, 0x38, 0xaa, 0x3b, 0x29)) -> return "c64flt.FL_LOGEB2"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x81, 0x49, 0x0f, 0xda, 0xa2)) -> return "c64flt.FL_PIHALF"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x83, 0x49, 0x0f, 0xda, 0xa2)) -> return "c64flt.FL_TWOPI"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x7f, 0x00, 0x00, 0x00, 0x00)) -> return "c64flt.FL_FR4"
|
||||||
|
else -> {
|
||||||
|
// attempt to correct for a few rounding issues
|
||||||
|
when (number.toBigDecimal().setScale(10, RoundingMode.HALF_DOWN).toDouble()) {
|
||||||
|
3.1415926536 -> return "c64flt.FL_PIVAL"
|
||||||
|
1.4142135624 -> return "c64flt.FL_SQRTWO"
|
||||||
|
0.7071067812 -> return "c64flt.FL_SQRHLF"
|
||||||
|
0.6931471806 -> return "c64flt.FL_LOG2"
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no ROM float const for this value, create our own
|
||||||
|
val name = globalFloatConsts[number]
|
||||||
|
if(name!=null)
|
||||||
|
return name
|
||||||
|
val newName = "prog8_float_const_${globalFloatConsts.size}"
|
||||||
|
globalFloatConsts[number] = newName
|
||||||
|
return newName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun signExtendAtoMsb(destination: String) =
|
||||||
|
"""
|
||||||
|
ora #$7f
|
||||||
|
bmi +
|
||||||
|
lda #0
|
||||||
|
+ sta $destination
|
||||||
|
"""
|
||||||
|
|
||||||
|
internal fun asmIdentifierName(identifier: IdentifierReference): String {
|
||||||
|
val name = if(identifier.memberOfStruct(program.namespace)!=null) {
|
||||||
|
identifier.targetVarDecl(program.namespace)!!.name
|
||||||
|
} else {
|
||||||
|
identifier.nameInSource.joinToString(".")
|
||||||
|
}
|
||||||
|
return fixNameSymbols(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun fixNameSymbols(name: String) = name.replace("<", "prog8_").replace(">", "") // take care of the autogenerated invalid (anon) label names
|
||||||
|
|
||||||
|
private fun branchInstruction(condition: BranchCondition, complement: Boolean) =
|
||||||
|
if(complement) {
|
||||||
|
when (condition) {
|
||||||
|
BranchCondition.CS -> "bcc"
|
||||||
|
BranchCondition.CC -> "bcs"
|
||||||
|
BranchCondition.EQ, BranchCondition.Z -> "beq"
|
||||||
|
BranchCondition.NE, BranchCondition.NZ -> "bne"
|
||||||
|
BranchCondition.VS -> "bvc"
|
||||||
|
BranchCondition.VC -> "bvs"
|
||||||
|
BranchCondition.MI, BranchCondition.NEG -> "bmi"
|
||||||
|
BranchCondition.PL, BranchCondition.POS -> "bpl"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
when (condition) {
|
||||||
|
BranchCondition.CS -> "bcs"
|
||||||
|
BranchCondition.CC -> "bcc"
|
||||||
|
BranchCondition.EQ, BranchCondition.Z -> "beq"
|
||||||
|
BranchCondition.NE, BranchCondition.NZ -> "bne"
|
||||||
|
BranchCondition.VS -> "bvs"
|
||||||
|
BranchCondition.VC -> "bvc"
|
||||||
|
BranchCondition.MI, BranchCondition.NEG -> "bmi"
|
||||||
|
BranchCondition.PL, BranchCondition.POS -> "bpl"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun readAndPushArrayvalueWithIndexA(arrayDt: DataType, variable: IdentifierReference) {
|
||||||
|
val variablename = asmIdentifierName(variable)
|
||||||
|
when (arrayDt) {
|
||||||
|
DataType.STR, DataType.STR_S, DataType.ARRAY_UB, DataType.ARRAY_B ->
|
||||||
|
out(" tay | lda $variablename,y | sta $ESTACK_LO_HEX,x | dex")
|
||||||
|
DataType.ARRAY_UW, DataType.ARRAY_W ->
|
||||||
|
out(" asl a | tay | lda $variablename,y | sta $ESTACK_LO_HEX,x | lda $variablename+1,y | sta $ESTACK_HI_HEX,x | dex")
|
||||||
|
DataType.ARRAY_F ->
|
||||||
|
// index * 5 is done in the subroutine that's called
|
||||||
|
out("""
|
||||||
|
sta $ESTACK_LO_HEX,x
|
||||||
|
dex
|
||||||
|
lda #<$variablename
|
||||||
|
ldy #>$variablename
|
||||||
|
jsr c64flt.push_float_from_indexed_var
|
||||||
|
""")
|
||||||
|
else ->
|
||||||
|
throw AssemblyError("weird array type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun saveRegister(register: Register) {
|
||||||
|
when(register) {
|
||||||
|
Register.A -> out(" pha")
|
||||||
|
Register.X -> out(" txa | pha")
|
||||||
|
Register.Y -> out(" tya | pha")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun restoreRegister(register: Register) {
|
||||||
|
when(register) {
|
||||||
|
Register.A -> out(" pla")
|
||||||
|
Register.X -> out(" pla | tax")
|
||||||
|
Register.Y -> out(" pla | tay")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateSubroutine(sub: Subroutine) {
|
||||||
|
out("")
|
||||||
|
outputSourceLine(sub)
|
||||||
|
|
||||||
|
if(sub.isAsmSubroutine) {
|
||||||
|
if(sub.asmAddress!=null)
|
||||||
|
return // already done at the memvars section
|
||||||
|
|
||||||
|
// asmsub with most likely just an inline asm in it
|
||||||
|
out("${sub.name}\t.proc")
|
||||||
|
sub.statements.forEach{ translate(it) }
|
||||||
|
out(" .pend\n")
|
||||||
|
} else {
|
||||||
|
// regular subroutine
|
||||||
|
out("${sub.name}\t.proc")
|
||||||
|
zeropagevars2asm(sub.statements)
|
||||||
|
memdefs2asm(sub.statements)
|
||||||
|
out("; statements")
|
||||||
|
sub.statements.forEach{ translate(it) }
|
||||||
|
out("; variables")
|
||||||
|
vardecls2asm(sub.statements)
|
||||||
|
out(" .pend\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun translate(stmt: Statement) {
|
||||||
|
outputSourceLine(stmt)
|
||||||
|
when(stmt) {
|
||||||
|
is VarDecl, is StructDecl, is NopStatement -> {}
|
||||||
|
is Directive -> translate(stmt)
|
||||||
|
is Return -> translate(stmt)
|
||||||
|
is Subroutine -> translateSubroutine(stmt)
|
||||||
|
is InlineAssembly -> translate(stmt)
|
||||||
|
is FunctionCallStatement -> {
|
||||||
|
val functionName = stmt.target.nameInSource.last()
|
||||||
|
val builtinFunc = BuiltinFunctions[functionName]
|
||||||
|
if(builtinFunc!=null) {
|
||||||
|
builtinFunctionsAsmGen.translateFunctioncallStatement(stmt, builtinFunc)
|
||||||
|
} else {
|
||||||
|
functioncallAsmGen.translateFunctionCall(stmt)
|
||||||
|
// discard any results from the stack:
|
||||||
|
val sub = stmt.target.targetSubroutine(program.namespace)!!
|
||||||
|
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
|
||||||
|
for((t, reg) in returns) {
|
||||||
|
if(reg.stack) {
|
||||||
|
if (t in IntegerDatatypes || t in PassByReferenceDatatypes) out(" inx")
|
||||||
|
else if (t == DataType.FLOAT) out(" inx | inx | inx")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Assignment -> assignmentAsmGen.translate(stmt)
|
||||||
|
is Jump -> translate(stmt)
|
||||||
|
is PostIncrDecr -> postincrdecrAsmGen.translate(stmt)
|
||||||
|
is Label -> translate(stmt)
|
||||||
|
is BranchStatement -> translate(stmt)
|
||||||
|
is IfStatement -> translate(stmt)
|
||||||
|
is ForLoop -> forloopsAsmGen.translate(stmt)
|
||||||
|
is Continue -> out(" jmp ${loopContinueLabels.peek()}")
|
||||||
|
is Break -> out(" jmp ${loopEndLabels.peek()}")
|
||||||
|
is WhileLoop -> translate(stmt)
|
||||||
|
is RepeatLoop -> translate(stmt)
|
||||||
|
is WhenStatement -> translate(stmt)
|
||||||
|
is BuiltinFunctionStatementPlaceholder -> throw AssemblyError("builtin function should not have placeholder anymore?")
|
||||||
|
is AnonymousScope -> translate(stmt)
|
||||||
|
is Block -> throw AssemblyError("block should have been handled elsewhere")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translate(stmt: IfStatement) {
|
||||||
|
expressionsAsmGen.translateExpression(stmt.condition)
|
||||||
|
translateTestStack(stmt.condition.inferType(program).typeOrElse(DataType.STRUCT))
|
||||||
|
val elseLabel = makeLabel("if_else")
|
||||||
|
val endLabel = makeLabel("if_end")
|
||||||
|
out(" beq $elseLabel")
|
||||||
|
translate(stmt.truepart)
|
||||||
|
out(" jmp $endLabel")
|
||||||
|
out(elseLabel)
|
||||||
|
translate(stmt.elsepart)
|
||||||
|
out(endLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateTestStack(dataType: DataType) {
|
||||||
|
when(dataType) {
|
||||||
|
in ByteDatatypes -> out(" inx | lda $ESTACK_LO_HEX,x")
|
||||||
|
in WordDatatypes -> out(" inx | lda $ESTACK_LO_HEX,x | ora $ESTACK_HI_HEX,x")
|
||||||
|
DataType.FLOAT -> throw AssemblyError("conditional value should be an integer (boolean)")
|
||||||
|
else -> throw AssemblyError("non-numerical dt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translate(stmt: WhileLoop) {
|
||||||
|
val whileLabel = makeLabel("while")
|
||||||
|
val endLabel = makeLabel("whileend")
|
||||||
|
loopEndLabels.push(endLabel)
|
||||||
|
loopContinueLabels.push(whileLabel)
|
||||||
|
out(whileLabel)
|
||||||
|
// TODO optimize for the simple cases, can we avoid stack use?
|
||||||
|
expressionsAsmGen.translateExpression(stmt.condition)
|
||||||
|
val conditionDt = stmt.condition.inferType(program)
|
||||||
|
if(!conditionDt.isKnown)
|
||||||
|
throw AssemblyError("unknown condition dt")
|
||||||
|
if(conditionDt.typeOrElse(DataType.BYTE) in ByteDatatypes) {
|
||||||
|
out(" inx | lda $ESTACK_LO_HEX,x | beq $endLabel")
|
||||||
|
} else {
|
||||||
|
out("""
|
||||||
|
inx
|
||||||
|
lda $ESTACK_LO_HEX,x
|
||||||
|
bne +
|
||||||
|
lda $ESTACK_HI_HEX,x
|
||||||
|
beq $endLabel
|
||||||
|
+ """)
|
||||||
|
}
|
||||||
|
translate(stmt.body)
|
||||||
|
out(" jmp $whileLabel")
|
||||||
|
out(endLabel)
|
||||||
|
loopEndLabels.pop()
|
||||||
|
loopContinueLabels.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translate(stmt: RepeatLoop) {
|
||||||
|
val repeatLabel = makeLabel("repeat")
|
||||||
|
val endLabel = makeLabel("repeatend")
|
||||||
|
loopEndLabels.push(endLabel)
|
||||||
|
loopContinueLabels.push(repeatLabel)
|
||||||
|
out(repeatLabel)
|
||||||
|
// TODO optimize this for the simple cases, can we avoid stack use?
|
||||||
|
translate(stmt.body)
|
||||||
|
expressionsAsmGen.translateExpression(stmt.untilCondition)
|
||||||
|
val conditionDt = stmt.untilCondition.inferType(program)
|
||||||
|
if(!conditionDt.isKnown)
|
||||||
|
throw AssemblyError("unknown condition dt")
|
||||||
|
if(conditionDt.typeOrElse(DataType.BYTE) in ByteDatatypes) {
|
||||||
|
out(" inx | lda $ESTACK_LO_HEX,x | beq $repeatLabel")
|
||||||
|
} else {
|
||||||
|
out("""
|
||||||
|
inx
|
||||||
|
lda $ESTACK_LO_HEX,x
|
||||||
|
bne +
|
||||||
|
lda $ESTACK_HI_HEX,x
|
||||||
|
beq $repeatLabel
|
||||||
|
+ """)
|
||||||
|
}
|
||||||
|
out(endLabel)
|
||||||
|
loopEndLabels.pop()
|
||||||
|
loopContinueLabels.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translate(stmt: WhenStatement) {
|
||||||
|
expressionsAsmGen.translateExpression(stmt.condition)
|
||||||
|
val endLabel = makeLabel("choice_end")
|
||||||
|
val choiceBlocks = mutableListOf<Pair<String, AnonymousScope>>()
|
||||||
|
val conditionDt = stmt.condition.inferType(program)
|
||||||
|
if(!conditionDt.isKnown)
|
||||||
|
throw AssemblyError("unknown condition dt")
|
||||||
|
if(conditionDt.typeOrElse(DataType.BYTE) in ByteDatatypes)
|
||||||
|
out(" inx | lda $ESTACK_LO_HEX,x")
|
||||||
|
else
|
||||||
|
out(" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x")
|
||||||
|
for(choice in stmt.choices) {
|
||||||
|
val choiceLabel = makeLabel("choice")
|
||||||
|
if(choice.values==null) {
|
||||||
|
// the else choice
|
||||||
|
translate(choice.statements)
|
||||||
|
out(" jmp $endLabel")
|
||||||
|
} else {
|
||||||
|
choiceBlocks.add(Pair(choiceLabel, choice.statements))
|
||||||
|
for (cv in choice.values!!) {
|
||||||
|
val value = (cv as NumericLiteralValue).number.toInt()
|
||||||
|
if(conditionDt.typeOrElse(DataType.BYTE) in ByteDatatypes) {
|
||||||
|
out(" cmp #${value.toHex()} | beq $choiceLabel")
|
||||||
|
} else {
|
||||||
|
out("""
|
||||||
|
cmp #<${value.toHex()}
|
||||||
|
bne +
|
||||||
|
cpy #>${value.toHex()}
|
||||||
|
beq $choiceLabel
|
||||||
|
+
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(choiceBlock in choiceBlocks) {
|
||||||
|
out(choiceBlock.first)
|
||||||
|
translate(choiceBlock.second)
|
||||||
|
out(" jmp $endLabel")
|
||||||
|
}
|
||||||
|
out(endLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translate(stmt: Label) {
|
||||||
|
out(stmt.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translate(scope: AnonymousScope) {
|
||||||
|
// note: the variables defined in an anonymous scope have been moved to their defining subroutine's scope
|
||||||
|
scope.statements.forEach{ translate(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translate(stmt: BranchStatement) {
|
||||||
|
if(stmt.truepart.containsNoCodeNorVars() && stmt.elsepart.containsCodeOrVars())
|
||||||
|
throw AssemblyError("only else part contains code, shoud have been switched already")
|
||||||
|
|
||||||
|
val jump = stmt.truepart.statements.first() as? Jump
|
||||||
|
if(jump!=null) {
|
||||||
|
// branch with only a jump
|
||||||
|
val instruction = branchInstruction(stmt.condition, false)
|
||||||
|
out(" $instruction ${getJumpTarget(jump)}")
|
||||||
|
translate(stmt.elsepart)
|
||||||
|
} else {
|
||||||
|
if(stmt.elsepart.containsNoCodeNorVars()) {
|
||||||
|
val instruction = branchInstruction(stmt.condition, true)
|
||||||
|
val elseLabel = makeLabel("branch_else")
|
||||||
|
out(" $instruction $elseLabel")
|
||||||
|
translate(stmt.truepart)
|
||||||
|
out(elseLabel)
|
||||||
|
} else {
|
||||||
|
val instruction = branchInstruction(stmt.condition, false)
|
||||||
|
val trueLabel = makeLabel("branch_true")
|
||||||
|
val endLabel = makeLabel("branch_end")
|
||||||
|
out(" $instruction $trueLabel")
|
||||||
|
translate(stmt.elsepart)
|
||||||
|
out(" jmp $endLabel")
|
||||||
|
out(trueLabel)
|
||||||
|
translate(stmt.truepart)
|
||||||
|
out(endLabel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translate(stmt: Directive) {
|
||||||
|
when(stmt.directive) {
|
||||||
|
"%asminclude" -> {
|
||||||
|
val sourcecode = loadAsmIncludeFile(stmt.args[0].str!!, stmt.definingModule().source)
|
||||||
|
val scopeprefix = stmt.args[1].str ?: ""
|
||||||
|
if(!scopeprefix.isBlank())
|
||||||
|
out("$scopeprefix\t.proc")
|
||||||
|
assemblyLines.add(sourcecode.trimEnd().trimStart('\n'))
|
||||||
|
if(!scopeprefix.isBlank())
|
||||||
|
out(" .pend\n")
|
||||||
|
}
|
||||||
|
"%asmbinary" -> {
|
||||||
|
val offset = if(stmt.args.size>1) ", ${stmt.args[1].int}" else ""
|
||||||
|
val length = if(stmt.args.size>2) ", ${stmt.args[2].int}" else ""
|
||||||
|
out(" .binary \"${stmt.args[0].str}\" $offset $length")
|
||||||
|
}
|
||||||
|
"%breakpoint" -> {
|
||||||
|
val label = "_prog8_breakpoint_${breakpointLabels.size+1}"
|
||||||
|
breakpointLabels.add(label)
|
||||||
|
out("$label\tnop")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translate(jmp: Jump) {
|
||||||
|
out(" jmp ${getJumpTarget(jmp)}")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getJumpTarget(jmp: Jump): String {
|
||||||
|
return when {
|
||||||
|
jmp.identifier!=null -> asmIdentifierName(jmp.identifier)
|
||||||
|
jmp.generatedLabel!=null -> jmp.generatedLabel
|
||||||
|
jmp.address!=null -> jmp.address.toHex()
|
||||||
|
else -> "????"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translate(ret: Return) {
|
||||||
|
ret.value?.let { expressionsAsmGen.translateExpression(it) }
|
||||||
|
out(" rts")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translate(asm: InlineAssembly) {
|
||||||
|
val assembly = asm.assembly.trimEnd().trimStart('\n')
|
||||||
|
assemblyLines.add(assembly)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun translateArrayIndexIntoA(expr: ArrayIndexedExpression) {
|
||||||
|
when (val index = expr.arrayspec.index) {
|
||||||
|
is NumericLiteralValue -> throw AssemblyError("this should be optimized directly")
|
||||||
|
is RegisterExpr -> {
|
||||||
|
when (index.register) {
|
||||||
|
Register.A -> {}
|
||||||
|
Register.X -> out(" txa")
|
||||||
|
Register.Y -> out(" tya")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val indexName = asmIdentifierName(index)
|
||||||
|
out(" lda $indexName")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
expressionsAsmGen.translateExpression(index)
|
||||||
|
out(" inx | lda $ESTACK_LO_HEX,x")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun translateExpression(expression: Expression) =
|
||||||
|
expressionsAsmGen.translateExpression(expression)
|
||||||
|
|
||||||
|
internal fun translateFunctioncallExpression(functionCall: FunctionCall, signature: FunctionSignature) =
|
||||||
|
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature)
|
||||||
|
|
||||||
|
internal fun translateFunctionCall(functionCall: FunctionCall) =
|
||||||
|
functioncallAsmGen.translateFunctionCall(functionCall)
|
||||||
|
|
||||||
|
internal fun assignFromEvalResult(target: AssignTarget) =
|
||||||
|
assignmentAsmGen.assignFromEvalResult(target)
|
||||||
|
|
||||||
|
fun assignFromByteConstant(target: AssignTarget, value: Short) =
|
||||||
|
assignmentAsmGen.assignFromByteConstant(target, value)
|
||||||
|
|
||||||
|
fun assignFromWordConstant(target: AssignTarget, value: Int) =
|
||||||
|
assignmentAsmGen.assignFromWordConstant(target, value)
|
||||||
|
|
||||||
|
fun assignFromFloatConstant(target: AssignTarget, value: Double) =
|
||||||
|
assignmentAsmGen.assignFromFloatConstant(target, value)
|
||||||
|
|
||||||
|
fun assignFromByteVariable(target: AssignTarget, variable: IdentifierReference) =
|
||||||
|
assignmentAsmGen.assignFromByteVariable(target, variable)
|
||||||
|
|
||||||
|
fun assignFromWordVariable(target: AssignTarget, variable: IdentifierReference) =
|
||||||
|
assignmentAsmGen.assignFromWordVariable(target, variable)
|
||||||
|
|
||||||
|
fun assignFromFloatVariable(target: AssignTarget, variable: IdentifierReference) =
|
||||||
|
assignmentAsmGen.assignFromFloatVariable(target, variable)
|
||||||
|
|
||||||
|
fun assignFromRegister(target: AssignTarget, register: Register) =
|
||||||
|
assignmentAsmGen.assignFromRegister(target, register)
|
||||||
|
|
||||||
|
fun assignFromMemoryByte(target: AssignTarget, address: Int?, identifier: IdentifierReference?) =
|
||||||
|
assignmentAsmGen.assignFromMemoryByte(target, address, identifier)
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package prog8.compiler.target.c64
|
package prog8.compiler.target.c64.codegen
|
||||||
|
|
||||||
import prog8.compiler.toHex
|
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX
|
||||||
|
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS1_HEX
|
||||||
|
|
||||||
|
|
||||||
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
|
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
|
||||||
@ -53,7 +54,7 @@ fun optimizeAssembly(lines: MutableList<String>): Int {
|
|||||||
numberOfOptimizations++
|
numberOfOptimizations++
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO more assembly optimizations?
|
// TODO more assembly optimizations
|
||||||
|
|
||||||
return numberOfOptimizations
|
return numberOfOptimizations
|
||||||
}
|
}
|
||||||
@ -69,10 +70,10 @@ fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Int
|
|||||||
// the repeated lda can be removed
|
// the repeated lda can be removed
|
||||||
val removeLines = mutableListOf<Int>()
|
val removeLines = mutableListOf<Int>()
|
||||||
for(lines in linesByFour) {
|
for(lines in linesByFour) {
|
||||||
if(lines[0].value.trim()=="lda ${(ESTACK_LO+1).toHex()},x" &&
|
if(lines[0].value.trim()=="lda $ESTACK_LO_PLUS1_HEX,x" &&
|
||||||
lines[1].value.trim().startsWith("cmp ") &&
|
lines[1].value.trim().startsWith("cmp ") &&
|
||||||
lines[2].value.trim().startsWith("beq ") &&
|
lines[2].value.trim().startsWith("beq ") &&
|
||||||
lines[3].value.trim()=="lda ${(ESTACK_LO+1).toHex()},x") {
|
lines[3].value.trim()=="lda $ESTACK_LO_PLUS1_HEX,x") {
|
||||||
removeLines.add(lines[3].index) // remove the second lda
|
removeLines.add(lines[3].index) // remove the second lda
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -84,11 +85,10 @@ fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<String>>>
|
|||||||
// this is a lot harder for word values because the instruction sequence varies.
|
// this is a lot harder for word values because the instruction sequence varies.
|
||||||
val removeLines = mutableListOf<Int>()
|
val removeLines = mutableListOf<Int>()
|
||||||
for(lines in linesByFour) {
|
for(lines in linesByFour) {
|
||||||
if(lines[0].value.trim()=="sta ${ESTACK_LO.toHex()},x" &&
|
if(lines[0].value.trim()=="sta $ESTACK_LO_HEX,x" &&
|
||||||
lines[1].value.trim()=="dex" &&
|
lines[1].value.trim()=="dex" &&
|
||||||
lines[2].value.trim()=="inx" &&
|
lines[2].value.trim()=="inx" &&
|
||||||
lines[3].value.trim()=="lda ${ESTACK_LO.toHex()},x") {
|
lines[3].value.trim()=="lda $ESTACK_LO_HEX,x") {
|
||||||
removeLines.add(lines[0].index)
|
|
||||||
removeLines.add(lines[1].index)
|
removeLines.add(lines[1].index)
|
||||||
removeLines.add(lines[2].index)
|
removeLines.add(lines[2].index)
|
||||||
removeLines.add(lines[3].index)
|
removeLines.add(lines[3].index)
|
||||||
@ -101,7 +101,7 @@ fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>):
|
|||||||
|
|
||||||
// optimize sequential assignments of the isSameAs value to various targets (bytes, words, floats)
|
// optimize sequential assignments of the isSameAs value to various targets (bytes, words, floats)
|
||||||
// the float one is the one that requires 2*7=14 lines of code to check...
|
// the float one is the one that requires 2*7=14 lines of code to check...
|
||||||
// @todo a better place to do this is in the Compiler instead and work on opcodes, and never even create the inefficient asm...
|
// @todo a better place to do this is in the Compiler instead and transform the Ast, or the AsmGen, and never even create the inefficient asm in the first place...
|
||||||
|
|
||||||
val removeLines = mutableListOf<Int>()
|
val removeLines = mutableListOf<Int>()
|
||||||
for (pair in linesByFourteen) {
|
for (pair in linesByFourteen) {
|
@ -0,0 +1,745 @@
|
|||||||
|
package prog8.compiler.target.c64.codegen
|
||||||
|
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.*
|
||||||
|
import prog8.ast.expressions.*
|
||||||
|
import prog8.ast.statements.AssignTarget
|
||||||
|
import prog8.ast.statements.Assignment
|
||||||
|
import prog8.ast.statements.DirectMemoryWrite
|
||||||
|
import prog8.ast.statements.VarDecl
|
||||||
|
import prog8.compiler.target.c64.MachineDefinition
|
||||||
|
import prog8.compiler.toHex
|
||||||
|
|
||||||
|
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||||
|
|
||||||
|
internal fun translate(assign: Assignment) {
|
||||||
|
if(assign.aug_op!=null)
|
||||||
|
throw AssemblyError("aug-op assignments should have been transformed to normal ones")
|
||||||
|
|
||||||
|
when(assign.value) {
|
||||||
|
is NumericLiteralValue -> {
|
||||||
|
val numVal = assign.value as NumericLiteralValue
|
||||||
|
when(numVal.type) {
|
||||||
|
DataType.UBYTE, DataType.BYTE -> assignFromByteConstant(assign.target, numVal.number.toShort())
|
||||||
|
DataType.UWORD, DataType.WORD -> assignFromWordConstant(assign.target, numVal.number.toInt())
|
||||||
|
DataType.FLOAT -> assignFromFloatConstant(assign.target, numVal.number.toDouble())
|
||||||
|
else -> throw AssemblyError("weird numval type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is RegisterExpr -> {
|
||||||
|
assignFromRegister(assign.target, (assign.value as RegisterExpr).register)
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val type = assign.target.inferType(program, assign).typeOrElse(DataType.STRUCT)
|
||||||
|
when(type) {
|
||||||
|
DataType.UBYTE, DataType.BYTE -> assignFromByteVariable(assign.target, assign.value as IdentifierReference)
|
||||||
|
DataType.UWORD, DataType.WORD -> assignFromWordVariable(assign.target, assign.value as IdentifierReference)
|
||||||
|
DataType.FLOAT -> assignFromFloatVariable(assign.target, assign.value as IdentifierReference)
|
||||||
|
else -> throw AssemblyError("unsupported assignment target type $type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is AddressOf -> {
|
||||||
|
val identifier = (assign.value as AddressOf).identifier
|
||||||
|
assignFromAddressOf(assign.target, identifier)
|
||||||
|
}
|
||||||
|
is DirectMemoryRead -> {
|
||||||
|
val read = (assign.value as DirectMemoryRead)
|
||||||
|
when(read.addressExpression) {
|
||||||
|
is NumericLiteralValue -> {
|
||||||
|
val address = (read.addressExpression as NumericLiteralValue).number.toInt()
|
||||||
|
assignFromMemoryByte(assign.target, address, null)
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
assignFromMemoryByte(assign.target, null, read.addressExpression as IdentifierReference)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
asmgen.translateExpression(read.addressExpression)
|
||||||
|
TODO("read memory byte from result and put that in ${assign.target}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is PrefixExpression -> {
|
||||||
|
// TODO optimize common cases
|
||||||
|
asmgen.translateExpression(assign.value as PrefixExpression)
|
||||||
|
assignFromEvalResult(assign.target)
|
||||||
|
}
|
||||||
|
is BinaryExpression -> {
|
||||||
|
// TODO optimize common cases
|
||||||
|
asmgen.translateExpression(assign.value as BinaryExpression)
|
||||||
|
assignFromEvalResult(assign.target)
|
||||||
|
}
|
||||||
|
is ArrayIndexedExpression -> {
|
||||||
|
// TODO optimize common cases
|
||||||
|
val arrayExpr = assign.value as ArrayIndexedExpression
|
||||||
|
val arrayDt = arrayExpr.identifier.targetVarDecl(program.namespace)!!.datatype
|
||||||
|
val index = arrayExpr.arrayspec.index
|
||||||
|
if(index is NumericLiteralValue) {
|
||||||
|
// constant array index value
|
||||||
|
val arrayVarName = asmgen.asmIdentifierName(arrayExpr.identifier)
|
||||||
|
val indexValue = index.number.toInt() * ArrayElementTypes.getValue(arrayDt).memorySize()
|
||||||
|
when (arrayDt) {
|
||||||
|
DataType.STR, DataType.STR_S, DataType.ARRAY_UB, DataType.ARRAY_B ->
|
||||||
|
asmgen.out(" lda $arrayVarName+$indexValue | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
|
||||||
|
DataType.ARRAY_UW, DataType.ARRAY_W ->
|
||||||
|
asmgen.out(" lda $arrayVarName+$indexValue | sta ${MachineDefinition.ESTACK_LO_HEX},x | lda $arrayVarName+$indexValue+1 | sta ${MachineDefinition.ESTACK_HI_HEX},x | dex")
|
||||||
|
DataType.ARRAY_F ->
|
||||||
|
asmgen.out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue | jsr c64flt.push_float")
|
||||||
|
else ->
|
||||||
|
throw AssemblyError("weird array type")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
asmgen.translateArrayIndexIntoA(arrayExpr)
|
||||||
|
asmgen.readAndPushArrayvalueWithIndexA(arrayDt, arrayExpr.identifier)
|
||||||
|
}
|
||||||
|
assignFromEvalResult(assign.target)
|
||||||
|
}
|
||||||
|
is TypecastExpression -> {
|
||||||
|
val cast = assign.value as TypecastExpression
|
||||||
|
val sourceType = cast.expression.inferType(program)
|
||||||
|
val targetType = assign.target.inferType(program, assign)
|
||||||
|
if(sourceType.isKnown && targetType.isKnown &&
|
||||||
|
(sourceType.typeOrElse(DataType.STRUCT) in ByteDatatypes && targetType.typeOrElse(DataType.STRUCT) in ByteDatatypes) ||
|
||||||
|
(sourceType.typeOrElse(DataType.STRUCT) in WordDatatypes && targetType.typeOrElse(DataType.STRUCT) in WordDatatypes)) {
|
||||||
|
// no need for a type cast
|
||||||
|
assign.value = cast.expression
|
||||||
|
translate(assign)
|
||||||
|
} else {
|
||||||
|
asmgen.translateExpression(assign.value as TypecastExpression)
|
||||||
|
assignFromEvalResult(assign.target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is FunctionCall -> {
|
||||||
|
asmgen.translateExpression(assign.value as FunctionCall)
|
||||||
|
assignFromEvalResult(assign.target)
|
||||||
|
}
|
||||||
|
is ArrayLiteralValue, is StringLiteralValue -> TODO("string/array/struct assignment?")
|
||||||
|
is StructLiteralValue -> throw AssemblyError("struct literal value assignment should have been flattened")
|
||||||
|
is RangeExpr -> throw AssemblyError("range expression should have been changed into array values")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun assignFromEvalResult(target: AssignTarget) {
|
||||||
|
val targetIdent = target.identifier
|
||||||
|
when {
|
||||||
|
target.register!=null -> {
|
||||||
|
if(target.register== Register.X)
|
||||||
|
throw AssemblyError("can't pop into X register - use variable instead")
|
||||||
|
asmgen.out(" inx | ld${target.register.name.toLowerCase()} ${MachineDefinition.ESTACK_LO_HEX},x ")
|
||||||
|
}
|
||||||
|
targetIdent!=null -> {
|
||||||
|
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||||
|
val targetDt = targetIdent.inferType(program).typeOrElse(DataType.STRUCT)
|
||||||
|
when(targetDt) {
|
||||||
|
DataType.UBYTE, DataType.BYTE -> {
|
||||||
|
asmgen.out(" inx | lda ${MachineDefinition.ESTACK_LO_HEX},x | sta $targetName")
|
||||||
|
}
|
||||||
|
DataType.UWORD, DataType.WORD -> {
|
||||||
|
asmgen.out("""
|
||||||
|
inx
|
||||||
|
lda ${MachineDefinition.ESTACK_LO_HEX},x
|
||||||
|
sta $targetName
|
||||||
|
lda ${MachineDefinition.ESTACK_HI_HEX},x
|
||||||
|
sta $targetName+1
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
DataType.FLOAT -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<$targetName
|
||||||
|
ldy #>$targetName
|
||||||
|
jsr c64flt.pop_float
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird target variable type $targetDt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
target.memoryAddress!=null -> {
|
||||||
|
asmgen.out(" inx | ldy ${MachineDefinition.ESTACK_LO_HEX},x")
|
||||||
|
storeRegisterInMemoryAddress(Register.Y, target.memoryAddress)
|
||||||
|
}
|
||||||
|
target.arrayindexed!=null -> {
|
||||||
|
val arrayDt = target.arrayindexed!!.identifier.targetVarDecl(program.namespace)!!.datatype
|
||||||
|
val arrayVarName = asmgen.asmIdentifierName(target.arrayindexed!!.identifier)
|
||||||
|
asmgen.translateExpression(target.arrayindexed!!.arrayspec.index)
|
||||||
|
asmgen.out(" inx | lda ${MachineDefinition.ESTACK_LO_HEX},x")
|
||||||
|
popAndWriteArrayvalueWithIndexA(arrayDt, arrayVarName)
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird assignment target $target")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun assignFromAddressOf(target: AssignTarget, name: IdentifierReference) {
|
||||||
|
val targetIdent = target.identifier
|
||||||
|
val targetArrayIdx = target.arrayindexed
|
||||||
|
val struct = name.memberOfStruct(program.namespace)
|
||||||
|
val sourceName = if(struct!=null) {
|
||||||
|
// take the address of the first struct member instead
|
||||||
|
val decl = name.targetVarDecl(program.namespace)!!
|
||||||
|
val firstStructMember = struct.nameOfFirstMember()
|
||||||
|
// find the flattened var that belongs to this first struct member
|
||||||
|
val firstVarName = listOf(decl.name, firstStructMember)
|
||||||
|
val firstVar = name.definingScope().lookup(firstVarName, name) as VarDecl
|
||||||
|
firstVar.name
|
||||||
|
} else {
|
||||||
|
asmgen.fixNameSymbols(name.nameInSource.joinToString ("."))
|
||||||
|
}
|
||||||
|
|
||||||
|
when {
|
||||||
|
targetIdent!=null -> {
|
||||||
|
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<$sourceName
|
||||||
|
ldy #>$sourceName
|
||||||
|
sta $targetName
|
||||||
|
sty $targetName+1
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
target.memoryAddress!=null -> {
|
||||||
|
TODO("assign address $sourceName to memory word $target")
|
||||||
|
}
|
||||||
|
targetArrayIdx!=null -> {
|
||||||
|
val index = targetArrayIdx.arrayspec.index
|
||||||
|
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||||
|
TODO("assign address $sourceName to array $targetName [ $index ]")
|
||||||
|
}
|
||||||
|
else -> TODO("assign address $sourceName to $target")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun assignFromWordVariable(target: AssignTarget, variable: IdentifierReference) {
|
||||||
|
val sourceName = asmgen.asmIdentifierName(variable)
|
||||||
|
val targetIdent = target.identifier
|
||||||
|
val targetArrayIdx = target.arrayindexed
|
||||||
|
when {
|
||||||
|
targetIdent!=null -> {
|
||||||
|
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||||
|
asmgen.out("""
|
||||||
|
lda $sourceName
|
||||||
|
ldy $sourceName+1
|
||||||
|
sta $targetName
|
||||||
|
sty $targetName+1
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
target.memoryAddress!=null -> {
|
||||||
|
TODO("assign wordvar $sourceName to memory ${target.memoryAddress}")
|
||||||
|
}
|
||||||
|
targetArrayIdx!=null -> {
|
||||||
|
val index = targetArrayIdx.arrayspec.index
|
||||||
|
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||||
|
asmgen.out(" lda $sourceName | sta ${MachineDefinition.ESTACK_LO_HEX},x | lda $sourceName+1 | sta ${MachineDefinition.ESTACK_HI_HEX},x | dex")
|
||||||
|
asmgen.translateExpression(index)
|
||||||
|
asmgen.out(" inx | lda ${MachineDefinition.ESTACK_LO_HEX},x")
|
||||||
|
val arrayDt = targetArrayIdx.identifier.inferType(program).typeOrElse(DataType.STRUCT)
|
||||||
|
popAndWriteArrayvalueWithIndexA(arrayDt, targetName)
|
||||||
|
}
|
||||||
|
else -> TODO("assign wordvar to $target")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun assignFromFloatVariable(target: AssignTarget, variable: IdentifierReference) {
|
||||||
|
val sourceName = asmgen.asmIdentifierName(variable)
|
||||||
|
val targetIdent = target.identifier
|
||||||
|
val targetArrayIdx = target.arrayindexed
|
||||||
|
when {
|
||||||
|
targetIdent!=null -> {
|
||||||
|
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||||
|
asmgen.out("""
|
||||||
|
lda $sourceName
|
||||||
|
sta $targetName
|
||||||
|
lda $sourceName+1
|
||||||
|
sta $targetName+1
|
||||||
|
lda $sourceName+2
|
||||||
|
sta $targetName+2
|
||||||
|
lda $sourceName+3
|
||||||
|
sta $targetName+3
|
||||||
|
lda $sourceName+4
|
||||||
|
sta $targetName+4
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
targetArrayIdx!=null -> {
|
||||||
|
val index = targetArrayIdx.arrayspec.index
|
||||||
|
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||||
|
asmgen.out(" lda #<$sourceName | ldy #>$sourceName | jsr c64flt.push_float")
|
||||||
|
asmgen.translateExpression(index)
|
||||||
|
asmgen.out(" lda #<$targetName | ldy #>$targetName | jsr c64flt.pop_float_to_indexed_var")
|
||||||
|
}
|
||||||
|
else -> TODO("assign floatvar to $target")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun assignFromByteVariable(target: AssignTarget, variable: IdentifierReference) {
|
||||||
|
val sourceName = asmgen.asmIdentifierName(variable)
|
||||||
|
val targetIdent = target.identifier
|
||||||
|
val targetArrayIdx = target.arrayindexed
|
||||||
|
when {
|
||||||
|
target.register!=null -> {
|
||||||
|
asmgen.out(" ld${target.register.name.toLowerCase()} $sourceName")
|
||||||
|
}
|
||||||
|
targetIdent!=null -> {
|
||||||
|
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||||
|
asmgen.out("""
|
||||||
|
lda $sourceName
|
||||||
|
sta $targetName
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
targetArrayIdx!=null -> {
|
||||||
|
val index = targetArrayIdx.arrayspec.index
|
||||||
|
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||||
|
val arrayDt = targetArrayIdx.identifier.inferType(program).typeOrElse(DataType.STRUCT)
|
||||||
|
asmgen.out(" lda $sourceName | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
|
||||||
|
asmgen.translateExpression(index)
|
||||||
|
asmgen.out(" inx | lda ${MachineDefinition.ESTACK_LO_HEX},x")
|
||||||
|
popAndWriteArrayvalueWithIndexA(arrayDt, targetName)
|
||||||
|
}
|
||||||
|
target.memoryAddress != null -> {
|
||||||
|
val addressExpr = target.memoryAddress.addressExpression
|
||||||
|
val addressLv = addressExpr as? NumericLiteralValue
|
||||||
|
when {
|
||||||
|
addressLv != null -> asmgen.out(" lda $sourceName | sta ${addressLv.number.toHex()}")
|
||||||
|
addressExpr is IdentifierReference -> {
|
||||||
|
val targetName = asmgen.asmIdentifierName(addressExpr)
|
||||||
|
asmgen.out(" lda $sourceName | sta $targetName")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
asmgen.translateExpression(addressExpr)
|
||||||
|
asmgen.out("""
|
||||||
|
inx
|
||||||
|
lda ${MachineDefinition.ESTACK_LO_HEX},x
|
||||||
|
ldy ${MachineDefinition.ESTACK_HI_HEX},x
|
||||||
|
sta (+) +1
|
||||||
|
sty (+) +2
|
||||||
|
lda $sourceName
|
||||||
|
+ sta ${65535.toHex()} ; modified
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> TODO("assign bytevar to $target")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun assignFromRegister(target: AssignTarget, register: Register) {
|
||||||
|
val targetIdent = target.identifier
|
||||||
|
val targetArrayIdx = target.arrayindexed
|
||||||
|
when {
|
||||||
|
targetIdent!=null -> {
|
||||||
|
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||||
|
asmgen.out(" st${register.name.toLowerCase()} $targetName")
|
||||||
|
}
|
||||||
|
target.register!=null -> {
|
||||||
|
when(register) {
|
||||||
|
Register.A -> when(target.register) {
|
||||||
|
Register.A -> {}
|
||||||
|
Register.X -> asmgen.out(" tax")
|
||||||
|
Register.Y -> asmgen.out(" tay")
|
||||||
|
}
|
||||||
|
Register.X -> when(target.register) {
|
||||||
|
Register.A -> asmgen.out(" txa")
|
||||||
|
Register.X -> {}
|
||||||
|
Register.Y -> asmgen.out(" txy")
|
||||||
|
}
|
||||||
|
Register.Y -> when(target.register) {
|
||||||
|
Register.A -> asmgen.out(" tya")
|
||||||
|
Register.X -> asmgen.out(" tyx")
|
||||||
|
Register.Y -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
target.memoryAddress!=null -> {
|
||||||
|
storeRegisterInMemoryAddress(register, target.memoryAddress)
|
||||||
|
}
|
||||||
|
targetArrayIdx!=null -> {
|
||||||
|
val index = targetArrayIdx.arrayspec.index
|
||||||
|
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||||
|
when (index) {
|
||||||
|
is NumericLiteralValue -> {
|
||||||
|
val memindex = index.number.toInt()
|
||||||
|
when(register) {
|
||||||
|
Register.A -> asmgen.out(" sta $targetName+$memindex")
|
||||||
|
Register.X -> asmgen.out(" stx $targetName+$memindex")
|
||||||
|
Register.Y -> asmgen.out(" sty $targetName+$memindex")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is RegisterExpr -> {
|
||||||
|
when(register) {
|
||||||
|
Register.A -> asmgen.out(" sta ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
|
||||||
|
Register.X -> asmgen.out(" stx ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
|
||||||
|
Register.Y -> asmgen.out(" sty ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
|
||||||
|
}
|
||||||
|
when(index.register) {
|
||||||
|
Register.A -> {}
|
||||||
|
Register.X -> asmgen.out(" txa")
|
||||||
|
Register.Y -> asmgen.out(" tya")
|
||||||
|
}
|
||||||
|
asmgen.out("""
|
||||||
|
tay
|
||||||
|
lda ${MachineDefinition.C64Zeropage.SCRATCH_B1}
|
||||||
|
sta $targetName,y
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
when(register) {
|
||||||
|
Register.A -> asmgen.out(" sta ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
|
||||||
|
Register.X -> asmgen.out(" stx ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
|
||||||
|
Register.Y -> asmgen.out(" sty ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
|
||||||
|
}
|
||||||
|
asmgen.out("""
|
||||||
|
lda ${asmgen.asmIdentifierName(index)}
|
||||||
|
tay
|
||||||
|
lda ${MachineDefinition.C64Zeropage.SCRATCH_B1}
|
||||||
|
sta $targetName,y
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
asmgen.saveRegister(register)
|
||||||
|
asmgen.translateExpression(index)
|
||||||
|
asmgen.restoreRegister(register)
|
||||||
|
when(register) {
|
||||||
|
Register.A -> asmgen.out(" sta ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
|
||||||
|
Register.X -> asmgen.out(" stx ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
|
||||||
|
Register.Y -> asmgen.out(" sty ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
|
||||||
|
}
|
||||||
|
asmgen.out("""
|
||||||
|
inx
|
||||||
|
lda ${MachineDefinition.ESTACK_LO_HEX},x
|
||||||
|
tay
|
||||||
|
lda ${MachineDefinition.C64Zeropage.SCRATCH_B1}
|
||||||
|
sta $targetName,y
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> TODO("assign register $register to $target")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun storeRegisterInMemoryAddress(register: Register, memoryAddress: DirectMemoryWrite) {
|
||||||
|
val addressExpr = memoryAddress.addressExpression
|
||||||
|
val addressLv = addressExpr as? NumericLiteralValue
|
||||||
|
val registerName = register.name.toLowerCase()
|
||||||
|
when {
|
||||||
|
addressLv != null -> asmgen.out(" st$registerName ${addressLv.number.toHex()}")
|
||||||
|
addressExpr is IdentifierReference -> {
|
||||||
|
val targetName = asmgen.asmIdentifierName(addressExpr)
|
||||||
|
when(register) {
|
||||||
|
Register.A -> asmgen.out("""
|
||||||
|
ldy $targetName
|
||||||
|
sty ${MachineDefinition.C64Zeropage.SCRATCH_W1}
|
||||||
|
ldy $targetName+1
|
||||||
|
sty ${MachineDefinition.C64Zeropage.SCRATCH_W1+1}
|
||||||
|
ldy #0
|
||||||
|
sta (${MachineDefinition.C64Zeropage.SCRATCH_W1}),y
|
||||||
|
""")
|
||||||
|
Register.X -> asmgen.out("""
|
||||||
|
txa
|
||||||
|
ldy $targetName
|
||||||
|
sty ${MachineDefinition.C64Zeropage.SCRATCH_W1}
|
||||||
|
ldy $targetName+1
|
||||||
|
sty ${MachineDefinition.C64Zeropage.SCRATCH_W1+1}
|
||||||
|
ldy #0
|
||||||
|
sta (${MachineDefinition.C64Zeropage.SCRATCH_W1}),y
|
||||||
|
""")
|
||||||
|
Register.Y -> asmgen.out("""
|
||||||
|
tya
|
||||||
|
ldy $targetName
|
||||||
|
sty ${MachineDefinition.C64Zeropage.SCRATCH_W1}
|
||||||
|
ldy $targetName+1
|
||||||
|
sty ${MachineDefinition.C64Zeropage.SCRATCH_W1+1}
|
||||||
|
ldy #0
|
||||||
|
sta (${MachineDefinition.C64Zeropage.SCRATCH_W1}),y
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
asmgen.saveRegister(register)
|
||||||
|
asmgen.translateExpression(addressExpr)
|
||||||
|
asmgen.restoreRegister(register)
|
||||||
|
when (register) {
|
||||||
|
Register.A -> asmgen.out(" tay")
|
||||||
|
Register.X -> throw AssemblyError("can't use X register here")
|
||||||
|
Register.Y -> {}
|
||||||
|
}
|
||||||
|
asmgen.out("""
|
||||||
|
inx
|
||||||
|
lda ${MachineDefinition.ESTACK_LO_HEX},x
|
||||||
|
sta (+) +1
|
||||||
|
lda ${MachineDefinition.ESTACK_HI_HEX},x
|
||||||
|
sta (+) +2
|
||||||
|
+ sty ${65535.toHex()} ; modified
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun assignFromWordConstant(target: AssignTarget, word: Int) {
|
||||||
|
val targetIdent = target.identifier
|
||||||
|
val targetArrayIdx = target.arrayindexed
|
||||||
|
when {
|
||||||
|
targetIdent!=null -> {
|
||||||
|
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||||
|
if(word ushr 8 == word and 255) {
|
||||||
|
// lsb=msb
|
||||||
|
asmgen.out("""
|
||||||
|
lda #${(word and 255).toHex()}
|
||||||
|
sta $targetName
|
||||||
|
sta $targetName+1
|
||||||
|
""")
|
||||||
|
} else {
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<${word.toHex()}
|
||||||
|
ldy #>${word.toHex()}
|
||||||
|
sta $targetName
|
||||||
|
sty $targetName+1
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
target.memoryAddress!=null -> {
|
||||||
|
TODO("assign word $word to memory ${target.memoryAddress}")
|
||||||
|
}
|
||||||
|
targetArrayIdx!=null -> {
|
||||||
|
val index = targetArrayIdx.arrayspec.index
|
||||||
|
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||||
|
// TODO optimize common cases
|
||||||
|
asmgen.translateExpression(index)
|
||||||
|
asmgen.out("""
|
||||||
|
inx
|
||||||
|
lda ${MachineDefinition.ESTACK_LO_HEX},x
|
||||||
|
asl a
|
||||||
|
tay
|
||||||
|
lda #<${word.toHex()}
|
||||||
|
sta $targetName,y
|
||||||
|
lda #>${word.toHex()}
|
||||||
|
sta $targetName+1,y
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
else -> TODO("assign word $word to $target")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun assignFromByteConstant(target: AssignTarget, byte: Short) {
|
||||||
|
val targetIdent = target.identifier
|
||||||
|
val targetArrayIdx = target.arrayindexed
|
||||||
|
when {
|
||||||
|
target.register!=null -> {
|
||||||
|
asmgen.out(" ld${target.register.name.toLowerCase()} #${byte.toHex()}")
|
||||||
|
}
|
||||||
|
targetIdent!=null -> {
|
||||||
|
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||||
|
asmgen.out(" lda #${byte.toHex()} | sta $targetName ")
|
||||||
|
}
|
||||||
|
target.memoryAddress!=null -> {
|
||||||
|
asmgen.out(" ldy #${byte.toHex()}")
|
||||||
|
storeRegisterInMemoryAddress(Register.Y, target.memoryAddress)
|
||||||
|
}
|
||||||
|
targetArrayIdx!=null -> {
|
||||||
|
val index = targetArrayIdx.arrayspec.index
|
||||||
|
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||||
|
// TODO optimize common cases
|
||||||
|
asmgen.translateExpression(index)
|
||||||
|
asmgen.out("""
|
||||||
|
inx
|
||||||
|
ldy ${MachineDefinition.ESTACK_LO_HEX},x
|
||||||
|
lda #${byte.toHex()}
|
||||||
|
sta $targetName,y
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
else -> TODO("assign byte $byte to $target")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun assignFromFloatConstant(target: AssignTarget, float: Double) {
|
||||||
|
val targetIdent = target.identifier
|
||||||
|
val targetArrayIdx = target.arrayindexed
|
||||||
|
if(float==0.0) {
|
||||||
|
// optimized case for float zero
|
||||||
|
when {
|
||||||
|
targetIdent != null -> {
|
||||||
|
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||||
|
asmgen.out("""
|
||||||
|
lda #0
|
||||||
|
sta $targetName
|
||||||
|
sta $targetName+1
|
||||||
|
sta $targetName+2
|
||||||
|
sta $targetName+3
|
||||||
|
sta $targetName+4
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
targetArrayIdx!=null -> {
|
||||||
|
val index = targetArrayIdx.arrayspec.index
|
||||||
|
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||||
|
if(index is NumericLiteralValue) {
|
||||||
|
val indexValue = index.number.toInt() * MachineDefinition.Mflpt5.MemorySize
|
||||||
|
asmgen.out("""
|
||||||
|
lda #0
|
||||||
|
sta $targetName+$indexValue
|
||||||
|
sta $targetName+$indexValue+1
|
||||||
|
sta $targetName+$indexValue+2
|
||||||
|
sta $targetName+$indexValue+3
|
||||||
|
sta $targetName+$indexValue+4
|
||||||
|
""")
|
||||||
|
} else {
|
||||||
|
asmgen.translateExpression(index)
|
||||||
|
asmgen.out("""
|
||||||
|
inx
|
||||||
|
lda ${MachineDefinition.ESTACK_LO_HEX},x
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
clc
|
||||||
|
adc ${MachineDefinition.ESTACK_LO_HEX},x
|
||||||
|
tay
|
||||||
|
lda #0
|
||||||
|
sta $targetName,y
|
||||||
|
sta $targetName+1,y
|
||||||
|
sta $targetName+2,y
|
||||||
|
sta $targetName+3,y
|
||||||
|
sta $targetName+4,y
|
||||||
|
""") // TODO use a subroutine for this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> TODO("assign float 0.0 to $target")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// non-zero value
|
||||||
|
val constFloat = asmgen.getFloatConst(float)
|
||||||
|
when {
|
||||||
|
targetIdent != null -> {
|
||||||
|
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||||
|
asmgen.out("""
|
||||||
|
lda $constFloat
|
||||||
|
sta $targetName
|
||||||
|
lda $constFloat+1
|
||||||
|
sta $targetName+1
|
||||||
|
lda $constFloat+2
|
||||||
|
sta $targetName+2
|
||||||
|
lda $constFloat+3
|
||||||
|
sta $targetName+3
|
||||||
|
lda $constFloat+4
|
||||||
|
sta $targetName+4
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
targetArrayIdx!=null -> {
|
||||||
|
val index = targetArrayIdx.arrayspec.index
|
||||||
|
val arrayVarName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||||
|
if(index is NumericLiteralValue) {
|
||||||
|
val indexValue = index.number.toInt() * MachineDefinition.Mflpt5.MemorySize
|
||||||
|
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
|
||||||
|
""")
|
||||||
|
} else {
|
||||||
|
asmgen.translateArrayIndexIntoA(targetArrayIdx)
|
||||||
|
asmgen.out("""
|
||||||
|
sta ${MachineDefinition.C64Zeropage.SCRATCH_REG}
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
clc
|
||||||
|
adc ${MachineDefinition.C64Zeropage.SCRATCH_REG}
|
||||||
|
tay
|
||||||
|
lda $constFloat
|
||||||
|
sta $arrayVarName,y
|
||||||
|
lda $constFloat+1
|
||||||
|
sta $arrayVarName+1,y
|
||||||
|
lda $constFloat+2
|
||||||
|
sta $arrayVarName+2,y
|
||||||
|
lda $constFloat+3
|
||||||
|
sta $arrayVarName+3,y
|
||||||
|
lda $constFloat+4
|
||||||
|
sta $arrayVarName+4,y
|
||||||
|
""") // TODO use a subroutine for this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> TODO("assign float $float to $target")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun assignFromMemoryByte(target: AssignTarget, address: Int?, identifier: IdentifierReference?) {
|
||||||
|
val targetIdent = target.identifier
|
||||||
|
val targetArrayIdx = target.arrayindexed
|
||||||
|
if(address!=null) {
|
||||||
|
when {
|
||||||
|
target.register!=null -> {
|
||||||
|
asmgen.out(" ld${target.register.name.toLowerCase()} ${address.toHex()}")
|
||||||
|
}
|
||||||
|
targetIdent!=null -> {
|
||||||
|
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||||
|
asmgen.out("""
|
||||||
|
lda ${address.toHex()}
|
||||||
|
sta $targetName
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
target.memoryAddress!=null -> {
|
||||||
|
asmgen.out(" ldy ${address.toHex()}")
|
||||||
|
storeRegisterInMemoryAddress(Register.Y, target.memoryAddress)
|
||||||
|
}
|
||||||
|
targetArrayIdx!=null -> {
|
||||||
|
val index = targetArrayIdx.arrayspec.index
|
||||||
|
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||||
|
TODO("assign memory byte at $address to array $targetName [ $index ]")
|
||||||
|
}
|
||||||
|
else -> TODO("assign memory byte $target")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(identifier!=null) {
|
||||||
|
val sourceName = asmgen.asmIdentifierName(identifier)
|
||||||
|
when {
|
||||||
|
target.register!=null -> {
|
||||||
|
asmgen.out("""
|
||||||
|
ldy #0
|
||||||
|
lda ($sourceName),y
|
||||||
|
""")
|
||||||
|
when(target.register){
|
||||||
|
Register.A -> {}
|
||||||
|
Register.X -> asmgen.out(" tax")
|
||||||
|
Register.Y -> asmgen.out(" tay")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
targetIdent!=null -> {
|
||||||
|
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||||
|
asmgen.out("""
|
||||||
|
ldy #0
|
||||||
|
lda ($sourceName),y
|
||||||
|
sta $targetName
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
target.memoryAddress!=null -> {
|
||||||
|
asmgen.out(" ldy $sourceName")
|
||||||
|
storeRegisterInMemoryAddress(Register.Y, target.memoryAddress)
|
||||||
|
}
|
||||||
|
targetArrayIdx!=null -> {
|
||||||
|
val index = targetArrayIdx.arrayspec.index
|
||||||
|
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||||
|
TODO("assign memory byte $sourceName to array $targetName [ $index ]")
|
||||||
|
}
|
||||||
|
else -> TODO("assign memory byte $target")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun popAndWriteArrayvalueWithIndexA(arrayDt: DataType, variablename: String) {
|
||||||
|
when (arrayDt) {
|
||||||
|
DataType.STR, DataType.STR_S, DataType.ARRAY_UB, DataType.ARRAY_B ->
|
||||||
|
asmgen.out(" tay | inx | lda ${MachineDefinition.ESTACK_LO_HEX},x | sta $variablename,y")
|
||||||
|
DataType.ARRAY_UW, DataType.ARRAY_W ->
|
||||||
|
asmgen.out(" asl a | tay | inx | lda ${MachineDefinition.ESTACK_LO_HEX},x | sta $variablename,y | lda ${MachineDefinition.ESTACK_HI_HEX},x | sta $variablename+1,y")
|
||||||
|
DataType.ARRAY_F ->
|
||||||
|
// index * 5 is done in the subroutine that's called
|
||||||
|
asmgen.out("""
|
||||||
|
sta ${MachineDefinition.ESTACK_LO_HEX},x
|
||||||
|
dex
|
||||||
|
lda #<$variablename
|
||||||
|
ldy #>$variablename
|
||||||
|
jsr c64flt.pop_float_to_indexed_var
|
||||||
|
""")
|
||||||
|
else ->
|
||||||
|
throw AssemblyError("weird array type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,602 @@
|
|||||||
|
package prog8.compiler.target.c64.codegen
|
||||||
|
|
||||||
|
import prog8.ast.IFunctionCall
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.ByteDatatypes
|
||||||
|
import prog8.ast.base.DataType
|
||||||
|
import prog8.ast.base.Register
|
||||||
|
import prog8.ast.base.WordDatatypes
|
||||||
|
import prog8.ast.expressions.*
|
||||||
|
import prog8.ast.statements.AssignTarget
|
||||||
|
import prog8.ast.statements.FunctionCallStatement
|
||||||
|
import prog8.compiler.target.c64.MachineDefinition.C64Zeropage
|
||||||
|
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_HEX
|
||||||
|
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_PLUS1_HEX
|
||||||
|
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX
|
||||||
|
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS1_HEX
|
||||||
|
import prog8.compiler.toHex
|
||||||
|
import prog8.functions.FunctionSignature
|
||||||
|
|
||||||
|
internal class BuiltinFunctionsAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||||
|
|
||||||
|
internal fun translateFunctioncallExpression(fcall: FunctionCall, func: FunctionSignature) {
|
||||||
|
translateFunctioncall(fcall, func, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun translateFunctioncallStatement(fcall: FunctionCallStatement, func: FunctionSignature) {
|
||||||
|
translateFunctioncall(fcall, func, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateFunctioncall(fcall: IFunctionCall, func: FunctionSignature, discardResult: Boolean) {
|
||||||
|
val functionName = fcall.target.nameInSource.last()
|
||||||
|
if (discardResult) {
|
||||||
|
if (func.pure)
|
||||||
|
return // can just ignore the whole function call altogether
|
||||||
|
else if (func.returntype != null)
|
||||||
|
throw AssemblyError("discarding result of non-pure function $fcall")
|
||||||
|
}
|
||||||
|
|
||||||
|
when (functionName) {
|
||||||
|
"msb" -> {
|
||||||
|
val arg = fcall.arglist.single()
|
||||||
|
if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes)
|
||||||
|
throw AssemblyError("msb required word argument")
|
||||||
|
if (arg is NumericLiteralValue)
|
||||||
|
throw AssemblyError("should have been const-folded")
|
||||||
|
if (arg is IdentifierReference) {
|
||||||
|
val sourceName = asmgen.asmIdentifierName(arg)
|
||||||
|
asmgen.out(" lda $sourceName+1 | sta $ESTACK_LO_HEX,x | dex")
|
||||||
|
} else {
|
||||||
|
asmgen.translateExpression(arg)
|
||||||
|
asmgen.out(" lda $ESTACK_HI_PLUS1_HEX,x | sta $ESTACK_LO_PLUS1_HEX,x")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"mkword" -> {
|
||||||
|
translateFunctionArguments(fcall.arglist, func)
|
||||||
|
asmgen.out(" inx | lda $ESTACK_LO_HEX,x | sta $ESTACK_HI_PLUS1_HEX,x")
|
||||||
|
}
|
||||||
|
"abs" -> {
|
||||||
|
translateFunctionArguments(fcall.arglist, func)
|
||||||
|
val dt = fcall.arglist.single().inferType(program)
|
||||||
|
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||||
|
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.abs_b")
|
||||||
|
in WordDatatypes -> asmgen.out(" jsr prog8_lib.abs_w")
|
||||||
|
DataType.FLOAT -> asmgen.out(" jsr c64flt.abs_f")
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"swap" -> {
|
||||||
|
val first = fcall.arglist[0]
|
||||||
|
val second = fcall.arglist[1]
|
||||||
|
asmgen.translateExpression(first)
|
||||||
|
asmgen.translateExpression(second)
|
||||||
|
// pop in reverse order
|
||||||
|
val firstTarget = AssignTarget.fromExpr(first)
|
||||||
|
val secondTarget = AssignTarget.fromExpr(second)
|
||||||
|
asmgen.assignFromEvalResult(firstTarget)
|
||||||
|
asmgen.assignFromEvalResult(secondTarget)
|
||||||
|
}
|
||||||
|
"strlen" -> {
|
||||||
|
outputPushAddressOfIdentifier(fcall.arglist[0])
|
||||||
|
asmgen.out(" jsr prog8_lib.func_strlen")
|
||||||
|
}
|
||||||
|
"min", "max", "sum" -> {
|
||||||
|
outputPushAddressAndLenghtOfArray(fcall.arglist[0])
|
||||||
|
val dt = fcall.arglist.single().inferType(program)
|
||||||
|
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||||
|
DataType.ARRAY_UB, DataType.STR_S, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${functionName}_ub")
|
||||||
|
DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_${functionName}_b")
|
||||||
|
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_${functionName}_uw")
|
||||||
|
DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${functionName}_w")
|
||||||
|
DataType.ARRAY_F -> asmgen.out(" jsr c64flt.func_${functionName}_f")
|
||||||
|
else -> throw AssemblyError("weird type $dt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"any", "all" -> {
|
||||||
|
outputPushAddressAndLenghtOfArray(fcall.arglist[0])
|
||||||
|
val dt = fcall.arglist.single().inferType(program)
|
||||||
|
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||||
|
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR_S, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${functionName}_b")
|
||||||
|
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${functionName}_w")
|
||||||
|
DataType.ARRAY_F -> asmgen.out(" jsr c64flt.func_${functionName}_f")
|
||||||
|
else -> throw AssemblyError("weird type $dt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"sgn" -> {
|
||||||
|
translateFunctionArguments(fcall.arglist, func)
|
||||||
|
val dt = fcall.arglist.single().inferType(program)
|
||||||
|
when(dt.typeOrElse(DataType.STRUCT)) {
|
||||||
|
DataType.UBYTE -> asmgen.out(" jsr math.sign_ub")
|
||||||
|
DataType.BYTE -> asmgen.out(" jsr math.sign_b")
|
||||||
|
DataType.UWORD -> asmgen.out(" jsr math.sign_uw")
|
||||||
|
DataType.WORD -> asmgen.out(" jsr math.sign_w")
|
||||||
|
DataType.FLOAT -> asmgen.out(" jsr c64flt.sign_f")
|
||||||
|
else -> throw AssemblyError("weird type $dt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"sin", "cos", "tan", "atan",
|
||||||
|
"ln", "log2", "sqrt", "rad",
|
||||||
|
"deg", "round", "floor", "ceil",
|
||||||
|
"rdnf" -> {
|
||||||
|
translateFunctionArguments(fcall.arglist, func)
|
||||||
|
asmgen.out(" jsr c64flt.func_$functionName")
|
||||||
|
}
|
||||||
|
"lsl" -> {
|
||||||
|
// in-place
|
||||||
|
val what = fcall.arglist.single()
|
||||||
|
val dt = what.inferType(program)
|
||||||
|
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||||
|
in ByteDatatypes -> {
|
||||||
|
when (what) {
|
||||||
|
is RegisterExpr -> {
|
||||||
|
when (what.register) {
|
||||||
|
Register.A -> asmgen.out(" asl a")
|
||||||
|
Register.X -> asmgen.out(" txa | asl a | tax")
|
||||||
|
Register.Y -> asmgen.out(" tya | asl a | tay")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is IdentifierReference -> asmgen.out(" asl ${asmgen.asmIdentifierName(what)}")
|
||||||
|
is DirectMemoryRead -> {
|
||||||
|
if (what.addressExpression is NumericLiteralValue) {
|
||||||
|
val number = (what.addressExpression as NumericLiteralValue).number
|
||||||
|
asmgen.out(" asl ${number.toHex()}")
|
||||||
|
} else {
|
||||||
|
asmgen.translateExpression(what.addressExpression)
|
||||||
|
asmgen.out("""
|
||||||
|
inx
|
||||||
|
lda $ESTACK_LO_HEX,x
|
||||||
|
sta (+) + 1
|
||||||
|
lda $ESTACK_HI_HEX,x
|
||||||
|
sta (+) + 2
|
||||||
|
+ asl 0 ; modified
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is ArrayIndexedExpression -> {
|
||||||
|
asmgen.translateExpression(what.identifier)
|
||||||
|
asmgen.translateExpression(what.arrayspec.index)
|
||||||
|
asmgen.out(" jsr prog8_lib.lsl_array_b")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
in WordDatatypes -> {
|
||||||
|
when (what) {
|
||||||
|
is ArrayIndexedExpression -> {
|
||||||
|
asmgen.translateExpression(what.identifier)
|
||||||
|
asmgen.translateExpression(what.arrayspec.index)
|
||||||
|
asmgen.out(" jsr prog8_lib.lsl_array_w")
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val variable = asmgen.asmIdentifierName(what)
|
||||||
|
asmgen.out(" asl $variable | rol $variable+1")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"lsr" -> {
|
||||||
|
// in-place
|
||||||
|
val what = fcall.arglist.single()
|
||||||
|
val dt = what.inferType(program)
|
||||||
|
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||||
|
DataType.UBYTE -> {
|
||||||
|
when (what) {
|
||||||
|
is RegisterExpr -> {
|
||||||
|
when (what.register) {
|
||||||
|
Register.A -> asmgen.out(" lsr a")
|
||||||
|
Register.X -> asmgen.out(" txa | lsr a | tax")
|
||||||
|
Register.Y -> asmgen.out(" tya | lsr a | tay")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is IdentifierReference -> asmgen.out(" lsr ${asmgen.asmIdentifierName(what)}")
|
||||||
|
is DirectMemoryRead -> {
|
||||||
|
if (what.addressExpression is NumericLiteralValue) {
|
||||||
|
val number = (what.addressExpression as NumericLiteralValue).number
|
||||||
|
asmgen.out(" lsr ${number.toHex()}")
|
||||||
|
} else {
|
||||||
|
asmgen.translateExpression(what.addressExpression)
|
||||||
|
asmgen.out("""
|
||||||
|
inx
|
||||||
|
lda $ESTACK_LO_HEX,x
|
||||||
|
sta (+) + 1
|
||||||
|
lda $ESTACK_HI_HEX,x
|
||||||
|
sta (+) + 2
|
||||||
|
+ lsr 0 ; modified
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is ArrayIndexedExpression -> {
|
||||||
|
asmgen.translateExpression(what.identifier)
|
||||||
|
asmgen.translateExpression(what.arrayspec.index)
|
||||||
|
asmgen.out(" jsr prog8_lib.lsr_array_ub")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.BYTE -> {
|
||||||
|
when (what) {
|
||||||
|
is ArrayIndexedExpression -> {
|
||||||
|
asmgen.translateExpression(what.identifier)
|
||||||
|
asmgen.translateExpression(what.arrayspec.index)
|
||||||
|
asmgen.out(" jsr prog8_lib.lsr_array_b")
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val variable = asmgen.asmIdentifierName(what)
|
||||||
|
asmgen.out(" lda $variable | asl a | ror $variable")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.UWORD -> {
|
||||||
|
when (what) {
|
||||||
|
is ArrayIndexedExpression -> {
|
||||||
|
asmgen.translateExpression(what.identifier)
|
||||||
|
asmgen.translateExpression(what.arrayspec.index)
|
||||||
|
asmgen.out(" jsr prog8_lib.lsr_array_uw")
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val variable = asmgen.asmIdentifierName(what)
|
||||||
|
asmgen.out(" lsr $variable+1 | ror $variable")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.WORD -> {
|
||||||
|
when (what) {
|
||||||
|
is ArrayIndexedExpression -> {
|
||||||
|
asmgen.translateExpression(what.identifier)
|
||||||
|
asmgen.translateExpression(what.arrayspec.index)
|
||||||
|
asmgen.out(" jsr prog8_lib.lsr_array_w")
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val variable = asmgen.asmIdentifierName(what)
|
||||||
|
asmgen.out(" lda $variable+1 | asl a | ror $variable+1 | ror $variable")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"rol" -> {
|
||||||
|
// in-place
|
||||||
|
val what = fcall.arglist.single()
|
||||||
|
val dt = what.inferType(program)
|
||||||
|
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||||
|
DataType.UBYTE -> {
|
||||||
|
when(what) {
|
||||||
|
is ArrayIndexedExpression -> {
|
||||||
|
asmgen.translateExpression(what.identifier)
|
||||||
|
asmgen.translateExpression(what.arrayspec.index)
|
||||||
|
asmgen.out(" jsr prog8_lib.rol_array_ub")
|
||||||
|
}
|
||||||
|
is DirectMemoryRead -> {
|
||||||
|
if (what.addressExpression is NumericLiteralValue) {
|
||||||
|
val number = (what.addressExpression as NumericLiteralValue).number
|
||||||
|
asmgen.out(" rol ${number.toHex()}")
|
||||||
|
} else {
|
||||||
|
asmgen.translateExpression(what.addressExpression)
|
||||||
|
asmgen.out("""
|
||||||
|
inx
|
||||||
|
lda $ESTACK_LO_HEX,x
|
||||||
|
sta (+) + 1
|
||||||
|
lda $ESTACK_HI_HEX,x
|
||||||
|
sta (+) + 2
|
||||||
|
+ rol 0 ; modified
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is RegisterExpr -> {
|
||||||
|
when(what.register) {
|
||||||
|
Register.A -> asmgen.out(" rol a")
|
||||||
|
Register.X -> asmgen.out(" txa | rol a | tax")
|
||||||
|
Register.Y -> asmgen.out(" tya | rol a | tay")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val variable = asmgen.asmIdentifierName(what)
|
||||||
|
asmgen.out(" rol $variable")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.UWORD -> {
|
||||||
|
when(what) {
|
||||||
|
is ArrayIndexedExpression -> {
|
||||||
|
asmgen.translateExpression(what.identifier)
|
||||||
|
asmgen.translateExpression(what.arrayspec.index)
|
||||||
|
asmgen.out(" jsr prog8_lib.rol_array_uw")
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val variable = asmgen.asmIdentifierName(what)
|
||||||
|
asmgen.out(" rol $variable | rol $variable+1")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"rol2" -> {
|
||||||
|
// in-place
|
||||||
|
val what = fcall.arglist.single()
|
||||||
|
val dt = what.inferType(program)
|
||||||
|
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||||
|
DataType.UBYTE -> {
|
||||||
|
when(what) {
|
||||||
|
is ArrayIndexedExpression -> {
|
||||||
|
asmgen.translateExpression(what.identifier)
|
||||||
|
asmgen.translateExpression(what.arrayspec.index)
|
||||||
|
asmgen.out(" jsr prog8_lib.rol2_array_ub")
|
||||||
|
}
|
||||||
|
is DirectMemoryRead -> {
|
||||||
|
if (what.addressExpression is NumericLiteralValue) {
|
||||||
|
val number = (what.addressExpression as NumericLiteralValue).number
|
||||||
|
asmgen.out(" lda ${number.toHex()} | cmp #\$80 | rol a | sta ${number.toHex()}")
|
||||||
|
} else {
|
||||||
|
asmgen.translateExpression(what.addressExpression)
|
||||||
|
asmgen.out(" jsr prog8_lib.rol2_mem_ub")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is RegisterExpr -> {
|
||||||
|
when(what.register) {
|
||||||
|
Register.A -> asmgen.out(" cmp #\$80 | rol a ")
|
||||||
|
Register.X -> asmgen.out(" txa | cmp #\$80 | rol a | tax")
|
||||||
|
Register.Y -> asmgen.out(" tya | cmp #\$80 | rol a | tay")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val variable = asmgen.asmIdentifierName(what)
|
||||||
|
asmgen.out(" lda $variable | cmp #\$80 | rol a | sta $variable")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.UWORD -> {
|
||||||
|
when(what) {
|
||||||
|
is ArrayIndexedExpression -> {
|
||||||
|
asmgen.translateExpression(what.identifier)
|
||||||
|
asmgen.translateExpression(what.arrayspec.index)
|
||||||
|
asmgen.out(" jsr prog8_lib.rol2_array_uw")
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val variable = asmgen.asmIdentifierName(what)
|
||||||
|
asmgen.out(" asl $variable | rol $variable+1 | bcc + | inc $variable |+ ")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"ror" -> {
|
||||||
|
// in-place
|
||||||
|
val what = fcall.arglist.single()
|
||||||
|
val dt = what.inferType(program)
|
||||||
|
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||||
|
DataType.UBYTE -> {
|
||||||
|
when(what) {
|
||||||
|
is ArrayIndexedExpression -> {
|
||||||
|
asmgen.translateExpression(what.identifier)
|
||||||
|
asmgen.translateExpression(what.arrayspec.index)
|
||||||
|
asmgen.out(" jsr prog8_lib.ror_array_ub")
|
||||||
|
}
|
||||||
|
is DirectMemoryRead -> {
|
||||||
|
if (what.addressExpression is NumericLiteralValue) {
|
||||||
|
val number = (what.addressExpression as NumericLiteralValue).number
|
||||||
|
asmgen.out(" ror ${number.toHex()}")
|
||||||
|
} else {
|
||||||
|
asmgen.translateExpression(what.addressExpression)
|
||||||
|
asmgen.out("""
|
||||||
|
inx
|
||||||
|
lda $ESTACK_LO_HEX,x
|
||||||
|
sta (+) + 1
|
||||||
|
lda $ESTACK_HI_HEX,x
|
||||||
|
sta (+) + 2
|
||||||
|
+ ror 0 ; modified
|
||||||
|
""") }
|
||||||
|
}
|
||||||
|
is RegisterExpr -> {
|
||||||
|
when(what.register) {
|
||||||
|
Register.A -> asmgen.out(" ror a")
|
||||||
|
Register.X -> asmgen.out(" txa | ror a | tax")
|
||||||
|
Register.Y -> asmgen.out(" tya | ror a | tay")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val variable = asmgen.asmIdentifierName(what)
|
||||||
|
asmgen.out(" ror $variable")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.UWORD -> {
|
||||||
|
when(what) {
|
||||||
|
is ArrayIndexedExpression -> {
|
||||||
|
asmgen.translateExpression(what.identifier)
|
||||||
|
asmgen.translateExpression(what.arrayspec.index)
|
||||||
|
asmgen.out(" jsr prog8_lib.ror_array_uw")
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val variable = asmgen.asmIdentifierName(what)
|
||||||
|
asmgen.out(" ror $variable+1 | ror $variable")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"ror2" -> {
|
||||||
|
// in-place
|
||||||
|
val what = fcall.arglist.single()
|
||||||
|
val dt = what.inferType(program)
|
||||||
|
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||||
|
DataType.UBYTE -> {
|
||||||
|
when(what) {
|
||||||
|
is ArrayIndexedExpression -> {
|
||||||
|
asmgen.translateExpression(what.identifier)
|
||||||
|
asmgen.translateExpression(what.arrayspec.index)
|
||||||
|
asmgen.out(" jsr prog8_lib.ror2_array_ub")
|
||||||
|
}
|
||||||
|
is DirectMemoryRead -> {
|
||||||
|
if (what.addressExpression is NumericLiteralValue) {
|
||||||
|
val number = (what.addressExpression as NumericLiteralValue).number
|
||||||
|
asmgen.out(" lda ${number.toHex()} | lsr a | bcc + | ora #\$80 |+ | sta ${number.toHex()}")
|
||||||
|
} else {
|
||||||
|
asmgen.translateExpression(what.addressExpression)
|
||||||
|
asmgen.out(" jsr prog8_lib.ror2_mem_ub")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is RegisterExpr -> {
|
||||||
|
when(what.register) {
|
||||||
|
Register.A -> asmgen.out(" lsr a | bcc + | ora #\$80 |+ ")
|
||||||
|
Register.X -> asmgen.out(" txa | lsr a | bcc + | ora #\$80 |+ tax ")
|
||||||
|
Register.Y -> asmgen.out(" tya | lsr a | bcc + | ora #\$80 |+ tay ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val variable = asmgen.asmIdentifierName(what)
|
||||||
|
asmgen.out(" lda $variable | lsr a | bcc + | ora #\$80 |+ | sta $variable")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.UWORD -> {
|
||||||
|
when(what) {
|
||||||
|
is ArrayIndexedExpression -> {
|
||||||
|
asmgen.translateExpression(what.identifier)
|
||||||
|
asmgen.translateExpression(what.arrayspec.index)
|
||||||
|
asmgen.out(" jsr prog8_lib.ror2_array_uw")
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val variable = asmgen.asmIdentifierName(what)
|
||||||
|
asmgen.out(" lsr $variable+1 | ror $variable | bcc + | lda $variable+1 | ora #\$80 | sta $variable+1 |+ ")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"sort" -> {
|
||||||
|
val variable = fcall.arglist.single()
|
||||||
|
if(variable is IdentifierReference) {
|
||||||
|
val decl = variable.targetVarDecl(program.namespace)!!
|
||||||
|
val varName = asmgen.asmIdentifierName(variable)
|
||||||
|
val numElements = decl.arraysize!!.size()
|
||||||
|
when(decl.datatype) {
|
||||||
|
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<$varName
|
||||||
|
ldy #>$varName
|
||||||
|
sta ${C64Zeropage.SCRATCH_W1}
|
||||||
|
sty ${C64Zeropage.SCRATCH_W1+1}
|
||||||
|
lda #$numElements
|
||||||
|
sta ${C64Zeropage.SCRATCH_B1}
|
||||||
|
""")
|
||||||
|
asmgen.out(if(decl.datatype==DataType.ARRAY_UB) " jsr prog8_lib.sort_ub" else " jsr prog8_lib.sort_b")
|
||||||
|
}
|
||||||
|
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<$varName
|
||||||
|
ldy #>$varName
|
||||||
|
sta ${C64Zeropage.SCRATCH_W1}
|
||||||
|
sty ${C64Zeropage.SCRATCH_W1+1}
|
||||||
|
lda #$numElements
|
||||||
|
sta ${C64Zeropage.SCRATCH_B1}
|
||||||
|
""")
|
||||||
|
asmgen.out(if(decl.datatype==DataType.ARRAY_UW) " jsr prog8_lib.sort_uw" else " jsr prog8_lib.sort_w")
|
||||||
|
}
|
||||||
|
DataType.ARRAY_F -> TODO("sort floats (consider another solution if possible - this will be very slow, if ever implemented)")
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
"reverse" -> {
|
||||||
|
val variable = fcall.arglist.single()
|
||||||
|
if (variable is IdentifierReference) {
|
||||||
|
val decl = variable.targetVarDecl(program.namespace)!!
|
||||||
|
val varName = asmgen.asmIdentifierName(variable)
|
||||||
|
val numElements = decl.arraysize!!.size()
|
||||||
|
when (decl.datatype) {
|
||||||
|
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<$varName
|
||||||
|
ldy #>$varName
|
||||||
|
sta ${C64Zeropage.SCRATCH_W1}
|
||||||
|
sty ${C64Zeropage.SCRATCH_W1 + 1}
|
||||||
|
lda #$numElements
|
||||||
|
jsr prog8_lib.reverse_b
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<$varName
|
||||||
|
ldy #>$varName
|
||||||
|
sta ${C64Zeropage.SCRATCH_W1}
|
||||||
|
sty ${C64Zeropage.SCRATCH_W1 + 1}
|
||||||
|
lda #$numElements
|
||||||
|
jsr prog8_lib.reverse_w
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
DataType.ARRAY_F -> TODO("reverse floats (consider another solution if possible - this will be quite slow, if ever implemented)")
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"rsave" -> {
|
||||||
|
// save cpu status flag and all registers A, X, Y.
|
||||||
|
// see http://6502.org/tutorials/register_preservation.html
|
||||||
|
asmgen.out(" php | sta ${C64Zeropage.SCRATCH_REG} | pha | txa | pha | tya | pha | lda ${C64Zeropage.SCRATCH_REG}")
|
||||||
|
}
|
||||||
|
"rrestore" -> {
|
||||||
|
// restore all registers and cpu status flag
|
||||||
|
asmgen.out(" pla | tay | pla | tax | pla | plp")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
translateFunctionArguments(fcall.arglist, func)
|
||||||
|
asmgen.out(" jsr prog8_lib.func_$functionName")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun outputPushAddressAndLenghtOfArray(arg: Expression) {
|
||||||
|
arg as IdentifierReference
|
||||||
|
val identifierName = asmgen.asmIdentifierName(arg)
|
||||||
|
val size = arg.targetVarDecl(program.namespace)!!.arraysize!!.size()!!
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<$identifierName
|
||||||
|
sta $ESTACK_LO_HEX,x
|
||||||
|
lda #>$identifierName
|
||||||
|
sta $ESTACK_HI_HEX,x
|
||||||
|
dex
|
||||||
|
lda #$size
|
||||||
|
sta $ESTACK_LO_HEX,x
|
||||||
|
dex
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun outputPushAddressOfIdentifier(arg: Expression) {
|
||||||
|
val identifierName = asmgen.asmIdentifierName(arg as IdentifierReference)
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<$identifierName
|
||||||
|
sta $ESTACK_LO_HEX,x
|
||||||
|
lda #>$identifierName
|
||||||
|
sta $ESTACK_HI_HEX,x
|
||||||
|
dex
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateFunctionArguments(args: MutableList<Expression>, signature: FunctionSignature) {
|
||||||
|
args.forEach {
|
||||||
|
asmgen.translateExpression(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,433 @@
|
|||||||
|
package prog8.compiler.target.c64.codegen
|
||||||
|
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.*
|
||||||
|
import prog8.ast.expressions.*
|
||||||
|
import prog8.compiler.target.c64.MachineDefinition
|
||||||
|
import prog8.compiler.toHex
|
||||||
|
import prog8.functions.BuiltinFunctions
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
|
internal class ExpressionsAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||||
|
|
||||||
|
internal fun translateExpression(expression: Expression) {
|
||||||
|
when(expression) {
|
||||||
|
is PrefixExpression -> translateExpression(expression)
|
||||||
|
is BinaryExpression -> translateExpression(expression)
|
||||||
|
is ArrayIndexedExpression -> translatePushFromArray(expression)
|
||||||
|
is TypecastExpression -> translateExpression(expression)
|
||||||
|
is AddressOf -> translateExpression(expression)
|
||||||
|
is DirectMemoryRead -> translateExpression(expression)
|
||||||
|
is NumericLiteralValue -> translateExpression(expression)
|
||||||
|
is RegisterExpr -> translateExpression(expression)
|
||||||
|
is IdentifierReference -> translateExpression(expression)
|
||||||
|
is FunctionCall -> translateExpression(expression)
|
||||||
|
is ArrayLiteralValue, is StringLiteralValue -> TODO("string/array/struct assignment?")
|
||||||
|
is StructLiteralValue -> throw AssemblyError("struct literal value assignment should have been flattened")
|
||||||
|
is RangeExpr -> throw AssemblyError("range expression should have been changed into array values")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateExpression(expression: FunctionCall) {
|
||||||
|
val functionName = expression.target.nameInSource.last()
|
||||||
|
val builtinFunc = BuiltinFunctions[functionName]
|
||||||
|
if (builtinFunc != null) {
|
||||||
|
asmgen.translateFunctioncallExpression(expression, builtinFunc)
|
||||||
|
} else {
|
||||||
|
asmgen.translateFunctionCall(expression)
|
||||||
|
val sub = expression.target.targetSubroutine(program.namespace)!!
|
||||||
|
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
|
||||||
|
for ((_, reg) in returns) {
|
||||||
|
if (!reg.stack) {
|
||||||
|
// result value in cpu or status registers, put it on the stack
|
||||||
|
if (reg.registerOrPair != null) {
|
||||||
|
when (reg.registerOrPair) {
|
||||||
|
RegisterOrPair.A -> asmgen.out(" sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
|
||||||
|
RegisterOrPair.Y -> asmgen.out(" tya | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
|
||||||
|
RegisterOrPair.AY -> asmgen.out(" sta ${MachineDefinition.ESTACK_LO_HEX},x | tya | sta ${MachineDefinition.ESTACK_HI_HEX},x | dex")
|
||||||
|
RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY -> throw AssemblyError("can't push X register - use a variable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// return value from a statusregister is not put on the stack, it should be acted on via a conditional branch such as if_cc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateExpression(expr: TypecastExpression) {
|
||||||
|
translateExpression(expr.expression)
|
||||||
|
when(expr.expression.inferType(program).typeOrElse(DataType.STRUCT)) {
|
||||||
|
DataType.UBYTE -> {
|
||||||
|
when(expr.type) {
|
||||||
|
DataType.UBYTE, DataType.BYTE -> {}
|
||||||
|
DataType.UWORD, DataType.WORD -> asmgen.out(" lda #0 | sta ${MachineDefinition.ESTACK_HI_PLUS1_HEX},x")
|
||||||
|
DataType.FLOAT -> asmgen.out(" jsr c64flt.stack_ub2float")
|
||||||
|
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.BYTE -> {
|
||||||
|
when(expr.type) {
|
||||||
|
DataType.UBYTE, DataType.BYTE -> {}
|
||||||
|
DataType.UWORD, DataType.WORD -> asmgen.out(" lda ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x | ${asmgen.signExtendAtoMsb("${MachineDefinition.ESTACK_HI_PLUS1_HEX},x")}")
|
||||||
|
DataType.FLOAT -> asmgen.out(" jsr c64flt.stack_b2float")
|
||||||
|
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.UWORD -> {
|
||||||
|
when(expr.type) {
|
||||||
|
DataType.BYTE, DataType.UBYTE -> {}
|
||||||
|
DataType.WORD, DataType.UWORD -> {}
|
||||||
|
DataType.FLOAT -> asmgen.out(" jsr c64flt.stack_uw2float")
|
||||||
|
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.WORD -> {
|
||||||
|
when(expr.type) {
|
||||||
|
DataType.BYTE, DataType.UBYTE -> {}
|
||||||
|
DataType.WORD, DataType.UWORD -> {}
|
||||||
|
DataType.FLOAT -> asmgen.out(" jsr c64flt.stack_w2float")
|
||||||
|
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.FLOAT -> {
|
||||||
|
when(expr.type) {
|
||||||
|
DataType.UBYTE -> asmgen.out(" jsr c64flt.stack_float2uw")
|
||||||
|
DataType.BYTE -> asmgen.out(" jsr c64flt.stack_float2w")
|
||||||
|
DataType.UWORD -> asmgen.out(" jsr c64flt.stack_float2uw")
|
||||||
|
DataType.WORD -> asmgen.out(" jsr c64flt.stack_float2w")
|
||||||
|
DataType.FLOAT -> {}
|
||||||
|
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
in PassByReferenceDatatypes -> throw AssemblyError("cannot case a pass-by-reference datatypes into something else")
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateExpression(expr: AddressOf) {
|
||||||
|
val name = asmgen.asmIdentifierName(expr.identifier)
|
||||||
|
asmgen.out(" lda #<$name | sta ${MachineDefinition.ESTACK_LO_HEX},x | lda #>$name | sta ${MachineDefinition.ESTACK_HI_HEX},x | dex")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateExpression(expr: DirectMemoryRead) {
|
||||||
|
when(expr.addressExpression) {
|
||||||
|
is NumericLiteralValue -> {
|
||||||
|
val address = (expr.addressExpression as NumericLiteralValue).number.toInt()
|
||||||
|
asmgen.out(" lda ${address.toHex()} | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val sourceName = asmgen.asmIdentifierName(expr.addressExpression as IdentifierReference)
|
||||||
|
asmgen.out(" lda $sourceName | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
translateExpression(expr.addressExpression)
|
||||||
|
asmgen.out(" jsr prog8_lib.read_byte_from_address")
|
||||||
|
asmgen.out(" sta ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateExpression(expr: NumericLiteralValue) {
|
||||||
|
when(expr.type) {
|
||||||
|
DataType.UBYTE, DataType.BYTE -> asmgen.out(" lda #${expr.number.toHex()} | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
|
||||||
|
DataType.UWORD, DataType.WORD -> asmgen.out("""
|
||||||
|
lda #<${expr.number.toHex()}
|
||||||
|
sta ${MachineDefinition.ESTACK_LO_HEX},x
|
||||||
|
lda #>${expr.number.toHex()}
|
||||||
|
sta ${MachineDefinition.ESTACK_HI_HEX},x
|
||||||
|
dex
|
||||||
|
""")
|
||||||
|
DataType.FLOAT -> {
|
||||||
|
val floatConst = asmgen.getFloatConst(expr.number.toDouble())
|
||||||
|
asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr c64flt.push_float")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateExpression(expr: RegisterExpr) {
|
||||||
|
when(expr.register) {
|
||||||
|
Register.A -> asmgen.out(" sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
|
||||||
|
Register.X -> throw AssemblyError("cannot push X - use a variable instead of the X register")
|
||||||
|
Register.Y -> asmgen.out(" tya | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateExpression(expr: IdentifierReference) {
|
||||||
|
val varname = asmgen.asmIdentifierName(expr)
|
||||||
|
when(expr.inferType(program).typeOrElse(DataType.STRUCT)) {
|
||||||
|
DataType.UBYTE, DataType.BYTE -> {
|
||||||
|
asmgen.out(" lda $varname | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
|
||||||
|
}
|
||||||
|
DataType.UWORD, DataType.WORD -> {
|
||||||
|
asmgen.out(" lda $varname | sta ${MachineDefinition.ESTACK_LO_HEX},x | lda $varname+1 | sta ${MachineDefinition.ESTACK_HI_HEX},x | dex")
|
||||||
|
}
|
||||||
|
in ArrayDatatypes, in StringDatatypes -> {
|
||||||
|
asmgen.out(" lda #<$varname | sta ${MachineDefinition.ESTACK_LO_HEX},x | lda #>$varname | sta ${MachineDefinition.ESTACK_HI_HEX},x | dex")
|
||||||
|
}
|
||||||
|
DataType.FLOAT -> {
|
||||||
|
asmgen.out(" lda #<$varname | ldy #>$varname| jsr c64flt.push_float")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("stack push weird variable type $expr")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val optimizedByteMultiplications = setOf(3,5,6,7,9,10,11,12,13,14,15,20,25,40)
|
||||||
|
private val optimizedWordMultiplications = setOf(3,5,6,7,9,10,12,15,20,25,40)
|
||||||
|
private val powersOfTwo = setOf(0,1,2,4,8,16,32,64,128,256)
|
||||||
|
|
||||||
|
private fun translateExpression(expr: BinaryExpression) {
|
||||||
|
val leftIDt = expr.left.inferType(program)
|
||||||
|
val rightIDt = expr.right.inferType(program)
|
||||||
|
if(!leftIDt.isKnown || !rightIDt.isKnown)
|
||||||
|
throw AssemblyError("can't infer type of both expression operands")
|
||||||
|
|
||||||
|
val leftDt = leftIDt.typeOrElse(DataType.STRUCT)
|
||||||
|
val rightDt = rightIDt.typeOrElse(DataType.STRUCT)
|
||||||
|
// see if we can apply some optimized routines
|
||||||
|
when(expr.operator) {
|
||||||
|
">>" -> {
|
||||||
|
// bit-shifts are always by a constant number (for now)
|
||||||
|
translateExpression(expr.left)
|
||||||
|
val amount = expr.right.constValue(program)!!.number.toInt()
|
||||||
|
when (leftDt) {
|
||||||
|
DataType.UBYTE -> repeat(amount) { asmgen.out(" lsr ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x") }
|
||||||
|
DataType.BYTE -> repeat(amount) { asmgen.out(" lda ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x | asl a | ror ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x") }
|
||||||
|
DataType.UWORD -> repeat(amount) { asmgen.out(" lsr ${MachineDefinition.ESTACK_HI_PLUS1_HEX},x | ror ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x") }
|
||||||
|
DataType.WORD -> repeat(amount) { asmgen.out(" lda ${MachineDefinition.ESTACK_HI_PLUS1_HEX},x | asl a | ror ${MachineDefinition.ESTACK_HI_PLUS1_HEX},x | ror ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x") }
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
"<<" -> {
|
||||||
|
// bit-shifts are always by a constant number (for now)
|
||||||
|
translateExpression(expr.left)
|
||||||
|
val amount = expr.right.constValue(program)!!.number.toInt()
|
||||||
|
if (leftDt in ByteDatatypes)
|
||||||
|
repeat(amount) { asmgen.out(" asl ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x") }
|
||||||
|
else
|
||||||
|
repeat(amount) { asmgen.out(" asl ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x | rol ${MachineDefinition.ESTACK_HI_PLUS1_HEX},x") }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
"*" -> {
|
||||||
|
val value = expr.right.constValue(program)
|
||||||
|
if(value!=null) {
|
||||||
|
if(rightDt in IntegerDatatypes) {
|
||||||
|
val amount = value.number.toInt()
|
||||||
|
if(amount in powersOfTwo)
|
||||||
|
printWarning("${expr.right.position} multiplication by power of 2 should have been optimized into a left shift instruction: $amount")
|
||||||
|
when(rightDt) {
|
||||||
|
DataType.UBYTE -> {
|
||||||
|
if(amount in optimizedByteMultiplications) {
|
||||||
|
translateExpression(expr.left)
|
||||||
|
asmgen.out(" jsr math.mul_byte_$amount")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.BYTE -> {
|
||||||
|
if(amount in optimizedByteMultiplications) {
|
||||||
|
translateExpression(expr.left)
|
||||||
|
asmgen.out(" jsr math.mul_byte_$amount")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if(amount.absoluteValue in optimizedByteMultiplications) {
|
||||||
|
translateExpression(expr.left)
|
||||||
|
asmgen.out(" jsr prog8_lib.neg_b | jsr math.mul_byte_${amount.absoluteValue}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.UWORD -> {
|
||||||
|
if(amount in optimizedWordMultiplications) {
|
||||||
|
translateExpression(expr.left)
|
||||||
|
asmgen.out(" jsr math.mul_word_$amount")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.WORD -> {
|
||||||
|
if(amount in optimizedWordMultiplications) {
|
||||||
|
translateExpression(expr.left)
|
||||||
|
asmgen.out(" jsr math.mul_word_$amount")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if(amount.absoluteValue in optimizedWordMultiplications) {
|
||||||
|
translateExpression(expr.left)
|
||||||
|
asmgen.out(" jsr prog8_lib.neg_w | jsr math.mul_word_${amount.absoluteValue}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the general, non-optimized cases
|
||||||
|
translateExpression(expr.left)
|
||||||
|
translateExpression(expr.right)
|
||||||
|
if(leftDt!=rightDt)
|
||||||
|
throw AssemblyError("binary operator ${expr.operator} left/right dt not identical") // is this strictly required always?
|
||||||
|
when (leftDt) {
|
||||||
|
in ByteDatatypes -> translateBinaryOperatorBytes(expr.operator, leftDt)
|
||||||
|
in WordDatatypes -> translateBinaryOperatorWords(expr.operator, leftDt)
|
||||||
|
DataType.FLOAT -> translateBinaryOperatorFloats(expr.operator)
|
||||||
|
else -> throw AssemblyError("non-numerical datatype")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateExpression(expr: PrefixExpression) {
|
||||||
|
translateExpression(expr.expression)
|
||||||
|
val type = expr.inferType(program).typeOrElse(DataType.STRUCT)
|
||||||
|
when(expr.operator) {
|
||||||
|
"+" -> {}
|
||||||
|
"-" -> {
|
||||||
|
when(type) {
|
||||||
|
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.neg_b")
|
||||||
|
in WordDatatypes -> asmgen.out(" jsr prog8_lib.neg_w")
|
||||||
|
DataType.FLOAT -> asmgen.out(" jsr c64flt.neg_f")
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"~" -> {
|
||||||
|
when(type) {
|
||||||
|
in ByteDatatypes ->
|
||||||
|
asmgen.out("""
|
||||||
|
lda ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x
|
||||||
|
eor #255
|
||||||
|
sta ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x
|
||||||
|
""")
|
||||||
|
in WordDatatypes -> asmgen.out(" jsr prog8_lib.inv_word")
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"not" -> {
|
||||||
|
when(type) {
|
||||||
|
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.not_byte")
|
||||||
|
in WordDatatypes -> asmgen.out(" jsr prog8_lib.not_word")
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("invalid prefix operator ${expr.operator}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translatePushFromArray(arrayExpr: ArrayIndexedExpression) {
|
||||||
|
// assume *reading* from an array
|
||||||
|
val index = arrayExpr.arrayspec.index
|
||||||
|
val arrayDt = arrayExpr.identifier.targetVarDecl(program.namespace)!!.datatype
|
||||||
|
val arrayVarName = asmgen.asmIdentifierName(arrayExpr.identifier)
|
||||||
|
if(index is NumericLiteralValue) {
|
||||||
|
val elementDt = ArrayElementTypes.getValue(arrayDt)
|
||||||
|
val indexValue = index.number.toInt() * elementDt.memorySize()
|
||||||
|
when(elementDt) {
|
||||||
|
in ByteDatatypes -> {
|
||||||
|
asmgen.out(" lda $arrayVarName+$indexValue | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
|
||||||
|
}
|
||||||
|
in WordDatatypes -> {
|
||||||
|
asmgen.out(" lda $arrayVarName+$indexValue | sta ${MachineDefinition.ESTACK_LO_HEX},x | lda $arrayVarName+$indexValue+1 | sta ${MachineDefinition.ESTACK_HI_HEX},x | dex")
|
||||||
|
}
|
||||||
|
DataType.FLOAT -> {
|
||||||
|
asmgen.out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue | jsr c64flt.push_float")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
asmgen.translateArrayIndexIntoA(arrayExpr)
|
||||||
|
asmgen.readAndPushArrayvalueWithIndexA(arrayDt, arrayExpr.identifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateBinaryOperatorBytes(operator: String, types: DataType) {
|
||||||
|
when(operator) {
|
||||||
|
"**" -> throw AssemblyError("** operator requires floats")
|
||||||
|
"*" -> asmgen.out(" jsr prog8_lib.mul_byte") // the optimized routines should have been checked earlier
|
||||||
|
"/" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.idiv_ub" else " jsr prog8_lib.idiv_b")
|
||||||
|
"%" -> {
|
||||||
|
if(types==DataType.BYTE)
|
||||||
|
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
|
||||||
|
asmgen.out(" jsr prog8_lib.remainder_ub")
|
||||||
|
}
|
||||||
|
"+" -> asmgen.out("""
|
||||||
|
lda ${MachineDefinition.ESTACK_LO_PLUS2_HEX},x
|
||||||
|
clc
|
||||||
|
adc ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x
|
||||||
|
inx
|
||||||
|
sta ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x
|
||||||
|
""")
|
||||||
|
"-" -> asmgen.out("""
|
||||||
|
lda ${MachineDefinition.ESTACK_LO_PLUS2_HEX},x
|
||||||
|
sec
|
||||||
|
sbc ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x
|
||||||
|
inx
|
||||||
|
sta ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x
|
||||||
|
""")
|
||||||
|
"<<", ">>" -> throw AssemblyError("bit-shifts not via stack")
|
||||||
|
"<" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.less_ub" else " jsr prog8_lib.less_b")
|
||||||
|
">" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.greater_ub" else " jsr prog8_lib.greater_b")
|
||||||
|
"<=" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.lesseq_ub" else " jsr prog8_lib.lesseq_b")
|
||||||
|
">=" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.greatereq_ub" else " jsr prog8_lib.greatereq_b")
|
||||||
|
"==" -> asmgen.out(" jsr prog8_lib.equal_b")
|
||||||
|
"!=" -> asmgen.out(" jsr prog8_lib.notequal_b")
|
||||||
|
"&" -> asmgen.out(" jsr prog8_lib.bitand_b")
|
||||||
|
"^" -> asmgen.out(" jsr prog8_lib.bitxor_b")
|
||||||
|
"|" -> asmgen.out(" jsr prog8_lib.bitor_b")
|
||||||
|
"and" -> asmgen.out(" jsr prog8_lib.and_b")
|
||||||
|
"or" -> asmgen.out(" jsr prog8_lib.or_b")
|
||||||
|
"xor" -> asmgen.out(" jsr prog8_lib.xor_b")
|
||||||
|
else -> throw AssemblyError("invalid operator $operator")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateBinaryOperatorWords(operator: String, types: DataType) {
|
||||||
|
when(operator) {
|
||||||
|
"**" -> throw AssemblyError("** operator requires floats")
|
||||||
|
"*" -> asmgen.out(" jsr prog8_lib.mul_word")
|
||||||
|
"/" -> asmgen.out(if(types==DataType.UWORD) " jsr prog8_lib.idiv_uw" else " jsr prog8_lib.idiv_w")
|
||||||
|
"%" -> {
|
||||||
|
if(types==DataType.WORD)
|
||||||
|
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
|
||||||
|
asmgen.out(" jsr prog8_lib.remainder_uw")
|
||||||
|
}
|
||||||
|
"+" -> asmgen.out(" jsr prog8_lib.add_w")
|
||||||
|
"-" -> asmgen.out(" jsr prog8_lib.sub_w")
|
||||||
|
"<<" -> throw AssemblyError("<< should not operate via stack")
|
||||||
|
">>" -> throw AssemblyError(">> should not operate via stack")
|
||||||
|
"<" -> asmgen.out(if(types==DataType.UWORD) " jsr prog8_lib.less_uw" else " jsr prog8_lib.less_w")
|
||||||
|
">" -> asmgen.out(if(types==DataType.UWORD) " jsr prog8_lib.greater_uw" else " jsr prog8_lib.greater_w")
|
||||||
|
"<=" -> asmgen.out(if(types==DataType.UWORD) " jsr prog8_lib.lesseq_uw" else " jsr prog8_lib.lesseq_w")
|
||||||
|
">=" -> asmgen.out(if(types==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.bitxor_w")
|
||||||
|
"|" -> asmgen.out(" jsr prog8_lib.bitor_w")
|
||||||
|
"and" -> asmgen.out(" jsr prog8_lib.and_w")
|
||||||
|
"or" -> asmgen.out(" jsr prog8_lib.or_w")
|
||||||
|
"xor" -> asmgen.out(" jsr prog8_lib.xor_w")
|
||||||
|
else -> throw AssemblyError("invalid operator $operator")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateBinaryOperatorFloats(operator: String) {
|
||||||
|
when(operator) {
|
||||||
|
"**" -> asmgen.out(" jsr c64flt.pow_f")
|
||||||
|
"*" -> asmgen.out(" jsr c64flt.mul_f")
|
||||||
|
"/" -> asmgen.out(" jsr c64flt.div_f")
|
||||||
|
"+" -> asmgen.out(" jsr c64flt.add_f")
|
||||||
|
"-" -> asmgen.out(" jsr c64flt.sub_f")
|
||||||
|
"<" -> asmgen.out(" jsr c64flt.less_f")
|
||||||
|
">" -> asmgen.out(" jsr c64flt.greater_f")
|
||||||
|
"<=" -> asmgen.out(" jsr c64flt.lesseq_f")
|
||||||
|
">=" -> asmgen.out(" jsr c64flt.greatereq_f")
|
||||||
|
"==" -> asmgen.out(" jsr c64flt.equal_f")
|
||||||
|
"!=" -> asmgen.out(" jsr c64flt.notequal_f")
|
||||||
|
"%", "<<", ">>", "&", "^", "|", "and", "or", "xor" -> throw AssemblyError("requires integer datatype")
|
||||||
|
else -> throw AssemblyError("invalid operator $operator")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
699
compiler/src/prog8/compiler/target/c64/codegen/ForLoopsAsmGen.kt
Normal file
699
compiler/src/prog8/compiler/target/c64/codegen/ForLoopsAsmGen.kt
Normal file
@ -0,0 +1,699 @@
|
|||||||
|
package prog8.compiler.target.c64.codegen
|
||||||
|
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.DataType
|
||||||
|
import prog8.ast.base.Register
|
||||||
|
import prog8.ast.expressions.IdentifierReference
|
||||||
|
import prog8.ast.expressions.RangeExpr
|
||||||
|
import prog8.ast.statements.AssignTarget
|
||||||
|
import prog8.ast.statements.Assignment
|
||||||
|
import prog8.ast.statements.ForLoop
|
||||||
|
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX
|
||||||
|
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS1_HEX
|
||||||
|
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_PLUS1_HEX
|
||||||
|
import prog8.compiler.toHex
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
|
// todo choose more efficient comparisons to avoid needless lda's
|
||||||
|
// todo optimize common case step == 2 / -2
|
||||||
|
|
||||||
|
|
||||||
|
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||||
|
|
||||||
|
internal fun translate(stmt: ForLoop) {
|
||||||
|
val iterableDt = stmt.iterable.inferType(program)
|
||||||
|
if(!iterableDt.isKnown)
|
||||||
|
throw AssemblyError("can't determine iterable dt")
|
||||||
|
when(stmt.iterable) {
|
||||||
|
is RangeExpr -> {
|
||||||
|
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange()
|
||||||
|
if(range==null) {
|
||||||
|
translateForOverNonconstRange(stmt, iterableDt.typeOrElse(DataType.STRUCT), stmt.iterable as RangeExpr)
|
||||||
|
} else {
|
||||||
|
translateForOverConstRange(stmt, iterableDt.typeOrElse(DataType.STRUCT), range)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
translateForOverIterableVar(stmt, iterableDt.typeOrElse(DataType.STRUCT), stmt.iterable as IdentifierReference)
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("can't iterate over ${stmt.iterable}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateForOverNonconstRange(stmt: ForLoop, iterableDt: DataType, range: RangeExpr) {
|
||||||
|
val loopLabel = asmgen.makeLabel("for_loop")
|
||||||
|
val endLabel = asmgen.makeLabel("for_end")
|
||||||
|
val continueLabel = asmgen.makeLabel("for_continue")
|
||||||
|
asmgen.loopEndLabels.push(endLabel)
|
||||||
|
asmgen.loopContinueLabels.push(continueLabel)
|
||||||
|
val stepsize=range.step.constValue(program)!!.number.toInt()
|
||||||
|
when(iterableDt) {
|
||||||
|
DataType.ARRAY_B, DataType.ARRAY_UB -> {
|
||||||
|
if (stepsize==1 || stepsize==-1) {
|
||||||
|
|
||||||
|
// bytes, step 1 or -1
|
||||||
|
|
||||||
|
val incdec = if(stepsize==1) "inc" else "dec"
|
||||||
|
if (stmt.loopRegister != null) {
|
||||||
|
// loop register over range
|
||||||
|
if(stmt.loopRegister!= Register.A)
|
||||||
|
throw AssemblyError("can only use A")
|
||||||
|
asmgen.translateExpression(range.to)
|
||||||
|
asmgen.translateExpression(range.from)
|
||||||
|
asmgen.out("""
|
||||||
|
inx
|
||||||
|
lda ${ESTACK_LO_HEX},x
|
||||||
|
sta $loopLabel+1
|
||||||
|
$loopLabel lda #0 ; modified""")
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
asmgen.out("""
|
||||||
|
$continueLabel lda $loopLabel+1
|
||||||
|
cmp $ESTACK_LO_PLUS1_HEX,x
|
||||||
|
beq $endLabel
|
||||||
|
$incdec $loopLabel+1
|
||||||
|
jmp $loopLabel
|
||||||
|
$endLabel inx""")
|
||||||
|
} else {
|
||||||
|
// loop over byte range via loopvar
|
||||||
|
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
|
||||||
|
asmgen.translateExpression(range.to)
|
||||||
|
asmgen.translateExpression(range.from)
|
||||||
|
asmgen.out("""
|
||||||
|
inx
|
||||||
|
lda ${ESTACK_LO_HEX},x
|
||||||
|
sta $varname
|
||||||
|
$loopLabel""")
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
asmgen.out("""
|
||||||
|
$continueLabel lda $varname
|
||||||
|
cmp $ESTACK_LO_PLUS1_HEX,x
|
||||||
|
beq $endLabel
|
||||||
|
$incdec $varname
|
||||||
|
jmp $loopLabel
|
||||||
|
$endLabel inx""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
// bytes, step >= 2 or <= -2
|
||||||
|
|
||||||
|
if (stmt.loopRegister != null) {
|
||||||
|
// loop register over range
|
||||||
|
if(stmt.loopRegister!= Register.A)
|
||||||
|
throw AssemblyError("can only use A")
|
||||||
|
asmgen.translateExpression(range.to)
|
||||||
|
asmgen.translateExpression(range.from)
|
||||||
|
asmgen.out("""
|
||||||
|
inx
|
||||||
|
lda ${ESTACK_LO_HEX},x
|
||||||
|
sta $loopLabel+1
|
||||||
|
$loopLabel lda #0 ; modified""")
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
asmgen.out("""
|
||||||
|
$continueLabel lda $loopLabel+1""")
|
||||||
|
if(stepsize>0) {
|
||||||
|
asmgen.out("""
|
||||||
|
clc
|
||||||
|
adc #$stepsize
|
||||||
|
sta $loopLabel+1
|
||||||
|
cmp $ESTACK_LO_PLUS1_HEX,x
|
||||||
|
bcc $loopLabel
|
||||||
|
beq $loopLabel""")
|
||||||
|
} else {
|
||||||
|
asmgen.out("""
|
||||||
|
sec
|
||||||
|
sbc #${stepsize.absoluteValue}
|
||||||
|
sta $loopLabel+1
|
||||||
|
cmp $ESTACK_LO_PLUS1_HEX,x
|
||||||
|
bcs $loopLabel""")
|
||||||
|
}
|
||||||
|
asmgen.out("""
|
||||||
|
$endLabel inx""")
|
||||||
|
} else {
|
||||||
|
// loop over byte range via loopvar
|
||||||
|
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
|
||||||
|
asmgen.translateExpression(range.to)
|
||||||
|
asmgen.translateExpression(range.from)
|
||||||
|
asmgen.out("""
|
||||||
|
inx
|
||||||
|
lda ${ESTACK_LO_HEX},x
|
||||||
|
sta $varname
|
||||||
|
$loopLabel""")
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
asmgen.out("""
|
||||||
|
$continueLabel lda $varname""")
|
||||||
|
if(stepsize>0) {
|
||||||
|
asmgen.out("""
|
||||||
|
clc
|
||||||
|
adc #$stepsize
|
||||||
|
sta $varname
|
||||||
|
cmp $ESTACK_LO_PLUS1_HEX,x
|
||||||
|
bcc $loopLabel
|
||||||
|
beq $loopLabel""")
|
||||||
|
} else {
|
||||||
|
asmgen.out("""
|
||||||
|
sec
|
||||||
|
sbc #${stepsize.absoluteValue}
|
||||||
|
sta $varname
|
||||||
|
cmp $ESTACK_LO_PLUS1_HEX,x
|
||||||
|
bcs $loopLabel""")
|
||||||
|
}
|
||||||
|
asmgen.out("""
|
||||||
|
$endLabel inx""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
||||||
|
when {
|
||||||
|
|
||||||
|
// words, step 1 or -1
|
||||||
|
|
||||||
|
stepsize == 1 || stepsize == -1 -> {
|
||||||
|
asmgen.translateExpression(range.to)
|
||||||
|
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
|
||||||
|
val assignLoopvar = Assignment(AssignTarget(null, stmt.loopVar, null, null, stmt.loopVar!!.position),
|
||||||
|
null, range.from, range.position)
|
||||||
|
assignLoopvar.linkParents(stmt)
|
||||||
|
asmgen.translate(assignLoopvar)
|
||||||
|
asmgen.out(loopLabel)
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
asmgen.out("""
|
||||||
|
lda $varname+1
|
||||||
|
cmp $ESTACK_HI_PLUS1_HEX,x
|
||||||
|
bne +
|
||||||
|
lda $varname
|
||||||
|
cmp $ESTACK_LO_PLUS1_HEX,x
|
||||||
|
beq $endLabel""")
|
||||||
|
if(stepsize==1) {
|
||||||
|
asmgen.out("""
|
||||||
|
+ inc $varname
|
||||||
|
bne +
|
||||||
|
inc $varname+1
|
||||||
|
""")
|
||||||
|
} else {
|
||||||
|
asmgen.out("""
|
||||||
|
+ lda $varname
|
||||||
|
bne +
|
||||||
|
dec $varname+1
|
||||||
|
+ dec $varname""")
|
||||||
|
}
|
||||||
|
asmgen.out("""
|
||||||
|
+ jmp $loopLabel
|
||||||
|
$endLabel inx""")
|
||||||
|
}
|
||||||
|
stepsize > 0 -> {
|
||||||
|
|
||||||
|
// (u)words, step >= 2
|
||||||
|
|
||||||
|
asmgen.translateExpression(range.to)
|
||||||
|
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
|
||||||
|
val assignLoopvar = Assignment(AssignTarget(null, stmt.loopVar, null, null, stmt.loopVar!!.position),
|
||||||
|
null, range.from, range.position)
|
||||||
|
assignLoopvar.linkParents(stmt)
|
||||||
|
asmgen.translate(assignLoopvar)
|
||||||
|
asmgen.out(loopLabel)
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
|
||||||
|
if (iterableDt == DataType.ARRAY_UW) {
|
||||||
|
asmgen.out("""
|
||||||
|
lda $varname
|
||||||
|
clc
|
||||||
|
adc #<$stepsize
|
||||||
|
sta $varname
|
||||||
|
lda $varname+1
|
||||||
|
adc #>$stepsize
|
||||||
|
sta $varname+1
|
||||||
|
lda $ESTACK_HI_PLUS1_HEX,x
|
||||||
|
cmp $varname+1
|
||||||
|
bcc $endLabel
|
||||||
|
bne $loopLabel
|
||||||
|
lda $varname
|
||||||
|
cmp $ESTACK_LO_PLUS1_HEX,x
|
||||||
|
bcc $endLabel
|
||||||
|
bcs $loopLabel
|
||||||
|
$endLabel inx""")
|
||||||
|
} else {
|
||||||
|
asmgen.out("""
|
||||||
|
lda $varname
|
||||||
|
clc
|
||||||
|
adc #<$stepsize
|
||||||
|
sta $varname
|
||||||
|
lda $varname+1
|
||||||
|
adc #>$stepsize
|
||||||
|
sta $varname+1
|
||||||
|
lda $ESTACK_LO_PLUS1_HEX,x
|
||||||
|
cmp $varname
|
||||||
|
lda $ESTACK_HI_PLUS1_HEX,x
|
||||||
|
sbc $varname+1
|
||||||
|
bvc +
|
||||||
|
eor #$80
|
||||||
|
+ bpl $loopLabel
|
||||||
|
$endLabel inx""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
|
||||||
|
// (u)words, step <= -2
|
||||||
|
asmgen.translateExpression(range.to)
|
||||||
|
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
|
||||||
|
val assignLoopvar = Assignment(AssignTarget(null, stmt.loopVar, null, null, stmt.loopVar!!.position),
|
||||||
|
null, range.from, range.position)
|
||||||
|
assignLoopvar.linkParents(stmt)
|
||||||
|
asmgen.translate(assignLoopvar)
|
||||||
|
asmgen.out(loopLabel)
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
|
||||||
|
if(iterableDt==DataType.ARRAY_UW) {
|
||||||
|
asmgen.out("""
|
||||||
|
lda $varname
|
||||||
|
sec
|
||||||
|
sbc #<${stepsize.absoluteValue}
|
||||||
|
sta $varname
|
||||||
|
lda $varname+1
|
||||||
|
sbc #>${stepsize.absoluteValue}
|
||||||
|
sta $varname+1
|
||||||
|
cmp $ESTACK_HI_PLUS1_HEX,x
|
||||||
|
bcc $endLabel
|
||||||
|
bne $loopLabel
|
||||||
|
lda $varname
|
||||||
|
cmp $ESTACK_LO_PLUS1_HEX,x
|
||||||
|
bcs $loopLabel
|
||||||
|
$endLabel inx""")
|
||||||
|
} else {
|
||||||
|
asmgen.out("""
|
||||||
|
lda $varname
|
||||||
|
sec
|
||||||
|
sbc #<${stepsize.absoluteValue}
|
||||||
|
sta $varname
|
||||||
|
pha
|
||||||
|
lda $varname+1
|
||||||
|
sbc #>${stepsize.absoluteValue}
|
||||||
|
sta $varname+1
|
||||||
|
pla
|
||||||
|
cmp $ESTACK_LO_PLUS1_HEX,x
|
||||||
|
lda $varname+1
|
||||||
|
sbc $ESTACK_HI_PLUS1_HEX,x
|
||||||
|
bvc +
|
||||||
|
eor #$80
|
||||||
|
+ bpl $loopLabel
|
||||||
|
$endLabel inx""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("range expression can only be byte or word")
|
||||||
|
}
|
||||||
|
|
||||||
|
asmgen.loopEndLabels.pop()
|
||||||
|
asmgen.loopContinueLabels.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateForOverIterableVar(stmt: ForLoop, iterableDt: DataType, ident: IdentifierReference) {
|
||||||
|
val loopLabel = asmgen.makeLabel("for_loop")
|
||||||
|
val endLabel = asmgen.makeLabel("for_end")
|
||||||
|
val continueLabel = asmgen.makeLabel("for_continue")
|
||||||
|
asmgen.loopEndLabels.push(endLabel)
|
||||||
|
asmgen.loopContinueLabels.push(continueLabel)
|
||||||
|
val iterableName = asmgen.asmIdentifierName(ident)
|
||||||
|
val decl = ident.targetVarDecl(program.namespace)!!
|
||||||
|
when(iterableDt) {
|
||||||
|
DataType.STR, DataType.STR_S -> {
|
||||||
|
if(stmt.loopRegister!=null && stmt.loopRegister!= Register.A)
|
||||||
|
throw AssemblyError("can only use A")
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<$iterableName
|
||||||
|
ldy #>$iterableName
|
||||||
|
sta $loopLabel+1
|
||||||
|
sty $loopLabel+2
|
||||||
|
$loopLabel lda ${65535.toHex()} ; modified
|
||||||
|
beq $endLabel""")
|
||||||
|
if(stmt.loopVar!=null)
|
||||||
|
asmgen.out(" sta ${asmgen.asmIdentifierName(stmt.loopVar!!)}")
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
asmgen.out("""
|
||||||
|
$continueLabel inc $loopLabel+1
|
||||||
|
bne $loopLabel
|
||||||
|
inc $loopLabel+2
|
||||||
|
bne $loopLabel
|
||||||
|
$endLabel""")
|
||||||
|
}
|
||||||
|
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||||
|
// TODO: optimize loop code when the length of the array is < 256, don't need a separate counter in such cases
|
||||||
|
val length = decl.arraysize!!.size()!!
|
||||||
|
if(stmt.loopRegister!=null && stmt.loopRegister!= Register.A)
|
||||||
|
throw AssemblyError("can only use A")
|
||||||
|
val counterLabel = asmgen.makeLabel("for_counter")
|
||||||
|
val modifiedLabel = asmgen.makeLabel("for_modified")
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<$iterableName
|
||||||
|
ldy #>$iterableName
|
||||||
|
sta $modifiedLabel+1
|
||||||
|
sty $modifiedLabel+2
|
||||||
|
ldy #0
|
||||||
|
$loopLabel sty $counterLabel
|
||||||
|
$modifiedLabel lda ${65535.toHex()},y ; modified""")
|
||||||
|
if(stmt.loopVar!=null)
|
||||||
|
asmgen.out(" sta ${asmgen.asmIdentifierName(stmt.loopVar!!)}")
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
asmgen.out("""
|
||||||
|
$continueLabel ldy $counterLabel
|
||||||
|
iny
|
||||||
|
cpy #${length and 255}
|
||||||
|
beq $endLabel
|
||||||
|
jmp $loopLabel
|
||||||
|
$counterLabel .byte 0
|
||||||
|
$endLabel""")
|
||||||
|
}
|
||||||
|
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
||||||
|
// TODO: optimize loop code when the length of the array is < 256, don't need a separate counter in such cases
|
||||||
|
val length = decl.arraysize!!.size()!! * 2
|
||||||
|
if(stmt.loopRegister!=null)
|
||||||
|
throw AssemblyError("can't use register to loop over words")
|
||||||
|
val counterLabel = asmgen.makeLabel("for_counter")
|
||||||
|
val modifiedLabel = asmgen.makeLabel("for_modified")
|
||||||
|
val modifiedLabel2 = asmgen.makeLabel("for_modified2")
|
||||||
|
val loopvarName = asmgen.asmIdentifierName(stmt.loopVar!!)
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<$iterableName
|
||||||
|
ldy #>$iterableName
|
||||||
|
sta $modifiedLabel+1
|
||||||
|
sty $modifiedLabel+2
|
||||||
|
lda #<$iterableName+1
|
||||||
|
ldy #>$iterableName+1
|
||||||
|
sta $modifiedLabel2+1
|
||||||
|
sty $modifiedLabel2+2
|
||||||
|
ldy #0
|
||||||
|
$loopLabel sty $counterLabel
|
||||||
|
$modifiedLabel lda ${65535.toHex()},y ; modified
|
||||||
|
sta $loopvarName
|
||||||
|
$modifiedLabel2 lda ${65535.toHex()},y ; modified
|
||||||
|
sta $loopvarName+1""")
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
asmgen.out("""
|
||||||
|
$continueLabel ldy $counterLabel
|
||||||
|
iny
|
||||||
|
iny
|
||||||
|
cpy #${length and 255}
|
||||||
|
beq $endLabel
|
||||||
|
jmp $loopLabel
|
||||||
|
$counterLabel .byte 0
|
||||||
|
$endLabel""")
|
||||||
|
}
|
||||||
|
DataType.ARRAY_F -> {
|
||||||
|
throw AssemblyError("for loop with floating point variables is not supported")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("can't iterate over $iterableDt")
|
||||||
|
}
|
||||||
|
asmgen.loopEndLabels.pop()
|
||||||
|
asmgen.loopContinueLabels.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateForOverConstRange(stmt: ForLoop, iterableDt: DataType, range: IntProgression) {
|
||||||
|
// TODO: optimize loop code when the range is < 256 iterations, don't need a separate counter in such cases
|
||||||
|
if (range.isEmpty())
|
||||||
|
throw AssemblyError("empty range")
|
||||||
|
val loopLabel = asmgen.makeLabel("for_loop")
|
||||||
|
val endLabel = asmgen.makeLabel("for_end")
|
||||||
|
val continueLabel = asmgen.makeLabel("for_continue")
|
||||||
|
asmgen.loopEndLabels.push(endLabel)
|
||||||
|
asmgen.loopContinueLabels.push(continueLabel)
|
||||||
|
when(iterableDt) {
|
||||||
|
DataType.ARRAY_B, DataType.ARRAY_UB -> {
|
||||||
|
val counterLabel = asmgen.makeLabel("for_counter")
|
||||||
|
if(stmt.loopRegister!=null) {
|
||||||
|
|
||||||
|
// loop register over range
|
||||||
|
|
||||||
|
if(stmt.loopRegister!= Register.A)
|
||||||
|
throw AssemblyError("can only use A")
|
||||||
|
when {
|
||||||
|
range.step==1 -> {
|
||||||
|
// step = 1
|
||||||
|
asmgen.out("""
|
||||||
|
lda #${range.first}
|
||||||
|
sta $loopLabel+1
|
||||||
|
lda #${range.last-range.first+1 and 255}
|
||||||
|
sta $counterLabel
|
||||||
|
$loopLabel lda #0 ; modified""")
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
asmgen.out("""
|
||||||
|
$continueLabel dec $counterLabel
|
||||||
|
beq $endLabel
|
||||||
|
inc $loopLabel+1
|
||||||
|
jmp $loopLabel
|
||||||
|
$counterLabel .byte 0
|
||||||
|
$endLabel""")
|
||||||
|
}
|
||||||
|
range.step==-1 -> {
|
||||||
|
// step = -1
|
||||||
|
asmgen.out("""
|
||||||
|
lda #${range.first}
|
||||||
|
sta $loopLabel+1
|
||||||
|
lda #${range.first-range.last+1 and 255}
|
||||||
|
sta $counterLabel
|
||||||
|
$loopLabel lda #0 ; modified """)
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
asmgen.out("""
|
||||||
|
$continueLabel dec $counterLabel
|
||||||
|
beq $endLabel
|
||||||
|
dec $loopLabel+1
|
||||||
|
jmp $loopLabel
|
||||||
|
$counterLabel .byte 0
|
||||||
|
$endLabel""")
|
||||||
|
}
|
||||||
|
range.step >= 2 -> {
|
||||||
|
// step >= 2
|
||||||
|
asmgen.out("""
|
||||||
|
lda #${(range.last-range.first) / range.step + 1}
|
||||||
|
sta $counterLabel
|
||||||
|
lda #${range.first}
|
||||||
|
$loopLabel pha""")
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
asmgen.out("""
|
||||||
|
$continueLabel pla
|
||||||
|
dec $counterLabel
|
||||||
|
beq $endLabel
|
||||||
|
clc
|
||||||
|
adc #${range.step}
|
||||||
|
jmp $loopLabel
|
||||||
|
$counterLabel .byte 0
|
||||||
|
$endLabel""")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// step <= -2
|
||||||
|
asmgen.out("""
|
||||||
|
lda #${(range.first-range.last) / range.step.absoluteValue + 1}
|
||||||
|
sta $counterLabel
|
||||||
|
lda #${range.first}
|
||||||
|
$loopLabel pha""")
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
asmgen.out("""
|
||||||
|
$continueLabel pla
|
||||||
|
dec $counterLabel
|
||||||
|
beq $endLabel
|
||||||
|
sec
|
||||||
|
sbc #${range.step.absoluteValue}
|
||||||
|
jmp $loopLabel
|
||||||
|
$counterLabel .byte 0
|
||||||
|
$endLabel""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// loop over byte range via loopvar
|
||||||
|
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
|
||||||
|
when {
|
||||||
|
range.step==1 -> {
|
||||||
|
// step = 1
|
||||||
|
asmgen.out("""
|
||||||
|
lda #${range.first}
|
||||||
|
sta $varname
|
||||||
|
lda #${range.last-range.first+1 and 255}
|
||||||
|
sta $counterLabel
|
||||||
|
$loopLabel""")
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
asmgen.out("""
|
||||||
|
$continueLabel dec $counterLabel
|
||||||
|
beq $endLabel
|
||||||
|
inc $varname
|
||||||
|
jmp $loopLabel
|
||||||
|
$counterLabel .byte 0
|
||||||
|
$endLabel""")
|
||||||
|
}
|
||||||
|
range.step==-1 -> {
|
||||||
|
// step = -1
|
||||||
|
asmgen.out("""
|
||||||
|
lda #${range.first}
|
||||||
|
sta $varname
|
||||||
|
lda #${range.first-range.last+1 and 255}
|
||||||
|
sta $counterLabel
|
||||||
|
$loopLabel""")
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
asmgen.out("""
|
||||||
|
$continueLabel dec $counterLabel
|
||||||
|
beq $endLabel
|
||||||
|
dec $varname
|
||||||
|
jmp $loopLabel
|
||||||
|
$counterLabel .byte 0
|
||||||
|
$endLabel""")
|
||||||
|
}
|
||||||
|
range.step >= 2 -> {
|
||||||
|
// step >= 2
|
||||||
|
asmgen.out("""
|
||||||
|
lda #${(range.last-range.first) / range.step + 1}
|
||||||
|
sta $counterLabel
|
||||||
|
lda #${range.first}
|
||||||
|
sta $varname
|
||||||
|
$loopLabel""")
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
asmgen.out("""
|
||||||
|
$continueLabel dec $counterLabel
|
||||||
|
beq $endLabel
|
||||||
|
lda $varname
|
||||||
|
clc
|
||||||
|
adc #${range.step}
|
||||||
|
sta $varname
|
||||||
|
jmp $loopLabel
|
||||||
|
$counterLabel .byte 0
|
||||||
|
$endLabel""")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// step <= -2
|
||||||
|
asmgen.out("""
|
||||||
|
lda #${(range.first-range.last) / range.step.absoluteValue + 1}
|
||||||
|
sta $counterLabel
|
||||||
|
lda #${range.first}
|
||||||
|
sta $varname
|
||||||
|
$loopLabel""")
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
asmgen.out("""
|
||||||
|
$continueLabel dec $counterLabel
|
||||||
|
beq $endLabel
|
||||||
|
lda $varname
|
||||||
|
sec
|
||||||
|
sbc #${range.step.absoluteValue}
|
||||||
|
sta $varname
|
||||||
|
jmp $loopLabel
|
||||||
|
$counterLabel .byte 0
|
||||||
|
$endLabel""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
||||||
|
// loop over word range via loopvar
|
||||||
|
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
|
||||||
|
when {
|
||||||
|
range.step == 1 -> {
|
||||||
|
// word, step = 1
|
||||||
|
val lastValue = range.last+1
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<${range.first}
|
||||||
|
ldy #>${range.first}
|
||||||
|
sta $varname
|
||||||
|
sty $varname+1
|
||||||
|
$loopLabel""")
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
asmgen.out("""
|
||||||
|
$continueLabel inc $varname
|
||||||
|
bne +
|
||||||
|
inc $varname+1
|
||||||
|
+ lda $varname
|
||||||
|
cmp #<$lastValue
|
||||||
|
bne +
|
||||||
|
lda $varname+1
|
||||||
|
cmp #>$lastValue
|
||||||
|
beq $endLabel
|
||||||
|
+ jmp $loopLabel
|
||||||
|
$endLabel""")
|
||||||
|
}
|
||||||
|
range.step == -1 -> {
|
||||||
|
// word, step = 1
|
||||||
|
val lastValue = range.last-1
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<${range.first}
|
||||||
|
ldy #>${range.first}
|
||||||
|
sta $varname
|
||||||
|
sty $varname+1
|
||||||
|
$loopLabel""")
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
asmgen.out("""
|
||||||
|
$continueLabel lda $varname
|
||||||
|
bne +
|
||||||
|
dec $varname+1
|
||||||
|
+ dec $varname
|
||||||
|
lda $varname
|
||||||
|
cmp #<$lastValue
|
||||||
|
bne +
|
||||||
|
lda $varname+1
|
||||||
|
cmp #>$lastValue
|
||||||
|
beq $endLabel
|
||||||
|
+ jmp $loopLabel
|
||||||
|
$endLabel""")
|
||||||
|
}
|
||||||
|
range.step >= 2 -> {
|
||||||
|
// word, step >= 2
|
||||||
|
// note: range.last has already been adjusted by kotlin itself to actually be the last value of the sequence
|
||||||
|
val lastValue = range.last+range.step
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<${range.first}
|
||||||
|
ldy #>${range.first}
|
||||||
|
sta $varname
|
||||||
|
sty $varname+1
|
||||||
|
$loopLabel""")
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
asmgen.out("""
|
||||||
|
$continueLabel clc
|
||||||
|
lda $varname
|
||||||
|
adc #<${range.step}
|
||||||
|
sta $varname
|
||||||
|
lda $varname+1
|
||||||
|
adc #>${range.step}
|
||||||
|
sta $varname+1
|
||||||
|
lda $varname
|
||||||
|
cmp #<$lastValue
|
||||||
|
bne +
|
||||||
|
lda $varname+1
|
||||||
|
cmp #>$lastValue
|
||||||
|
beq $endLabel
|
||||||
|
+ jmp $loopLabel
|
||||||
|
$endLabel""")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// step <= -2
|
||||||
|
// note: range.last has already been adjusted by kotlin itself to actually be the last value of the sequence
|
||||||
|
val lastValue = range.last+range.step
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<${range.first}
|
||||||
|
ldy #>${range.first}
|
||||||
|
sta $varname
|
||||||
|
sty $varname+1
|
||||||
|
$loopLabel""")
|
||||||
|
asmgen.translate(stmt.body)
|
||||||
|
asmgen.out("""
|
||||||
|
$continueLabel sec
|
||||||
|
lda $varname
|
||||||
|
sbc #<${range.step.absoluteValue}
|
||||||
|
sta $varname
|
||||||
|
lda $varname+1
|
||||||
|
sbc #>${range.step.absoluteValue}
|
||||||
|
sta $varname+1
|
||||||
|
lda $varname
|
||||||
|
cmp #<$lastValue
|
||||||
|
bne +
|
||||||
|
lda $varname+1
|
||||||
|
cmp #>$lastValue
|
||||||
|
beq $endLabel
|
||||||
|
+ jmp $loopLabel
|
||||||
|
$endLabel""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("range expression can only be byte or word")
|
||||||
|
}
|
||||||
|
asmgen.loopEndLabels.pop()
|
||||||
|
asmgen.loopContinueLabels.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,235 @@
|
|||||||
|
package prog8.compiler.target.c64.codegen
|
||||||
|
|
||||||
|
import prog8.ast.IFunctionCall
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.*
|
||||||
|
import prog8.ast.expressions.*
|
||||||
|
import prog8.ast.statements.AssignTarget
|
||||||
|
import prog8.ast.statements.Subroutine
|
||||||
|
import prog8.ast.statements.SubroutineParameter
|
||||||
|
import prog8.compiler.target.c64.MachineDefinition
|
||||||
|
import prog8.compiler.toHex
|
||||||
|
|
||||||
|
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||||
|
|
||||||
|
internal fun translateFunctionCall(stmt: IFunctionCall) {
|
||||||
|
// output the code to setup the parameters and perform the actual call
|
||||||
|
// does NOT output the code to deal with the result values!
|
||||||
|
val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
||||||
|
if(Register.X in sub.asmClobbers)
|
||||||
|
asmgen.out(" stx c64.SCRATCH_ZPREGX") // we only save X for now (required! is the eval stack pointer), screw A and Y...
|
||||||
|
|
||||||
|
val subName = asmgen.asmIdentifierName(stmt.target)
|
||||||
|
if(stmt.arglist.isNotEmpty()) {
|
||||||
|
for(arg in sub.parameters.withIndex().zip(stmt.arglist)) {
|
||||||
|
translateFuncArguments(arg.first, arg.second, sub)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
asmgen.out(" jsr $subName")
|
||||||
|
|
||||||
|
if(Register.X in sub.asmClobbers)
|
||||||
|
asmgen.out(" ldx c64.SCRATCH_ZPREGX") // restore X again
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateFuncArguments(parameter: IndexedValue<SubroutineParameter>, value: Expression, sub: Subroutine) {
|
||||||
|
val sourceIDt = value.inferType(program)
|
||||||
|
if(!sourceIDt.isKnown)
|
||||||
|
throw AssemblyError("arg type unknown")
|
||||||
|
val sourceDt = sourceIDt.typeOrElse(DataType.STRUCT)
|
||||||
|
if(!argumentTypeCompatible(sourceDt, parameter.value.type))
|
||||||
|
throw AssemblyError("argument type incompatible")
|
||||||
|
if(sub.asmParameterRegisters.isEmpty()) {
|
||||||
|
// pass parameter via a variable
|
||||||
|
val paramVar = parameter.value
|
||||||
|
val scopedParamVar = (sub.scopedname+"."+paramVar.name).split(".")
|
||||||
|
val target = AssignTarget(null, IdentifierReference(scopedParamVar, sub.position), null, null, sub.position)
|
||||||
|
target.linkParents(value.parent)
|
||||||
|
when (value) {
|
||||||
|
is NumericLiteralValue -> {
|
||||||
|
// optimize when the argument is a constant literal
|
||||||
|
when(parameter.value.type) {
|
||||||
|
in ByteDatatypes -> asmgen.assignFromByteConstant(target, value.number.toShort())
|
||||||
|
in WordDatatypes -> asmgen.assignFromWordConstant(target, value.number.toInt())
|
||||||
|
DataType.FLOAT -> asmgen.assignFromFloatConstant(target, value.number.toDouble())
|
||||||
|
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as arguments?")
|
||||||
|
else -> throw AssemblyError("weird parameter datatype")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
// optimize when the argument is a variable
|
||||||
|
when (parameter.value.type) {
|
||||||
|
in ByteDatatypes -> asmgen.assignFromByteVariable(target, value)
|
||||||
|
in WordDatatypes -> asmgen.assignFromWordVariable(target, value)
|
||||||
|
DataType.FLOAT -> asmgen.assignFromFloatVariable(target, value)
|
||||||
|
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as arguments?")
|
||||||
|
else -> throw AssemblyError("weird parameter datatype")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is RegisterExpr -> {
|
||||||
|
asmgen.assignFromRegister(target, value.register)
|
||||||
|
}
|
||||||
|
is DirectMemoryRead -> {
|
||||||
|
when(value.addressExpression) {
|
||||||
|
is NumericLiteralValue -> {
|
||||||
|
val address = (value.addressExpression as NumericLiteralValue).number.toInt()
|
||||||
|
asmgen.assignFromMemoryByte(target, address, null)
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
asmgen.assignFromMemoryByte(target, null, value.addressExpression as IdentifierReference)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
asmgen.translateExpression(value.addressExpression)
|
||||||
|
asmgen.out(" jsr prog8_lib.read_byte_from_address | inx")
|
||||||
|
asmgen.assignFromRegister(target, Register.A)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
asmgen.translateExpression(value)
|
||||||
|
asmgen.assignFromEvalResult(target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// pass parameter via a register parameter
|
||||||
|
val paramRegister = sub.asmParameterRegisters[parameter.index]
|
||||||
|
val statusflag = paramRegister.statusflag
|
||||||
|
val register = paramRegister.registerOrPair
|
||||||
|
val stack = paramRegister.stack
|
||||||
|
when {
|
||||||
|
stack -> {
|
||||||
|
// push arg onto the stack
|
||||||
|
// note: argument order is reversed (first argument will be deepest on the stack)
|
||||||
|
asmgen.translateExpression(value)
|
||||||
|
}
|
||||||
|
statusflag!=null -> {
|
||||||
|
if (statusflag == Statusflag.Pc) {
|
||||||
|
// 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 -> {
|
||||||
|
val carrySet = value.number.toInt() != 0
|
||||||
|
asmgen.out(if(carrySet) " sec" else " clc")
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val sourceName = asmgen.asmIdentifierName(value)
|
||||||
|
asmgen.out("""
|
||||||
|
lda $sourceName
|
||||||
|
beq +
|
||||||
|
sec
|
||||||
|
bcs ++
|
||||||
|
+ clc
|
||||||
|
+
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
is RegisterExpr -> {
|
||||||
|
when(value.register) {
|
||||||
|
Register.A -> asmgen.out(" cmp #0")
|
||||||
|
Register.X -> asmgen.out(" txa")
|
||||||
|
Register.Y -> asmgen.out(" tya")
|
||||||
|
}
|
||||||
|
asmgen.out("""
|
||||||
|
beq +
|
||||||
|
sec
|
||||||
|
bcs ++
|
||||||
|
+ clc
|
||||||
|
+
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
asmgen.translateExpression(value)
|
||||||
|
asmgen.out("""
|
||||||
|
inx
|
||||||
|
lda ${MachineDefinition.ESTACK_LO_HEX},x
|
||||||
|
beq +
|
||||||
|
sec
|
||||||
|
bcs ++
|
||||||
|
+ clc
|
||||||
|
+
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else throw AssemblyError("can only use Carry as status flag parameter")
|
||||||
|
}
|
||||||
|
register!=null && register.name.length==1 -> {
|
||||||
|
when (value) {
|
||||||
|
is NumericLiteralValue -> {
|
||||||
|
val target = AssignTarget(Register.valueOf(register.name), null, null, null, sub.position)
|
||||||
|
target.linkParents(value.parent)
|
||||||
|
asmgen.assignFromByteConstant(target, value.number.toShort())
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val target = AssignTarget(Register.valueOf(register.name), null, null, null, sub.position)
|
||||||
|
target.linkParents(value.parent)
|
||||||
|
asmgen.assignFromByteVariable(target, value)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
asmgen.translateExpression(value)
|
||||||
|
when(register) {
|
||||||
|
RegisterOrPair.A -> asmgen.out(" inx | lda ${MachineDefinition.ESTACK_LO_HEX},x")
|
||||||
|
RegisterOrPair.X -> throw AssemblyError("can't pop into X register - use a variable instead")
|
||||||
|
RegisterOrPair.Y -> asmgen.out(" inx | ldy ${MachineDefinition.ESTACK_LO_HEX},x")
|
||||||
|
else -> throw AssemblyError("cannot assign to register pair")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
register!=null && register.name.length==2 -> {
|
||||||
|
// register pair as a 16-bit value (only possible for subroutine parameters)
|
||||||
|
when (value) {
|
||||||
|
is NumericLiteralValue -> {
|
||||||
|
// optimize when the argument is a constant literal
|
||||||
|
val hex = value.number.toHex()
|
||||||
|
when (register) {
|
||||||
|
RegisterOrPair.AX -> asmgen.out(" lda #<$hex | ldx #>$hex")
|
||||||
|
RegisterOrPair.AY -> asmgen.out(" lda #<$hex | ldy #>$hex")
|
||||||
|
RegisterOrPair.XY -> asmgen.out(" ldx #<$hex | ldy #>$hex")
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is AddressOf -> {
|
||||||
|
// optimize when the argument is an address of something
|
||||||
|
val sourceName = asmgen.asmIdentifierName(value.identifier)
|
||||||
|
when (register) {
|
||||||
|
RegisterOrPair.AX -> asmgen.out(" lda #<$sourceName | ldx #>$sourceName")
|
||||||
|
RegisterOrPair.AY -> asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
|
||||||
|
RegisterOrPair.XY -> asmgen.out(" ldx #<$sourceName | ldy #>$sourceName")
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val sourceName = asmgen.asmIdentifierName(value)
|
||||||
|
when (register) {
|
||||||
|
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx $sourceName+1")
|
||||||
|
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy $sourceName+1")
|
||||||
|
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy $sourceName+1")
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
asmgen.translateExpression(value)
|
||||||
|
if (register == RegisterOrPair.AX || register == RegisterOrPair.XY)
|
||||||
|
throw AssemblyError("can't use X register here - use a variable")
|
||||||
|
else if (register == RegisterOrPair.AY)
|
||||||
|
asmgen.out(" inx | lda ${MachineDefinition.ESTACK_LO_HEX},x | ldy ${MachineDefinition.ESTACK_HI_HEX},x")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun argumentTypeCompatible(argType: DataType, paramType: DataType): Boolean {
|
||||||
|
if(argType isAssignableTo paramType)
|
||||||
|
return true
|
||||||
|
|
||||||
|
// we have a special rule for some types.
|
||||||
|
// strings are assignable to UWORD, for example, and vice versa
|
||||||
|
if(argType in StringDatatypes && paramType==DataType.UWORD)
|
||||||
|
return true
|
||||||
|
if(argType==DataType.UWORD && paramType in StringDatatypes)
|
||||||
|
return true
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,148 @@
|
|||||||
|
package prog8.compiler.target.c64.codegen
|
||||||
|
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.*
|
||||||
|
import prog8.ast.expressions.IdentifierReference
|
||||||
|
import prog8.ast.expressions.NumericLiteralValue
|
||||||
|
import prog8.ast.expressions.RegisterExpr
|
||||||
|
import prog8.ast.statements.PostIncrDecr
|
||||||
|
import prog8.compiler.target.c64.MachineDefinition
|
||||||
|
import prog8.compiler.toHex
|
||||||
|
|
||||||
|
internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||||
|
internal fun translate(stmt: PostIncrDecr) {
|
||||||
|
val incr = stmt.operator=="++"
|
||||||
|
val targetIdent = stmt.target.identifier
|
||||||
|
val targetMemory = stmt.target.memoryAddress
|
||||||
|
val targetArrayIdx = stmt.target.arrayindexed
|
||||||
|
val targetRegister = stmt.target.register
|
||||||
|
when {
|
||||||
|
targetRegister!=null -> {
|
||||||
|
when(targetRegister) {
|
||||||
|
Register.A -> {
|
||||||
|
if(incr)
|
||||||
|
asmgen.out(" clc | adc #1 ")
|
||||||
|
else
|
||||||
|
asmgen.out(" sec | sbc #1 ")
|
||||||
|
}
|
||||||
|
Register.X -> {
|
||||||
|
if(incr) asmgen.out(" inx") else asmgen.out(" dex")
|
||||||
|
}
|
||||||
|
Register.Y -> {
|
||||||
|
if(incr) asmgen.out(" iny") else asmgen.out(" dey")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
targetIdent!=null -> {
|
||||||
|
val what = asmgen.asmIdentifierName(targetIdent)
|
||||||
|
val dt = stmt.target.inferType(program, stmt).typeOrElse(DataType.STRUCT)
|
||||||
|
when (dt) {
|
||||||
|
in ByteDatatypes -> asmgen.out(if (incr) " inc $what" else " dec $what")
|
||||||
|
in WordDatatypes -> {
|
||||||
|
if(incr)
|
||||||
|
asmgen.out(" inc $what | bne + | inc $what+1 |+")
|
||||||
|
else
|
||||||
|
asmgen.out("""
|
||||||
|
lda $what
|
||||||
|
bne +
|
||||||
|
dec $what+1
|
||||||
|
+ dec $what
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
DataType.FLOAT -> {
|
||||||
|
asmgen.out(" lda #<$what | ldy #>$what")
|
||||||
|
asmgen.out(if(incr) " jsr c64flt.inc_var_f" else " jsr c64flt.dec_var_f")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("need numeric type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
targetMemory!=null -> {
|
||||||
|
when (val addressExpr = targetMemory.addressExpression) {
|
||||||
|
is NumericLiteralValue -> {
|
||||||
|
val what = addressExpr.number.toHex()
|
||||||
|
asmgen.out(if(incr) " inc $what" else " dec $what")
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val what = asmgen.asmIdentifierName(addressExpr)
|
||||||
|
asmgen.out(if(incr) " inc $what" else " dec $what")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird target type $targetMemory")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
targetArrayIdx!=null -> {
|
||||||
|
val index = targetArrayIdx.arrayspec.index
|
||||||
|
val what = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||||
|
val arrayDt = targetArrayIdx.identifier.inferType(program).typeOrElse(DataType.STRUCT)
|
||||||
|
val elementDt = ArrayElementTypes.getValue(arrayDt)
|
||||||
|
when(index) {
|
||||||
|
is NumericLiteralValue -> {
|
||||||
|
val indexValue = index.number.toInt() * elementDt.memorySize()
|
||||||
|
when(elementDt) {
|
||||||
|
in ByteDatatypes -> asmgen.out(if (incr) " inc $what+$indexValue" else " dec $what+$indexValue")
|
||||||
|
in WordDatatypes -> {
|
||||||
|
if(incr)
|
||||||
|
asmgen.out(" inc $what+$indexValue | bne + | inc $what+$indexValue+1 |+")
|
||||||
|
else
|
||||||
|
asmgen.out("""
|
||||||
|
lda $what+$indexValue
|
||||||
|
bne +
|
||||||
|
dec $what+$indexValue+1
|
||||||
|
+ dec $what+$indexValue
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
DataType.FLOAT -> {
|
||||||
|
asmgen.out(" lda #<$what+$indexValue | ldy #>$what+$indexValue")
|
||||||
|
asmgen.out(if(incr) " jsr c64flt.inc_var_f" else " jsr c64flt.dec_var_f")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("need numeric type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is RegisterExpr -> {
|
||||||
|
// TODO optimize common cases
|
||||||
|
asmgen.translateArrayIndexIntoA(targetArrayIdx)
|
||||||
|
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
// TODO optimize common cases
|
||||||
|
asmgen.translateArrayIndexIntoA(targetArrayIdx)
|
||||||
|
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// TODO optimize common cases
|
||||||
|
asmgen.translateArrayIndexIntoA(targetArrayIdx)
|
||||||
|
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird target type ${stmt.target}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun incrDecrArrayvalueWithIndexA(incr: Boolean, arrayDt: DataType, arrayVarName: String) {
|
||||||
|
asmgen.out(" stx ${MachineDefinition.C64Zeropage.SCRATCH_REG_X} | tax")
|
||||||
|
when(arrayDt) {
|
||||||
|
DataType.STR, DataType.STR_S,
|
||||||
|
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||||
|
asmgen.out(if(incr) " inc $arrayVarName,x" else " dec $arrayVarName,x")
|
||||||
|
}
|
||||||
|
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||||
|
if(incr)
|
||||||
|
asmgen.out(" inc $arrayVarName,x | bne + | inc $arrayVarName+1,x |+")
|
||||||
|
else
|
||||||
|
asmgen.out("""
|
||||||
|
lda $arrayVarName,x
|
||||||
|
bne +
|
||||||
|
dec $arrayVarName+1,x
|
||||||
|
+ dec $arrayVarName
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
DataType.ARRAY_F -> {
|
||||||
|
asmgen.out(" lda #<$arrayVarName | ldy #>$arrayVarName")
|
||||||
|
asmgen.out(if(incr) " jsr c64flt.inc_indexed_var_f" else " jsr c64flt.dec_indexed_var_f")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird array dt")
|
||||||
|
}
|
||||||
|
asmgen.out(" ldx ${MachineDefinition.C64Zeropage.SCRATCH_REG_X}")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,21 +1,22 @@
|
|||||||
package prog8.functions
|
package prog8.functions
|
||||||
|
|
||||||
import prog8.ast.*
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.expressions.DirectMemoryRead
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.expressions.IdentifierReference
|
|
||||||
import prog8.ast.expressions.LiteralValue
|
|
||||||
import prog8.ast.statements.VarDecl
|
|
||||||
import prog8.compiler.CompilerException
|
import prog8.compiler.CompilerException
|
||||||
import kotlin.math.*
|
import kotlin.math.*
|
||||||
|
|
||||||
|
|
||||||
class BuiltinFunctionParam(val name: String, val possibleDatatypes: Set<DataType>)
|
class BuiltinFunctionParam(val name: String, val possibleDatatypes: Set<DataType>)
|
||||||
|
|
||||||
|
|
||||||
|
typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program) -> NumericLiteralValue
|
||||||
|
|
||||||
|
|
||||||
class FunctionSignature(val pure: Boolean, // does it have side effects?
|
class FunctionSignature(val pure: Boolean, // does it have side effects?
|
||||||
val parameters: List<BuiltinFunctionParam>,
|
val parameters: List<BuiltinFunctionParam>,
|
||||||
val returntype: DataType?,
|
val returntype: DataType?,
|
||||||
val constExpressionFunc: ((args: List<IExpression>, position: Position, program: Program) -> LiteralValue)? = null)
|
val constExpressionFunc: ConstExpressionCaller? = null)
|
||||||
|
|
||||||
|
|
||||||
val BuiltinFunctions = mapOf(
|
val BuiltinFunctions = mapOf(
|
||||||
@ -26,13 +27,16 @@ val BuiltinFunctions = mapOf(
|
|||||||
"ror2" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
"ror2" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||||
"lsl" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null),
|
"lsl" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null),
|
||||||
"lsr" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null),
|
"lsr" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null),
|
||||||
|
"sort" to FunctionSignature(false, listOf(BuiltinFunctionParam("array", ArrayDatatypes)), null),
|
||||||
|
"reverse" to FunctionSignature(false, listOf(BuiltinFunctionParam("array", ArrayDatatypes)), null),
|
||||||
// these few have a return value depending on the argument(s):
|
// these few have a return value depending on the argument(s):
|
||||||
"max" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArgOutputNumber(a, p, prg) { it.max()!! }}, // type depends on args
|
"max" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args
|
||||||
"min" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArgOutputNumber(a, p, prg) { it.min()!! }}, // type depends on args
|
"min" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args
|
||||||
"sum" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArgOutputNumber(a, p, prg) { it.sum() }}, // type depends on args
|
"sum" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
|
||||||
"abs" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
|
"abs" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
|
||||||
"len" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
|
"len" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
|
||||||
// normal functions follow:
|
// normal functions follow:
|
||||||
|
"sgn" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
|
||||||
"sin" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) },
|
"sin" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) },
|
||||||
"sin8" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
|
"sin8" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
|
||||||
"sin8u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
|
"sin8u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
|
||||||
@ -51,12 +55,11 @@ val BuiltinFunctions = mapOf(
|
|||||||
"sqrt" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
|
"sqrt" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
|
||||||
"rad" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) },
|
"rad" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) },
|
||||||
"deg" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
|
"deg" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
|
||||||
"avg" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.FLOAT, ::builtinAvg),
|
|
||||||
"round" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
|
"round" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
|
||||||
"floor" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
|
"floor" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
|
||||||
"ceil" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
|
"ceil" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
|
||||||
"any" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArgOutputBoolean(a, p, prg) { it.any { v -> v != 0.0} }},
|
"any" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) },
|
||||||
"all" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArgOutputBoolean(a, p, prg) { it.all { v -> v != 0.0} }},
|
"all" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
|
||||||
"lsb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 }},
|
"lsb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 }},
|
||||||
"msb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255}},
|
"msb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255}},
|
||||||
"mkword" to FunctionSignature(true, listOf(
|
"mkword" to FunctionSignature(true, listOf(
|
||||||
@ -74,63 +77,53 @@ val BuiltinFunctions = mapOf(
|
|||||||
"read_flags" to FunctionSignature(false, emptyList(), DataType.UBYTE),
|
"read_flags" to FunctionSignature(false, emptyList(), DataType.UBYTE),
|
||||||
"swap" to FunctionSignature(false, listOf(BuiltinFunctionParam("first", NumericDatatypes), BuiltinFunctionParam("second", NumericDatatypes)), null),
|
"swap" to FunctionSignature(false, listOf(BuiltinFunctionParam("first", NumericDatatypes), BuiltinFunctionParam("second", NumericDatatypes)), null),
|
||||||
"memcopy" to FunctionSignature(false, listOf(
|
"memcopy" to FunctionSignature(false, listOf(
|
||||||
BuiltinFunctionParam("from", IterableDatatypes + setOf(DataType.UWORD)),
|
BuiltinFunctionParam("from", IterableDatatypes + DataType.UWORD),
|
||||||
BuiltinFunctionParam("to", IterableDatatypes + setOf(DataType.UWORD)),
|
BuiltinFunctionParam("to", IterableDatatypes + DataType.UWORD),
|
||||||
BuiltinFunctionParam("numbytes", setOf(DataType.UBYTE))), null),
|
BuiltinFunctionParam("numbytes", setOf(DataType.UBYTE))), null),
|
||||||
"memset" to FunctionSignature(false, listOf(
|
"memset" to FunctionSignature(false, listOf(
|
||||||
BuiltinFunctionParam("address", IterableDatatypes + setOf(DataType.UWORD)),
|
BuiltinFunctionParam("address", IterableDatatypes + DataType.UWORD),
|
||||||
BuiltinFunctionParam("numbytes", setOf(DataType.UWORD)),
|
BuiltinFunctionParam("numbytes", setOf(DataType.UWORD)),
|
||||||
BuiltinFunctionParam("bytevalue", ByteDatatypes)), null),
|
BuiltinFunctionParam("bytevalue", ByteDatatypes)), null),
|
||||||
"memsetw" to FunctionSignature(false, listOf(
|
"memsetw" to FunctionSignature(false, listOf(
|
||||||
BuiltinFunctionParam("address", IterableDatatypes + setOf(DataType.UWORD)),
|
BuiltinFunctionParam("address", IterableDatatypes + DataType.UWORD),
|
||||||
BuiltinFunctionParam("numwords", setOf(DataType.UWORD)),
|
BuiltinFunctionParam("numwords", setOf(DataType.UWORD)),
|
||||||
BuiltinFunctionParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null),
|
BuiltinFunctionParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null),
|
||||||
"strlen" to FunctionSignature(true, listOf(BuiltinFunctionParam("string", StringDatatypes)), DataType.UBYTE, ::builtinStrlen),
|
"strlen" to FunctionSignature(true, listOf(BuiltinFunctionParam("string", StringDatatypes)), DataType.UBYTE, ::builtinStrlen)
|
||||||
"vm_write_memchr" to FunctionSignature(false, listOf(BuiltinFunctionParam("address", setOf(DataType.UWORD))), null),
|
|
||||||
"vm_write_memstr" to FunctionSignature(false, listOf(BuiltinFunctionParam("address", setOf(DataType.UWORD))), null),
|
|
||||||
"vm_write_num" to FunctionSignature(false, listOf(BuiltinFunctionParam("number", NumericDatatypes)), null),
|
|
||||||
"vm_write_char" to FunctionSignature(false, listOf(BuiltinFunctionParam("char", setOf(DataType.UBYTE))), null),
|
|
||||||
"vm_write_str" to FunctionSignature(false, listOf(BuiltinFunctionParam("string", StringDatatypes)), null),
|
|
||||||
"vm_input_str" to FunctionSignature(false, listOf(BuiltinFunctionParam("intovar", StringDatatypes)), null),
|
|
||||||
"vm_gfx_clearscr" to FunctionSignature(false, listOf(BuiltinFunctionParam("color", setOf(DataType.UBYTE))), null),
|
|
||||||
"vm_gfx_pixel" to FunctionSignature(false, listOf(
|
|
||||||
BuiltinFunctionParam("x", IntegerDatatypes),
|
|
||||||
BuiltinFunctionParam("y", IntegerDatatypes),
|
|
||||||
BuiltinFunctionParam("color", IntegerDatatypes)), null),
|
|
||||||
"vm_gfx_line" to FunctionSignature(false, listOf(
|
|
||||||
BuiltinFunctionParam("x1", IntegerDatatypes),
|
|
||||||
BuiltinFunctionParam("y1", IntegerDatatypes),
|
|
||||||
BuiltinFunctionParam("x2", IntegerDatatypes),
|
|
||||||
BuiltinFunctionParam("y2", IntegerDatatypes),
|
|
||||||
BuiltinFunctionParam("color", IntegerDatatypes)), null),
|
|
||||||
"vm_gfx_text" to FunctionSignature(false, listOf(
|
|
||||||
BuiltinFunctionParam("x", IntegerDatatypes),
|
|
||||||
BuiltinFunctionParam("y", IntegerDatatypes),
|
|
||||||
BuiltinFunctionParam("color", IntegerDatatypes),
|
|
||||||
BuiltinFunctionParam("text", StringDatatypes)),
|
|
||||||
null)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun builtinMax(array: List<Number>): Number = array.maxBy { it.toDouble() }!!
|
||||||
|
|
||||||
fun builtinFunctionReturnType(function: String, args: List<IExpression>, program: Program): DataType? {
|
fun builtinMin(array: List<Number>): Number = array.minBy { it.toDouble() }!!
|
||||||
|
|
||||||
fun datatypeFromIterableArg(arglist: IExpression): DataType {
|
fun builtinSum(array: List<Number>): Number = array.sumByDouble { it.toDouble() }
|
||||||
if(arglist is LiteralValue) {
|
|
||||||
if(arglist.type== DataType.ARRAY_UB || arglist.type== DataType.ARRAY_UW || arglist.type== DataType.ARRAY_F) {
|
fun builtinAny(array: List<Number>): Number = if(array.any { it.toDouble()!=0.0 }) 1 else 0
|
||||||
val dt = arglist.arrayvalue!!.map {it.inferType(program)}
|
|
||||||
if(dt.any { it!= DataType.UBYTE && it!= DataType.UWORD && it!= DataType.FLOAT}) {
|
fun builtinAll(array: List<Number>): Number = if(array.all { it.toDouble()!=0.0 }) 1 else 0
|
||||||
throw FatalAstException("fuction $function only accepts arraysize of numeric values")
|
|
||||||
}
|
|
||||||
if(dt.any { it== DataType.FLOAT }) return DataType.FLOAT
|
fun builtinFunctionReturnType(function: String, args: List<Expression>, program: Program): InferredTypes.InferredType {
|
||||||
if(dt.any { it== DataType.UWORD }) return DataType.UWORD
|
|
||||||
return DataType.UBYTE
|
fun datatypeFromIterableArg(arglist: Expression): DataType {
|
||||||
|
if(arglist is ArrayLiteralValue) {
|
||||||
|
val dt = arglist.value.map {it.inferType(program).typeOrElse(DataType.STRUCT)}.toSet()
|
||||||
|
if(dt.any { it !in NumericDatatypes }) {
|
||||||
|
throw FatalAstException("fuction $function only accepts array of numeric values")
|
||||||
}
|
}
|
||||||
|
if(DataType.FLOAT in dt) return DataType.FLOAT
|
||||||
|
if(DataType.UWORD in dt) return DataType.UWORD
|
||||||
|
if(DataType.WORD in dt) return DataType.WORD
|
||||||
|
if(DataType.BYTE in dt) return DataType.BYTE
|
||||||
|
return DataType.UBYTE
|
||||||
}
|
}
|
||||||
if(arglist is IdentifierReference) {
|
if(arglist is IdentifierReference) {
|
||||||
return when(val dt = arglist.inferType(program)) {
|
val idt = arglist.inferType(program)
|
||||||
in NumericDatatypes -> dt!!
|
if(!idt.isKnown)
|
||||||
in StringDatatypes -> dt!!
|
throw FatalAstException("couldn't determine type of iterable $arglist")
|
||||||
in ArrayDatatypes -> ArrayElementTypes.getValue(dt!!)
|
return when(val dt = idt.typeOrElse(DataType.STRUCT)) {
|
||||||
|
in NumericDatatypes -> dt
|
||||||
|
in StringDatatypes -> dt
|
||||||
|
in ArrayDatatypes -> ArrayElementTypes.getValue(dt)
|
||||||
else -> throw FatalAstException("function '$function' requires one argument which is an iterable")
|
else -> throw FatalAstException("function '$function' requires one argument which is an iterable")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -139,44 +132,43 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, program
|
|||||||
|
|
||||||
val func = BuiltinFunctions.getValue(function)
|
val func = BuiltinFunctions.getValue(function)
|
||||||
if(func.returntype!=null)
|
if(func.returntype!=null)
|
||||||
return func.returntype
|
return InferredTypes.knownFor(func.returntype)
|
||||||
// function has return values, but the return type depends on the arguments
|
// function has return values, but the return type depends on the arguments
|
||||||
|
|
||||||
return when (function) {
|
return when (function) {
|
||||||
"abs" -> {
|
"abs" -> {
|
||||||
when(val dt = args.single().inferType(program)) {
|
val dt = args.single().inferType(program)
|
||||||
in ByteDatatypes -> DataType.UBYTE
|
if(dt.typeOrElse(DataType.STRUCT) in NumericDatatypes)
|
||||||
in WordDatatypes -> DataType.UWORD
|
return dt
|
||||||
DataType.FLOAT -> DataType.FLOAT
|
else
|
||||||
else -> throw FatalAstException("weird datatype passed to abs $dt")
|
throw FatalAstException("weird datatype passed to abs $dt")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
"max", "min" -> {
|
"max", "min" -> {
|
||||||
when(val dt = datatypeFromIterableArg(args.single())) {
|
when(val dt = datatypeFromIterableArg(args.single())) {
|
||||||
in NumericDatatypes -> dt
|
in NumericDatatypes -> InferredTypes.knownFor(dt)
|
||||||
in StringDatatypes -> DataType.UBYTE
|
in StringDatatypes -> InferredTypes.knownFor(DataType.UBYTE)
|
||||||
in ArrayDatatypes -> ArrayElementTypes.getValue(dt)
|
in ArrayDatatypes -> InferredTypes.knownFor(ArrayElementTypes.getValue(dt))
|
||||||
else -> null
|
else -> InferredTypes.unknown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"sum" -> {
|
"sum" -> {
|
||||||
when(datatypeFromIterableArg(args.single())) {
|
when(datatypeFromIterableArg(args.single())) {
|
||||||
DataType.UBYTE, DataType.UWORD -> DataType.UWORD
|
DataType.UBYTE, DataType.UWORD -> InferredTypes.knownFor(DataType.UWORD)
|
||||||
DataType.BYTE, DataType.WORD -> DataType.WORD
|
DataType.BYTE, DataType.WORD -> InferredTypes.knownFor(DataType.WORD)
|
||||||
DataType.FLOAT -> DataType.FLOAT
|
DataType.FLOAT -> InferredTypes.knownFor(DataType.FLOAT)
|
||||||
DataType.ARRAY_UB, DataType.ARRAY_UW -> DataType.UWORD
|
DataType.ARRAY_UB, DataType.ARRAY_UW -> InferredTypes.knownFor(DataType.UWORD)
|
||||||
DataType.ARRAY_B, DataType.ARRAY_W -> DataType.WORD
|
DataType.ARRAY_B, DataType.ARRAY_W -> InferredTypes.knownFor(DataType.WORD)
|
||||||
DataType.ARRAY_F -> DataType.FLOAT
|
DataType.ARRAY_F -> InferredTypes.knownFor(DataType.FLOAT)
|
||||||
in StringDatatypes -> DataType.UWORD
|
in StringDatatypes -> InferredTypes.knownFor(DataType.UWORD)
|
||||||
else -> null
|
else -> InferredTypes.unknown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"len" -> {
|
"len" -> {
|
||||||
// a length can be >255 so in that case, the result is an UWORD instead of an UBYTE
|
// a length can be >255 so in that case, the result is an UWORD instead of an UBYTE
|
||||||
// but to avoid a lot of code duplication we simply assume UWORD in all cases for now
|
// but to avoid a lot of code duplication we simply assume UWORD in all cases for now
|
||||||
return DataType.UWORD
|
return InferredTypes.knownFor(DataType.UWORD)
|
||||||
}
|
}
|
||||||
else -> return null
|
else -> return InferredTypes.unknown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,181 +176,104 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, program
|
|||||||
class NotConstArgumentException: AstException("not a const argument to a built-in function")
|
class NotConstArgumentException: AstException("not a const argument to a built-in function")
|
||||||
|
|
||||||
|
|
||||||
private fun oneDoubleArg(args: List<IExpression>, position: Position, program: Program, function: (arg: Double)->Number): LiteralValue {
|
private fun oneDoubleArg(args: List<Expression>, position: Position, program: Program, function: (arg: Double)->Number): NumericLiteralValue {
|
||||||
if(args.size!=1)
|
if(args.size!=1)
|
||||||
throw SyntaxError("built-in function requires one floating point argument", position)
|
throw SyntaxError("built-in function requires one floating point argument", position)
|
||||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
val float = getFloatArg(constval, args[0].position)
|
val float = constval.number.toDouble()
|
||||||
return numericLiteral(function(float), args[0].position)
|
return numericLiteral(function(float), args[0].position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getFloatArg(v: LiteralValue, position: Position): Double {
|
private fun oneDoubleArgOutputWord(args: List<Expression>, position: Position, program: Program, function: (arg: Double)->Number): NumericLiteralValue {
|
||||||
val nv = v.asNumericValue ?: throw SyntaxError("numerical argument required", position)
|
|
||||||
return nv.toDouble()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun oneDoubleArgOutputWord(args: List<IExpression>, position: Position, program: Program, function: (arg: Double)->Number): LiteralValue {
|
|
||||||
if(args.size!=1)
|
if(args.size!=1)
|
||||||
throw SyntaxError("built-in function requires one floating point argument", position)
|
throw SyntaxError("built-in function requires one floating point argument", position)
|
||||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
val float = getFloatArg(constval, args[0].position)
|
val float = constval.number.toDouble()
|
||||||
return LiteralValue(DataType.WORD, wordvalue = function(float).toInt(), position = args[0].position)
|
return NumericLiteralValue(DataType.WORD, function(float).toInt(), args[0].position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun oneIntArgOutputInt(args: List<IExpression>, position: Position, program: Program, function: (arg: Int)->Number): LiteralValue {
|
private fun oneIntArgOutputInt(args: List<Expression>, position: Position, program: Program, function: (arg: Int)->Number): NumericLiteralValue {
|
||||||
if(args.size!=1)
|
if(args.size!=1)
|
||||||
throw SyntaxError("built-in function requires one integer argument", position)
|
throw SyntaxError("built-in function requires one integer argument", position)
|
||||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
if(constval.type!= DataType.UBYTE && constval.type!= DataType.UWORD)
|
if(constval.type != DataType.UBYTE && constval.type!= DataType.UWORD)
|
||||||
throw SyntaxError("built-in function requires one integer argument", position)
|
throw SyntaxError("built-in function requires one integer argument", position)
|
||||||
|
|
||||||
val integer = constval.asNumericValue?.toInt()!!
|
val integer = constval.number.toInt()
|
||||||
return numericLiteral(function(integer).toInt(), args[0].position)
|
return numericLiteral(function(integer).toInt(), args[0].position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun collectionArgOutputNumber(args: List<IExpression>, position: Position,
|
private fun collectionArg(args: List<Expression>, position: Position, program: Program, function: (arg: List<Number>)->Number): NumericLiteralValue {
|
||||||
program: Program,
|
|
||||||
function: (arg: Collection<Double>)->Number): LiteralValue {
|
|
||||||
if(args.size!=1)
|
if(args.size!=1)
|
||||||
throw SyntaxError("builtin function requires one non-scalar argument", position)
|
throw SyntaxError("builtin function requires one non-scalar argument", position)
|
||||||
val iterable = args[0].constValue(program) ?: throw NotConstArgumentException()
|
|
||||||
|
|
||||||
val result = if(iterable.arrayvalue != null) {
|
val array= args[0] as? ArrayLiteralValue ?: throw NotConstArgumentException()
|
||||||
val constants = iterable.arrayvalue.map { it.constValue(program)?.asNumericValue }
|
val constElements = array.value.map{it.constValue(program)?.number}
|
||||||
if(null in constants)
|
if(constElements.contains(null))
|
||||||
throw NotConstArgumentException()
|
throw NotConstArgumentException()
|
||||||
function(constants.map { it!!.toDouble() }).toDouble()
|
|
||||||
} else {
|
return NumericLiteralValue.optimalNumeric(function(constElements.mapNotNull { it }), args[0].position)
|
||||||
when(iterable.type) {
|
|
||||||
DataType.UBYTE, DataType.UWORD, DataType.FLOAT -> throw SyntaxError("function expects an iterable type", position)
|
|
||||||
else -> {
|
|
||||||
val heapId = iterable.heapId ?: throw FatalAstException("iterable value should be on the heap")
|
|
||||||
val array = program.heap.get(heapId).array ?: throw SyntaxError("function expects an iterable type", position)
|
|
||||||
function(array.map {
|
|
||||||
if(it.integer!=null)
|
|
||||||
it.integer.toDouble()
|
|
||||||
else
|
|
||||||
throw FatalAstException("cannot perform function over array that contains other values besides constant integers")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return numericLiteral(result, args[0].position)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun collectionArgOutputBoolean(args: List<IExpression>, position: Position,
|
private fun builtinAbs(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||||
program: Program,
|
|
||||||
function: (arg: Collection<Double>)->Boolean): LiteralValue {
|
|
||||||
if(args.size!=1)
|
|
||||||
throw SyntaxError("builtin function requires one non-scalar argument", position)
|
|
||||||
val iterable = args[0].constValue(program) ?: throw NotConstArgumentException()
|
|
||||||
|
|
||||||
val result = if(iterable.arrayvalue != null) {
|
|
||||||
val constants = iterable.arrayvalue.map { it.constValue(program)?.asNumericValue }
|
|
||||||
if(null in constants)
|
|
||||||
throw NotConstArgumentException()
|
|
||||||
function(constants.map { it!!.toDouble() })
|
|
||||||
} else {
|
|
||||||
val array = program.heap.get(iterable.heapId!!).array ?: throw SyntaxError("function requires array argument", position)
|
|
||||||
function(array.map {
|
|
||||||
if(it.integer!=null)
|
|
||||||
it.integer.toDouble()
|
|
||||||
else
|
|
||||||
throw FatalAstException("cannot perform function over array that contains other values besides constant integers")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return LiteralValue.fromBoolean(result, position)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun builtinAbs(args: List<IExpression>, position: Position, program: Program): LiteralValue {
|
|
||||||
// 1 arg, type = float or int, result type= isSameAs as argument type
|
// 1 arg, type = float or int, result type= isSameAs as argument type
|
||||||
if(args.size!=1)
|
if(args.size!=1)
|
||||||
throw SyntaxError("abs requires one numeric argument", position)
|
throw SyntaxError("abs requires one numeric argument", position)
|
||||||
|
|
||||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
return when (val number = constval.asNumericValue) {
|
return when (constval.type) {
|
||||||
is Int, is Byte, is Short -> numericLiteral(abs(number.toInt()), args[0].position)
|
in IntegerDatatypes -> numericLiteral(abs(constval.number.toInt()), args[0].position)
|
||||||
is Double -> numericLiteral(abs(number.toDouble()), args[0].position)
|
DataType.FLOAT -> numericLiteral(abs(constval.number.toDouble()), args[0].position)
|
||||||
else -> throw SyntaxError("abs requires one numeric argument", position)
|
else -> throw SyntaxError("abs requires one numeric argument", position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun builtinAvg(args: List<IExpression>, position: Position, program: Program): LiteralValue {
|
private fun builtinStrlen(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||||
if(args.size!=1)
|
|
||||||
throw SyntaxError("avg requires array argument", position)
|
|
||||||
val iterable = args[0].constValue(program) ?: throw NotConstArgumentException()
|
|
||||||
val result = if(iterable.arrayvalue!=null) {
|
|
||||||
val constants = iterable.arrayvalue.map { it.constValue(program)?.asNumericValue }
|
|
||||||
if (null in constants)
|
|
||||||
throw NotConstArgumentException()
|
|
||||||
(constants.map { it!!.toDouble() }).average()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
val heapId = iterable.heapId!!
|
|
||||||
val integerarray = program.heap.get(heapId).array
|
|
||||||
if(integerarray!=null) {
|
|
||||||
if (integerarray.all { it.integer != null }) {
|
|
||||||
integerarray.map { it.integer!! }.average()
|
|
||||||
} else {
|
|
||||||
throw ExpressionError("cannot avg() over array that does not only contain constant numerical values", position)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val doublearray = program.heap.get(heapId).doubleArray
|
|
||||||
doublearray?.average() ?: throw SyntaxError("avg requires array argument", position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return numericLiteral(result, args[0].position)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun builtinStrlen(args: List<IExpression>, position: Position, program: Program): LiteralValue {
|
|
||||||
if (args.size != 1)
|
if (args.size != 1)
|
||||||
throw SyntaxError("strlen requires one argument", position)
|
throw SyntaxError("strlen requires one argument", position)
|
||||||
val argument = args[0].constValue(program) ?: throw NotConstArgumentException()
|
val argument = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
if(argument.type !in StringDatatypes)
|
if(argument.type !in StringDatatypes)
|
||||||
throw SyntaxError("strlen must have string argument", position)
|
throw SyntaxError("strlen must have string argument", position)
|
||||||
val string = argument.strvalue!!
|
|
||||||
val zeroIdx = string.indexOf('\u0000')
|
throw NotConstArgumentException() // this function is not considering the string argument a constant
|
||||||
return if(zeroIdx>=0)
|
|
||||||
LiteralValue.optimalInteger(zeroIdx, position=position)
|
|
||||||
else
|
|
||||||
LiteralValue.optimalInteger(string.length, position=position)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun builtinLen(args: List<IExpression>, position: Position, program: Program): LiteralValue {
|
private fun builtinLen(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||||
// note: in some cases the length is > 255 and then we have to return a UWORD type instead of a UBYTE.
|
// note: in some cases the length is > 255 and then we have to return a UWORD type instead of a UBYTE.
|
||||||
if(args.size!=1)
|
if(args.size!=1)
|
||||||
throw SyntaxError("len requires one argument", position)
|
throw SyntaxError("len requires one argument", position)
|
||||||
var argument = args[0].constValue(program)
|
val constArg = args[0].constValue(program)
|
||||||
if(argument==null) {
|
if(constArg!=null)
|
||||||
val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program.namespace)
|
throw SyntaxError("len of weird argument ${args[0]}", position)
|
||||||
val arraySize = directMemVar?.arraysize?.size()
|
|
||||||
if(arraySize != null)
|
val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program.namespace)
|
||||||
return LiteralValue.optimalInteger(arraySize, position)
|
var arraySize = directMemVar?.arraysize?.size()
|
||||||
if(args[0] !is IdentifierReference)
|
if(arraySize != null)
|
||||||
throw SyntaxError("len argument should be an identifier, but is ${args[0]}", position)
|
return NumericLiteralValue.optimalInteger(arraySize, position)
|
||||||
val target = (args[0] as IdentifierReference).targetStatement(program.namespace)
|
if(args[0] is ArrayLiteralValue)
|
||||||
val argValue = (target as? VarDecl)?.value
|
return NumericLiteralValue.optimalInteger((args[0] as ArrayLiteralValue).value.size, position)
|
||||||
argument = argValue?.constValue(program)
|
if(args[0] !is IdentifierReference)
|
||||||
?: throw NotConstArgumentException()
|
throw SyntaxError("len argument should be an identifier, but is ${args[0]}", position)
|
||||||
}
|
val target = (args[0] as IdentifierReference).targetVarDecl(program.namespace)!!
|
||||||
return when(argument.type) {
|
|
||||||
|
return when(target.datatype) {
|
||||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||||
val arraySize = argument.arrayvalue?.size ?: program.heap.get(argument.heapId!!).arraysize
|
arraySize = target.arraysize!!.size()!!
|
||||||
if(arraySize>256)
|
if(arraySize>256)
|
||||||
throw CompilerException("array length exceeds byte limit ${argument.position}")
|
throw CompilerException("array length exceeds byte limit ${target.position}")
|
||||||
LiteralValue.optimalInteger(arraySize, args[0].position)
|
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
|
||||||
}
|
}
|
||||||
DataType.ARRAY_F -> {
|
DataType.ARRAY_F -> {
|
||||||
val arraySize = argument.arrayvalue?.size ?: program.heap.get(argument.heapId!!).arraysize
|
arraySize = target.arraysize!!.size()!!
|
||||||
if(arraySize>256)
|
if(arraySize>256)
|
||||||
throw CompilerException("array length exceeds byte limit ${argument.position}")
|
throw CompilerException("array length exceeds byte limit ${target.position}")
|
||||||
LiteralValue.optimalInteger(arraySize, args[0].position)
|
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
|
||||||
}
|
}
|
||||||
in StringDatatypes -> {
|
in StringDatatypes -> {
|
||||||
val str = argument.strvalue!!
|
val refLv = target.value as StringLiteralValue
|
||||||
if(str.length>255)
|
if(refLv.value.length>255)
|
||||||
throw CompilerException("string length exceeds byte limit ${argument.position}")
|
throw CompilerException("string length exceeds byte limit ${refLv.position}")
|
||||||
LiteralValue.optimalInteger(str.length, args[0].position)
|
NumericLiteralValue.optimalInteger(refLv.value.length, args[0].position)
|
||||||
}
|
}
|
||||||
in NumericDatatypes -> throw SyntaxError("len of weird argument ${args[0]}", position)
|
in NumericDatatypes -> throw SyntaxError("len of weird argument ${args[0]}", position)
|
||||||
else -> throw CompilerException("weird datatype")
|
else -> throw CompilerException("weird datatype")
|
||||||
@ -366,80 +281,87 @@ private fun builtinLen(args: List<IExpression>, position: Position, program: Pro
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun builtinMkword(args: List<IExpression>, position: Position, program: Program): LiteralValue {
|
private fun builtinMkword(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||||
if (args.size != 2)
|
if (args.size != 2)
|
||||||
throw SyntaxError("mkword requires lsb and msb arguments", position)
|
throw SyntaxError("mkword requires lsb and msb arguments", position)
|
||||||
val constLsb = args[0].constValue(program) ?: throw NotConstArgumentException()
|
val constLsb = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
val constMsb = args[1].constValue(program) ?: throw NotConstArgumentException()
|
val constMsb = args[1].constValue(program) ?: throw NotConstArgumentException()
|
||||||
val result = (constMsb.asIntegerValue!! shl 8) or constLsb.asIntegerValue!!
|
val result = (constMsb.number.toInt() shl 8) or constLsb.number.toInt()
|
||||||
return LiteralValue(DataType.UWORD, wordvalue = result, position = position)
|
return NumericLiteralValue(DataType.UWORD, result, position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun builtinSin8(args: List<IExpression>, position: Position, program: Program): LiteralValue {
|
private fun builtinSin8(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||||
if (args.size != 1)
|
if (args.size != 1)
|
||||||
throw SyntaxError("sin8 requires one argument", position)
|
throw SyntaxError("sin8 requires one argument", position)
|
||||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
|
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||||
return LiteralValue(DataType.BYTE, bytevalue = (127.0 * sin(rad)).toShort(), position = position)
|
return NumericLiteralValue(DataType.BYTE, (127.0 * sin(rad)).toShort(), position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun builtinSin8u(args: List<IExpression>, position: Position, program: Program): LiteralValue {
|
private fun builtinSin8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||||
if (args.size != 1)
|
if (args.size != 1)
|
||||||
throw SyntaxError("sin8u requires one argument", position)
|
throw SyntaxError("sin8u requires one argument", position)
|
||||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
|
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||||
return LiteralValue(DataType.UBYTE, bytevalue = (128.0 + 127.5 * sin(rad)).toShort(), position = position)
|
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toShort(), position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun builtinCos8(args: List<IExpression>, position: Position, program: Program): LiteralValue {
|
private fun builtinCos8(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||||
if (args.size != 1)
|
if (args.size != 1)
|
||||||
throw SyntaxError("cos8 requires one argument", position)
|
throw SyntaxError("cos8 requires one argument", position)
|
||||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
|
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||||
return LiteralValue(DataType.BYTE, bytevalue = (127.0 * cos(rad)).toShort(), position = position)
|
return NumericLiteralValue(DataType.BYTE, (127.0 * cos(rad)).toShort(), position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun builtinCos8u(args: List<IExpression>, position: Position, program: Program): LiteralValue {
|
private fun builtinCos8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||||
if (args.size != 1)
|
if (args.size != 1)
|
||||||
throw SyntaxError("cos8u requires one argument", position)
|
throw SyntaxError("cos8u requires one argument", position)
|
||||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
|
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||||
return LiteralValue(DataType.UBYTE, bytevalue = (128.0 + 127.5 * cos(rad)).toShort(), position = position)
|
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toShort(), position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun builtinSin16(args: List<IExpression>, position: Position, program: Program): LiteralValue {
|
private fun builtinSin16(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||||
if (args.size != 1)
|
if (args.size != 1)
|
||||||
throw SyntaxError("sin16 requires one argument", position)
|
throw SyntaxError("sin16 requires one argument", position)
|
||||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
|
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||||
return LiteralValue(DataType.WORD, wordvalue = (32767.0 * sin(rad)).toInt(), position = position)
|
return NumericLiteralValue(DataType.WORD, (32767.0 * sin(rad)).toInt(), position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun builtinSin16u(args: List<IExpression>, position: Position, program: Program): LiteralValue {
|
private fun builtinSin16u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||||
if (args.size != 1)
|
if (args.size != 1)
|
||||||
throw SyntaxError("sin16u requires one argument", position)
|
throw SyntaxError("sin16u requires one argument", position)
|
||||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
|
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||||
return LiteralValue(DataType.UWORD, wordvalue = (32768.0 + 32767.5 * sin(rad)).toInt(), position = position)
|
return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * sin(rad)).toInt(), position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun builtinCos16(args: List<IExpression>, position: Position, program: Program): LiteralValue {
|
private fun builtinCos16(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||||
if (args.size != 1)
|
if (args.size != 1)
|
||||||
throw SyntaxError("cos16 requires one argument", position)
|
throw SyntaxError("cos16 requires one argument", position)
|
||||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
|
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||||
return LiteralValue(DataType.WORD, wordvalue = (32767.0 * cos(rad)).toInt(), position = position)
|
return NumericLiteralValue(DataType.WORD, (32767.0 * cos(rad)).toInt(), position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun builtinCos16u(args: List<IExpression>, position: Position, program: Program): LiteralValue {
|
private fun builtinCos16u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||||
if (args.size != 1)
|
if (args.size != 1)
|
||||||
throw SyntaxError("cos16u requires one argument", position)
|
throw SyntaxError("cos16u requires one argument", position)
|
||||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
|
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||||
return LiteralValue(DataType.UWORD, wordvalue = (32768.0 + 32767.5 * cos(rad)).toInt(), position = position)
|
return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * cos(rad)).toInt(), position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun numericLiteral(value: Number, position: Position): LiteralValue {
|
private fun builtinSgn(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||||
|
if (args.size != 1)
|
||||||
|
throw SyntaxError("sgn requires one argument", position)
|
||||||
|
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||||
|
return NumericLiteralValue(DataType.BYTE, constval.number.toDouble().sign.toShort(), position)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun numericLiteral(value: Number, position: Position): NumericLiteralValue {
|
||||||
val floatNum=value.toDouble()
|
val floatNum=value.toDouble()
|
||||||
val tweakedValue: Number =
|
val tweakedValue: Number =
|
||||||
if(floatNum== floor(floatNum) && (floatNum>=-32768 && floatNum<=65535))
|
if(floatNum== floor(floatNum) && (floatNum>=-32768 && floatNum<=65535))
|
||||||
@ -448,11 +370,11 @@ private fun numericLiteral(value: Number, position: Position): LiteralValue {
|
|||||||
floatNum
|
floatNum
|
||||||
|
|
||||||
return when(tweakedValue) {
|
return when(tweakedValue) {
|
||||||
is Int -> LiteralValue.optimalNumeric(value.toInt(), position)
|
is Int -> NumericLiteralValue.optimalNumeric(value.toInt(), position)
|
||||||
is Short -> LiteralValue.optimalNumeric(value.toInt(), position)
|
is Short -> NumericLiteralValue.optimalNumeric(value.toInt(), position)
|
||||||
is Byte -> LiteralValue(DataType.UBYTE, bytevalue = value.toShort(), position = position)
|
is Byte -> NumericLiteralValue(DataType.UBYTE, value.toShort(), position)
|
||||||
is Double -> LiteralValue(DataType.FLOAT, floatvalue = value.toDouble(), position = position)
|
is Double -> NumericLiteralValue(DataType.FLOAT, value.toDouble(), position)
|
||||||
is Float -> LiteralValue(DataType.FLOAT, floatvalue = value.toDouble(), position = position)
|
is Float -> NumericLiteralValue(DataType.FLOAT, value.toDouble(), position)
|
||||||
else -> throw FatalAstException("invalid number type ${value::class}")
|
else -> throw FatalAstException("invalid number type ${value::class}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
package prog8.optimizer
|
package prog8.optimizer
|
||||||
|
|
||||||
import prog8.ast.*
|
import prog8.ast.INameScope
|
||||||
|
import prog8.ast.Module
|
||||||
|
import prog8.ast.Node
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.DataType
|
||||||
import prog8.ast.base.ParentSentinel
|
import prog8.ast.base.ParentSentinel
|
||||||
import prog8.ast.base.VarDeclType
|
import prog8.ast.base.VarDeclType
|
||||||
import prog8.ast.base.initvarsSubName
|
import prog8.ast.base.initvarsSubName
|
||||||
@ -17,7 +21,8 @@ class CallGraph(private val program: Program): IAstVisitor {
|
|||||||
val modulesImportedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
|
val modulesImportedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
|
||||||
val subroutinesCalling = mutableMapOf<INameScope, List<Subroutine>>().withDefault { mutableListOf() }
|
val subroutinesCalling = mutableMapOf<INameScope, List<Subroutine>>().withDefault { mutableListOf() }
|
||||||
val subroutinesCalledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
|
val subroutinesCalledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
|
||||||
val usedSymbols = mutableSetOf<IStatement>()
|
// TODO add dataflow graph: what statements use what variables
|
||||||
|
val usedSymbols = mutableSetOf<Statement>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
visit(program)
|
visit(program)
|
||||||
@ -92,11 +97,11 @@ class CallGraph(private val program: Program): IAstVisitor {
|
|||||||
super.visit(identifier)
|
super.visit(identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addNodeAndParentScopes(stmt: IStatement) {
|
private fun addNodeAndParentScopes(stmt: Statement) {
|
||||||
usedSymbols.add(stmt)
|
usedSymbols.add(stmt)
|
||||||
var node: Node=stmt
|
var node: Node=stmt
|
||||||
do {
|
do {
|
||||||
if(node is INameScope && node is IStatement) {
|
if(node is INameScope && node is Statement) {
|
||||||
usedSymbols.add(node)
|
usedSymbols.add(node)
|
||||||
}
|
}
|
||||||
node=node.parent
|
node=node.parent
|
||||||
@ -106,7 +111,8 @@ class CallGraph(private val program: Program): IAstVisitor {
|
|||||||
override fun visit(subroutine: Subroutine) {
|
override fun visit(subroutine: Subroutine) {
|
||||||
val alwaysKeepSubroutines = setOf(
|
val alwaysKeepSubroutines = setOf(
|
||||||
Pair("main", "start"),
|
Pair("main", "start"),
|
||||||
Pair("irq", "irq")
|
Pair("irq", "irq"),
|
||||||
|
Pair("prog8_lib", "init_system")
|
||||||
)
|
)
|
||||||
|
|
||||||
if(Pair(subroutine.definingScope().name, subroutine.name) in alwaysKeepSubroutines
|
if(Pair(subroutine.definingScope().name, subroutine.name) in alwaysKeepSubroutines
|
||||||
@ -118,10 +124,14 @@ class CallGraph(private val program: Program): IAstVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(decl: VarDecl) {
|
override fun visit(decl: VarDecl) {
|
||||||
if(decl.autoGenerated || (decl.definingModule().isLibraryModule && decl.type!=VarDeclType.VAR)) {
|
if(decl.autogeneratedDontRemove || (decl.definingModule().isLibraryModule && decl.type!=VarDeclType.VAR)) {
|
||||||
// make sure autogenerated vardecls are in the used symbols
|
// make sure autogenerated vardecls are in the used symbols
|
||||||
addNodeAndParentScopes(decl)
|
addNodeAndParentScopes(decl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(decl.datatype==DataType.STRUCT)
|
||||||
|
addNodeAndParentScopes(decl)
|
||||||
|
|
||||||
super.visit(decl)
|
super.visit(decl)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,6 +168,11 @@ class CallGraph(private val program: Program): IAstVisitor {
|
|||||||
super.visit(jump)
|
super.visit(jump)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun visit(structDecl: StructDecl) {
|
||||||
|
usedSymbols.add(structDecl)
|
||||||
|
usedSymbols.addAll(structDecl.statements)
|
||||||
|
}
|
||||||
|
|
||||||
override fun visit(inlineAssembly: InlineAssembly) {
|
override fun visit(inlineAssembly: InlineAssembly) {
|
||||||
// parse inline asm for subroutine calls (jmp, jsr)
|
// parse inline asm for subroutine calls (jmp, jsr)
|
||||||
val scope = inlineAssembly.definingScope()
|
val scope = inlineAssembly.definingScope()
|
||||||
@ -165,7 +180,7 @@ class CallGraph(private val program: Program): IAstVisitor {
|
|||||||
super.visit(inlineAssembly)
|
super.visit(inlineAssembly)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun scanAssemblyCode(asm: String, context: IStatement, scope: INameScope) {
|
private fun scanAssemblyCode(asm: String, context: Statement, scope: INameScope) {
|
||||||
val asmJumpRx = Regex("""[\-+a-zA-Z0-9_ \t]+(jmp|jsr)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
|
val asmJumpRx = Regex("""[\-+a-zA-Z0-9_ \t]+(jmp|jsr)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
|
||||||
val asmRefRx = Regex("""[\-+a-zA-Z0-9_ \t]+(...)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
|
val asmRefRx = Regex("""[\-+a-zA-Z0-9_ \t]+(...)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
|
||||||
asm.lines().forEach { line ->
|
asm.lines().forEach { line ->
|
||||||
@ -191,10 +206,12 @@ class CallGraph(private val program: Program): IAstVisitor {
|
|||||||
if (matches2 != null) {
|
if (matches2 != null) {
|
||||||
val target= matches2.groups[2]?.value
|
val target= matches2.groups[2]?.value
|
||||||
if (target != null && (target[0].isLetter() || target[0] == '_')) {
|
if (target != null && (target[0].isLetter() || target[0] == '_')) {
|
||||||
val node = program.namespace.lookup(listOf(target.substringBefore('.')), context)
|
if(target.contains('.')) {
|
||||||
if (node is Subroutine) {
|
val node = program.namespace.lookup(listOf(target.substringBefore('.')), context)
|
||||||
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node)
|
if (node is Subroutine) {
|
||||||
subroutinesCalledBy[node] = subroutinesCalledBy.getValue(node).plus(context)
|
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node)
|
||||||
|
subroutinesCalledBy[node] = subroutinesCalledBy.getValue(node).plus(context)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,14 @@
|
|||||||
package prog8.optimizer
|
package prog8.optimizer
|
||||||
|
|
||||||
import prog8.ast.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.base.DataType
|
import prog8.ast.expressions.Expression
|
||||||
import prog8.ast.base.ExpressionError
|
import prog8.ast.expressions.NumericLiteralValue
|
||||||
import prog8.ast.base.FatalAstException
|
|
||||||
import prog8.ast.base.Position
|
|
||||||
import prog8.ast.expressions.LiteralValue
|
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
|
|
||||||
|
|
||||||
class ConstExprEvaluator {
|
class ConstExprEvaluator {
|
||||||
|
|
||||||
fun evaluate(left: LiteralValue, operator: String, right: LiteralValue): IExpression {
|
fun evaluate(left: NumericLiteralValue, operator: String, right: NumericLiteralValue): Expression {
|
||||||
return when(operator) {
|
return when(operator) {
|
||||||
"+" -> plus(left, right)
|
"+" -> plus(left, right)
|
||||||
"-" -> minus(left, right)
|
"-" -> minus(left, right)
|
||||||
@ -25,200 +22,188 @@ class ConstExprEvaluator {
|
|||||||
"and" -> logicaland(left, right)
|
"and" -> logicaland(left, right)
|
||||||
"or" -> logicalor(left, right)
|
"or" -> logicalor(left, right)
|
||||||
"xor" -> logicalxor(left, right)
|
"xor" -> logicalxor(left, right)
|
||||||
"<" -> LiteralValue.fromBoolean(left < right, left.position)
|
"<" -> NumericLiteralValue.fromBoolean(left < right, left.position)
|
||||||
">" -> LiteralValue.fromBoolean(left > right, left.position)
|
">" -> NumericLiteralValue.fromBoolean(left > right, left.position)
|
||||||
"<=" -> LiteralValue.fromBoolean(left <= right, left.position)
|
"<=" -> NumericLiteralValue.fromBoolean(left <= right, left.position)
|
||||||
">=" -> LiteralValue.fromBoolean(left >= right, left.position)
|
">=" -> NumericLiteralValue.fromBoolean(left >= right, left.position)
|
||||||
"==" -> LiteralValue.fromBoolean(left == right, left.position)
|
"==" -> NumericLiteralValue.fromBoolean(left == right, left.position)
|
||||||
"!=" -> LiteralValue.fromBoolean(left != right, left.position)
|
"!=" -> NumericLiteralValue.fromBoolean(left != right, left.position)
|
||||||
"<<" -> shiftedleft(left, right)
|
"<<" -> shiftedleft(left, right)
|
||||||
">>" -> shiftedright(left, right)
|
">>" -> shiftedright(left, right)
|
||||||
else -> throw FatalAstException("const evaluation for invalid operator $operator")
|
else -> throw FatalAstException("const evaluation for invalid operator $operator")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun shiftedright(left: LiteralValue, amount: LiteralValue): IExpression {
|
private fun shiftedright(left: NumericLiteralValue, amount: NumericLiteralValue): Expression {
|
||||||
if(left.asIntegerValue==null || amount.asIntegerValue==null)
|
if(left.type !in IntegerDatatypes || amount.type !in IntegerDatatypes)
|
||||||
throw ExpressionError("cannot compute $left >> $amount", left.position)
|
throw ExpressionError("cannot compute $left >> $amount", left.position)
|
||||||
val result =
|
val result =
|
||||||
if(left.type== DataType.UBYTE || left.type== DataType.UWORD)
|
if(left.type== DataType.UBYTE || left.type== DataType.UWORD)
|
||||||
left.asIntegerValue.ushr(amount.asIntegerValue)
|
left.number.toInt().ushr(amount.number.toInt())
|
||||||
else
|
else
|
||||||
left.asIntegerValue.shr(amount.asIntegerValue)
|
left.number.toInt().shr(amount.number.toInt())
|
||||||
return LiteralValue.fromNumber(result, left.type, left.position)
|
return NumericLiteralValue(left.type, result, left.position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun shiftedleft(left: LiteralValue, amount: LiteralValue): IExpression {
|
private fun shiftedleft(left: NumericLiteralValue, amount: NumericLiteralValue): Expression {
|
||||||
if(left.asIntegerValue==null || amount.asIntegerValue==null)
|
if(left.type !in IntegerDatatypes || amount.type !in IntegerDatatypes)
|
||||||
throw ExpressionError("cannot compute $left << $amount", left.position)
|
throw ExpressionError("cannot compute $left << $amount", left.position)
|
||||||
val result = left.asIntegerValue.shl(amount.asIntegerValue)
|
val result = left.number.toInt().shl(amount.number.toInt())
|
||||||
return LiteralValue.fromNumber(result, left.type, left.position)
|
return NumericLiteralValue(left.type, result, left.position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun logicalxor(left: LiteralValue, right: LiteralValue): LiteralValue {
|
private fun logicalxor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||||
val error = "cannot compute $left locical-bitxor $right"
|
val error = "cannot compute $left locical-bitxor $right"
|
||||||
return when {
|
return when {
|
||||||
left.asIntegerValue!=null -> when {
|
left.type in IntegerDatatypes -> when {
|
||||||
right.asIntegerValue!=null -> LiteralValue.fromBoolean((left.asIntegerValue != 0) xor (right.asIntegerValue != 0), left.position)
|
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number.toInt() != 0), left.position)
|
||||||
right.floatvalue!=null -> LiteralValue.fromBoolean((left.asIntegerValue != 0) xor (right.floatvalue != 0.0), left.position)
|
right.type == DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number.toDouble() != 0.0), left.position)
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
||||||
left.floatvalue!=null -> when {
|
left.type == DataType.FLOAT -> when {
|
||||||
right.asIntegerValue!=null -> LiteralValue.fromBoolean((left.floatvalue != 0.0) xor (right.asIntegerValue != 0), left.position)
|
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toDouble() != 0.0) xor (right.number.toInt() != 0), left.position)
|
||||||
right.floatvalue!=null -> LiteralValue.fromBoolean((left.floatvalue != 0.0) xor (right.floatvalue != 0.0), left.position)
|
right.type == DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number.toDouble() != 0.0) xor (right.number.toDouble() != 0.0), left.position)
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun logicalor(left: LiteralValue, right: LiteralValue): LiteralValue {
|
private fun logicalor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||||
val error = "cannot compute $left locical-or $right"
|
val error = "cannot compute $left locical-or $right"
|
||||||
return when {
|
return when {
|
||||||
left.asIntegerValue!=null -> when {
|
left.type in IntegerDatatypes -> when {
|
||||||
right.asIntegerValue!=null -> LiteralValue.fromBoolean(left.asIntegerValue != 0 || right.asIntegerValue != 0, left.position)
|
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number.toInt() != 0, left.position)
|
||||||
right.floatvalue!=null -> LiteralValue.fromBoolean(left.asIntegerValue != 0 || right.floatvalue != 0.0, left.position)
|
right.type == DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number.toDouble() != 0.0, left.position)
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
||||||
left.floatvalue!=null -> when {
|
left.type == DataType.FLOAT -> when {
|
||||||
right.asIntegerValue!=null -> LiteralValue.fromBoolean(left.floatvalue != 0.0 || right.asIntegerValue != 0, left.position)
|
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 || right.number.toInt() != 0, left.position)
|
||||||
right.floatvalue!=null -> LiteralValue.fromBoolean(left.floatvalue != 0.0 || right.floatvalue != 0.0, left.position)
|
right.type == DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 || right.number.toDouble() != 0.0, left.position)
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun logicaland(left: LiteralValue, right: LiteralValue): LiteralValue {
|
private fun logicaland(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||||
val error = "cannot compute $left locical-and $right"
|
val error = "cannot compute $left locical-and $right"
|
||||||
return when {
|
return when {
|
||||||
left.asIntegerValue!=null -> when {
|
left.type in IntegerDatatypes -> when {
|
||||||
right.asIntegerValue!=null -> LiteralValue.fromBoolean(left.asIntegerValue != 0 && right.asIntegerValue != 0, left.position)
|
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number.toInt() != 0, left.position)
|
||||||
right.floatvalue!=null -> LiteralValue.fromBoolean(left.asIntegerValue != 0 && right.floatvalue != 0.0, left.position)
|
right.type == DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number.toDouble() != 0.0, left.position)
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
||||||
left.floatvalue!=null -> when {
|
left.type == DataType.FLOAT -> when {
|
||||||
right.asIntegerValue!=null -> LiteralValue.fromBoolean(left.floatvalue != 0.0 && right.asIntegerValue != 0, left.position)
|
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 && right.number.toInt() != 0, left.position)
|
||||||
right.floatvalue!=null -> LiteralValue.fromBoolean(left.floatvalue != 0.0 && right.floatvalue != 0.0, left.position)
|
right.type == DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 && right.number.toDouble() != 0.0, left.position)
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun bitwisexor(left: LiteralValue, right: LiteralValue): LiteralValue {
|
private fun bitwisexor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||||
if(left.type== DataType.UBYTE) {
|
if(left.type== DataType.UBYTE) {
|
||||||
if(right.asIntegerValue!=null) {
|
if(right.type in IntegerDatatypes) {
|
||||||
return LiteralValue(DataType.UBYTE, bytevalue = (left.bytevalue!!.toInt() xor (right.asIntegerValue and 255)).toShort(), position = left.position)
|
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() xor (right.number.toInt() and 255)).toShort(), left.position)
|
||||||
}
|
}
|
||||||
} else if(left.type== DataType.UWORD) {
|
} else if(left.type== DataType.UWORD) {
|
||||||
if(right.asIntegerValue!=null) {
|
if(right.type in IntegerDatatypes) {
|
||||||
return LiteralValue(DataType.UWORD, wordvalue = left.wordvalue!! xor right.asIntegerValue, position = left.position)
|
return NumericLiteralValue(DataType.UWORD, left.number.toInt() xor right.number.toInt(), left.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw ExpressionError("cannot calculate $left ^ $right", left.position)
|
throw ExpressionError("cannot calculate $left ^ $right", left.position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun bitwiseor(left: LiteralValue, right: LiteralValue): LiteralValue {
|
private fun bitwiseor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||||
if(left.type== DataType.UBYTE) {
|
if(left.type== DataType.UBYTE) {
|
||||||
if(right.asIntegerValue!=null) {
|
if(right.type in IntegerDatatypes) {
|
||||||
return LiteralValue(DataType.UBYTE, bytevalue = (left.bytevalue!!.toInt() or (right.asIntegerValue and 255)).toShort(), position = left.position)
|
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() or (right.number.toInt() and 255)).toShort(), left.position)
|
||||||
}
|
}
|
||||||
} else if(left.type== DataType.UWORD) {
|
} else if(left.type== DataType.UWORD) {
|
||||||
if(right.asIntegerValue!=null) {
|
if(right.type in IntegerDatatypes) {
|
||||||
return LiteralValue(DataType.UWORD, wordvalue = left.wordvalue!! or right.asIntegerValue, position = left.position)
|
return NumericLiteralValue(DataType.UWORD, left.number.toInt() or right.number.toInt(), left.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw ExpressionError("cannot calculate $left | $right", left.position)
|
throw ExpressionError("cannot calculate $left | $right", left.position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun bitwiseand(left: LiteralValue, right: LiteralValue): LiteralValue {
|
private fun bitwiseand(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||||
if(left.type== DataType.UBYTE) {
|
if(left.type== DataType.UBYTE) {
|
||||||
if(right.asIntegerValue!=null) {
|
if(right.type in IntegerDatatypes) {
|
||||||
return LiteralValue(DataType.UBYTE, bytevalue = (left.bytevalue!!.toInt() or (right.asIntegerValue and 255)).toShort(), position = left.position)
|
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() or (right.number.toInt() and 255)).toShort(), left.position)
|
||||||
}
|
}
|
||||||
} else if(left.type== DataType.UWORD) {
|
} else if(left.type== DataType.UWORD) {
|
||||||
if(right.asIntegerValue!=null) {
|
if(right.type in IntegerDatatypes) {
|
||||||
return LiteralValue(DataType.UWORD, wordvalue = left.wordvalue!! or right.asIntegerValue, position = left.position)
|
return NumericLiteralValue(DataType.UWORD, left.number.toInt() or right.number.toInt(), left.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw ExpressionError("cannot calculate $left & $right", left.position)
|
throw ExpressionError("cannot calculate $left & $right", left.position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun power(left: LiteralValue, right: LiteralValue): LiteralValue {
|
private fun power(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||||
val error = "cannot calculate $left ** $right"
|
val error = "cannot calculate $left ** $right"
|
||||||
return when {
|
return when {
|
||||||
left.asIntegerValue!=null -> when {
|
left.type in IntegerDatatypes -> when {
|
||||||
right.asIntegerValue!=null -> LiteralValue.optimalNumeric(left.asIntegerValue.toDouble().pow(right.asIntegerValue), left.position)
|
right.type in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble().pow(right.number.toInt()), left.position)
|
||||||
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.asIntegerValue.toDouble().pow(right.floatvalue), position = left.position)
|
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt().toDouble().pow(right.number.toDouble()), left.position)
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
||||||
left.floatvalue!=null -> when {
|
left.type == DataType.FLOAT -> when {
|
||||||
right.asIntegerValue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue.pow(right.asIntegerValue), position = left.position)
|
right.type in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble().pow(right.number.toInt()), left.position)
|
||||||
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue.pow(right.floatvalue), position = left.position)
|
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble().pow(right.number.toDouble()), left.position)
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun plus(left: LiteralValue, right: LiteralValue): LiteralValue {
|
private fun plus(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||||
val error = "cannot add $left and $right"
|
val error = "cannot add $left and $right"
|
||||||
return when {
|
return when {
|
||||||
left.asIntegerValue!=null -> when {
|
left.type in IntegerDatatypes -> when {
|
||||||
right.asIntegerValue!=null -> LiteralValue.optimalNumeric(left.asIntegerValue + right.asIntegerValue, left.position)
|
right.type in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() + right.number.toInt(), left.position)
|
||||||
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.asIntegerValue + right.floatvalue, position = left.position)
|
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() + right.number.toDouble(), left.position)
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
||||||
left.floatvalue!=null -> when {
|
left.type == DataType.FLOAT -> when {
|
||||||
right.asIntegerValue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue + right.asIntegerValue, position = left.position)
|
right.type in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() + right.number.toInt(), left.position)
|
||||||
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue + right.floatvalue, position = left.position)
|
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() + right.number.toDouble(), left.position)
|
||||||
else -> throw ExpressionError(error, left.position)
|
|
||||||
}
|
|
||||||
left.isString -> when {
|
|
||||||
right.isString -> {
|
|
||||||
val newStr = left.strvalue!! + right.strvalue!!
|
|
||||||
if(newStr.length > 255) throw ExpressionError("string too long", left.position)
|
|
||||||
LiteralValue(DataType.STR, strvalue = newStr, position = left.position)
|
|
||||||
}
|
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun minus(left: LiteralValue, right: LiteralValue): LiteralValue {
|
private fun minus(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||||
val error = "cannot subtract $left and $right"
|
val error = "cannot subtract $left and $right"
|
||||||
return when {
|
return when {
|
||||||
left.asIntegerValue!=null -> when {
|
left.type in IntegerDatatypes -> when {
|
||||||
right.asIntegerValue!=null -> LiteralValue.optimalNumeric(left.asIntegerValue - right.asIntegerValue, left.position)
|
right.type in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() - right.number.toInt(), left.position)
|
||||||
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.asIntegerValue - right.floatvalue, position = left.position)
|
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() - right.number.toDouble(), left.position)
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
||||||
left.floatvalue!=null -> when {
|
left.type == DataType.FLOAT -> when {
|
||||||
right.asIntegerValue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue - right.asIntegerValue, position = left.position)
|
right.type in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() - right.number.toInt(), left.position)
|
||||||
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue - right.floatvalue, position = left.position)
|
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() - right.number.toDouble(), left.position)
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun multiply(left: LiteralValue, right: LiteralValue): LiteralValue {
|
private fun multiply(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||||
val error = "cannot multiply ${left.type} and ${right.type}"
|
val error = "cannot multiply ${left.type} and ${right.type}"
|
||||||
return when {
|
return when {
|
||||||
left.asIntegerValue!=null -> when {
|
left.type in IntegerDatatypes -> when {
|
||||||
right.asIntegerValue!=null -> LiteralValue.optimalNumeric(left.asIntegerValue * right.asIntegerValue, left.position)
|
right.type in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() * right.number.toInt(), left.position)
|
||||||
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.asIntegerValue * right.floatvalue, position = left.position)
|
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() * right.number.toDouble(), left.position)
|
||||||
right.isString -> {
|
|
||||||
if(right.strvalue!!.length * left.asIntegerValue > 255) throw ExpressionError("string too long", left.position)
|
|
||||||
LiteralValue(DataType.STR, strvalue = right.strvalue.repeat(left.asIntegerValue), position = left.position)
|
|
||||||
}
|
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
||||||
left.floatvalue!=null -> when {
|
left.type == DataType.FLOAT -> when {
|
||||||
right.asIntegerValue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue * right.asIntegerValue, position = left.position)
|
right.type in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() * right.number.toInt(), left.position)
|
||||||
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue * right.floatvalue, position = left.position)
|
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() * right.number.toDouble(), left.position)
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
@ -228,29 +213,29 @@ class ConstExprEvaluator {
|
|||||||
private fun divideByZeroError(pos: Position): Unit =
|
private fun divideByZeroError(pos: Position): Unit =
|
||||||
throw ExpressionError("division by zero", pos)
|
throw ExpressionError("division by zero", pos)
|
||||||
|
|
||||||
private fun divide(left: LiteralValue, right: LiteralValue): LiteralValue {
|
private fun divide(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||||
val error = "cannot divide $left by $right"
|
val error = "cannot divide $left by $right"
|
||||||
return when {
|
return when {
|
||||||
left.asIntegerValue!=null -> when {
|
left.type in IntegerDatatypes -> when {
|
||||||
right.asIntegerValue!=null -> {
|
right.type in IntegerDatatypes -> {
|
||||||
if(right.asIntegerValue==0) divideByZeroError(right.position)
|
if(right.number.toInt()==0) divideByZeroError(right.position)
|
||||||
val result: Int = left.asIntegerValue / right.asIntegerValue
|
val result: Int = left.number.toInt() / right.number.toInt()
|
||||||
LiteralValue.optimalNumeric(result, left.position)
|
NumericLiteralValue.optimalNumeric(result, left.position)
|
||||||
}
|
}
|
||||||
right.floatvalue!=null -> {
|
right.type == DataType.FLOAT -> {
|
||||||
if(right.floatvalue==0.0) divideByZeroError(right.position)
|
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
|
||||||
LiteralValue(DataType.FLOAT, floatvalue = left.asIntegerValue / right.floatvalue, position = left.position)
|
NumericLiteralValue(DataType.FLOAT, left.number.toInt() / right.number.toDouble(), left.position)
|
||||||
}
|
}
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
||||||
left.floatvalue!=null -> when {
|
left.type == DataType.FLOAT -> when {
|
||||||
right.asIntegerValue!=null -> {
|
right.type in IntegerDatatypes -> {
|
||||||
if(right.asIntegerValue==0) divideByZeroError(right.position)
|
if(right.number.toInt()==0) divideByZeroError(right.position)
|
||||||
LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue / right.asIntegerValue, position = left.position)
|
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() / right.number.toInt(), left.position)
|
||||||
}
|
}
|
||||||
right.floatvalue!=null -> {
|
right.type == DataType.FLOAT -> {
|
||||||
if(right.floatvalue==0.0) divideByZeroError(right.position)
|
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
|
||||||
LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue / right.floatvalue, position = left.position)
|
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() / right.number.toDouble(), left.position)
|
||||||
}
|
}
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
||||||
@ -258,28 +243,28 @@ class ConstExprEvaluator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun remainder(left: LiteralValue, right: LiteralValue): LiteralValue {
|
private fun remainder(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||||
val error = "cannot compute remainder of $left by $right"
|
val error = "cannot compute remainder of $left by $right"
|
||||||
return when {
|
return when {
|
||||||
left.asIntegerValue!=null -> when {
|
left.type in IntegerDatatypes -> when {
|
||||||
right.asIntegerValue!=null -> {
|
right.type in IntegerDatatypes -> {
|
||||||
if(right.asIntegerValue==0) divideByZeroError(right.position)
|
if(right.number.toInt()==0) divideByZeroError(right.position)
|
||||||
LiteralValue.optimalNumeric(left.asIntegerValue.toDouble() % right.asIntegerValue.toDouble(), left.position)
|
NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble() % right.number.toInt().toDouble(), left.position)
|
||||||
}
|
}
|
||||||
right.floatvalue!=null -> {
|
right.type == DataType.FLOAT -> {
|
||||||
if(right.floatvalue==0.0) divideByZeroError(right.position)
|
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
|
||||||
LiteralValue(DataType.FLOAT, floatvalue = left.asIntegerValue % right.floatvalue, position = left.position)
|
NumericLiteralValue(DataType.FLOAT, left.number.toInt() % right.number.toDouble(), left.position)
|
||||||
}
|
}
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
||||||
left.floatvalue!=null -> when {
|
left.type == DataType.FLOAT -> when {
|
||||||
right.asIntegerValue!=null -> {
|
right.type in IntegerDatatypes -> {
|
||||||
if(right.asIntegerValue==0) divideByZeroError(right.position)
|
if(right.number.toInt()==0) divideByZeroError(right.position)
|
||||||
LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue % right.asIntegerValue, position = left.position)
|
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() % right.number.toInt(), left.position)
|
||||||
}
|
}
|
||||||
right.floatvalue!=null -> {
|
right.type == DataType.FLOAT -> {
|
||||||
if(right.floatvalue==0.0) divideByZeroError(right.position)
|
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
|
||||||
LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue % right.floatvalue, position = left.position)
|
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() % right.number.toDouble(), left.position)
|
||||||
}
|
}
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,22 @@
|
|||||||
package prog8.optimizer
|
package prog8.optimizer
|
||||||
|
|
||||||
import prog8.ast.*
|
import prog8.ast.IFunctionCall
|
||||||
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.processing.IAstModifyingVisitor
|
import prog8.ast.processing.IAstModifyingVisitor
|
||||||
|
import prog8.ast.processing.fixupArrayDatatype
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.compiler.HeapValues
|
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_NEGATIVE
|
||||||
import prog8.compiler.IntegerOrAddressOf
|
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_POSITIVE
|
||||||
import prog8.compiler.target.c64.FLOAT_MAX_NEGATIVE
|
import prog8.compiler.target.c64.codegen.AssemblyError
|
||||||
import prog8.compiler.target.c64.FLOAT_MAX_POSITIVE
|
import prog8.functions.BuiltinFunctions
|
||||||
import kotlin.math.floor
|
import kotlin.math.floor
|
||||||
|
|
||||||
|
|
||||||
class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
|
class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
|
||||||
var optimizationsDone: Int = 0
|
var optimizationsDone: Int = 0
|
||||||
var errors : MutableList<AstException> = mutableListOf()
|
var errors : MutableList<AstException> = mutableListOf()
|
||||||
|
|
||||||
private val reportedErrorMessages = mutableSetOf<String>()
|
private val reportedErrorMessages = mutableSetOf<String>()
|
||||||
|
|
||||||
fun addError(x: AstException) {
|
fun addError(x: AstException) {
|
||||||
@ -26,30 +27,27 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(decl: VarDecl): IStatement {
|
override fun visit(decl: VarDecl): Statement {
|
||||||
// the initializer value can't refer to the variable itself (recursive definition)
|
// the initializer value can't refer to the variable itself (recursive definition)
|
||||||
|
// TODO: use call tree for this?
|
||||||
if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true) {
|
if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true) {
|
||||||
errors.add(ExpressionError("recursive var declaration", decl.position))
|
errors.add(ExpressionError("recursive var declaration", decl.position))
|
||||||
return decl
|
return decl
|
||||||
}
|
}
|
||||||
|
|
||||||
if(decl.type==VarDeclType.CONST || decl.type==VarDeclType.VAR) {
|
if(decl.type==VarDeclType.CONST || decl.type==VarDeclType.VAR) {
|
||||||
val litval = decl.value as? LiteralValue
|
|
||||||
if(litval!=null && litval.isArray && litval.heapId!=null)
|
|
||||||
fixupArrayTypeOnHeap(decl, litval)
|
|
||||||
|
|
||||||
if(decl.isArray){
|
if(decl.isArray){
|
||||||
// for arrays that have no size specifier (or a non-constant one) attempt to deduce the size
|
|
||||||
if(decl.arraysize==null) {
|
if(decl.arraysize==null) {
|
||||||
val arrayval = (decl.value as? LiteralValue)?.arrayvalue
|
// for arrays that have no size specifier (or a non-constant one) attempt to deduce the size
|
||||||
|
val arrayval = decl.value as? ArrayLiteralValue
|
||||||
if(arrayval!=null) {
|
if(arrayval!=null) {
|
||||||
decl.arraysize = ArrayIndex(LiteralValue.optimalInteger(arrayval.size, decl.position), decl.position)
|
decl.arraysize = ArrayIndex(NumericLiteralValue.optimalInteger(arrayval.value.size, decl.position), decl.position)
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(decl.arraysize?.size()==null) {
|
else if(decl.arraysize?.size()==null) {
|
||||||
val size = decl.arraysize!!.index.accept(this)
|
val size = decl.arraysize!!.index.accept(this)
|
||||||
if(size is LiteralValue) {
|
if(size is NumericLiteralValue) {
|
||||||
decl.arraysize = ArrayIndex(size, decl.position)
|
decl.arraysize = ArrayIndex(size, decl.position)
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
}
|
}
|
||||||
@ -59,87 +57,100 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
|
|||||||
when(decl.datatype) {
|
when(decl.datatype) {
|
||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
// vardecl: for scalar float vars, promote constant integer initialization values to floats
|
// vardecl: for scalar float vars, promote constant integer initialization values to floats
|
||||||
if (litval != null && litval.type in IntegerDatatypes) {
|
val litval = decl.value as? NumericLiteralValue
|
||||||
val newValue = LiteralValue(DataType.FLOAT, floatvalue = litval.asNumericValue!!.toDouble(), position = litval.position)
|
if (litval!=null && litval.type in IntegerDatatypes) {
|
||||||
|
val newValue = NumericLiteralValue(DataType.FLOAT, litval.number.toDouble(), litval.position)
|
||||||
decl.value = newValue
|
decl.value = newValue
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
return decl
|
return super.visit(decl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
in StringDatatypes -> {
|
||||||
|
// nothing to do for strings
|
||||||
|
}
|
||||||
|
DataType.STRUCT -> {
|
||||||
|
// struct defintions don't have anything else in them
|
||||||
|
}
|
||||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||||
|
val numericLv = decl.value as? NumericLiteralValue
|
||||||
val rangeExpr = decl.value as? RangeExpr
|
val rangeExpr = decl.value as? RangeExpr
|
||||||
if(rangeExpr!=null) {
|
if(rangeExpr!=null) {
|
||||||
// convert the initializer range expression to an actual array (will be put on heap later)
|
// convert the initializer range expression to an actual array
|
||||||
val declArraySize = decl.arraysize?.size()
|
val declArraySize = decl.arraysize?.size()
|
||||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||||
errors.add(ExpressionError("range expression size doesn't match declared array size", decl.value?.position!!))
|
errors.add(ExpressionError("range expression size doesn't match declared array size", decl.value?.position!!))
|
||||||
val constRange = rangeExpr.toConstantIntegerRange()
|
val constRange = rangeExpr.toConstantIntegerRange()
|
||||||
if(constRange!=null) {
|
if(constRange!=null) {
|
||||||
val eltType = rangeExpr.inferType(program)!!
|
val eltType = rangeExpr.inferType(program).typeOrElse(DataType.UBYTE)
|
||||||
if(eltType in ByteDatatypes) {
|
if(eltType in ByteDatatypes) {
|
||||||
decl.value = LiteralValue(decl.datatype,
|
decl.value = ArrayLiteralValue(decl.datatype,
|
||||||
arrayvalue = constRange.map { LiteralValue(eltType, bytevalue = it.toShort(), position = decl.value!!.position) }
|
constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) }.toTypedArray(),
|
||||||
.toTypedArray(), position = decl.value!!.position)
|
position = decl.value!!.position)
|
||||||
} else {
|
} else {
|
||||||
decl.value = LiteralValue(decl.datatype,
|
decl.value = ArrayLiteralValue(decl.datatype,
|
||||||
arrayvalue = constRange.map { LiteralValue(eltType, wordvalue = it, position = decl.value!!.position) }
|
constRange.map { NumericLiteralValue(eltType, it, decl.value!!.position) }.toTypedArray(),
|
||||||
.toTypedArray(), position = decl.value!!.position)
|
position = decl.value!!.position)
|
||||||
}
|
}
|
||||||
decl.value!!.linkParents(decl)
|
decl.value!!.linkParents(decl)
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
return decl
|
return super.visit(decl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(litval?.type== DataType.FLOAT)
|
if(numericLv!=null && numericLv.type== DataType.FLOAT)
|
||||||
errors.add(ExpressionError("arraysize requires only integers here", litval.position))
|
errors.add(ExpressionError("arraysize requires only integers here", numericLv.position))
|
||||||
val size = decl.arraysize?.size() ?: return decl
|
val size = decl.arraysize?.size() ?: return decl
|
||||||
if ((litval==null || !litval.isArray) && rangeExpr==null) {
|
if (rangeExpr==null && numericLv!=null) {
|
||||||
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
|
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
|
||||||
val fillvalue = if (litval == null) 0 else litval.asIntegerValue ?: 0
|
val fillvalue = numericLv.number.toInt()
|
||||||
when(decl.datatype){
|
when(decl.datatype){
|
||||||
DataType.ARRAY_UB -> {
|
DataType.ARRAY_UB -> {
|
||||||
if(fillvalue !in 0..255)
|
if(fillvalue !in 0..255)
|
||||||
errors.add(ExpressionError("ubyte value overflow", litval?.position
|
errors.add(ExpressionError("ubyte value overflow", numericLv.position))
|
||||||
?: decl.position))
|
|
||||||
}
|
}
|
||||||
DataType.ARRAY_B -> {
|
DataType.ARRAY_B -> {
|
||||||
if(fillvalue !in -128..127)
|
if(fillvalue !in -128..127)
|
||||||
errors.add(ExpressionError("byte value overflow", litval?.position
|
errors.add(ExpressionError("byte value overflow", numericLv.position))
|
||||||
?: decl.position))
|
|
||||||
}
|
}
|
||||||
DataType.ARRAY_UW -> {
|
DataType.ARRAY_UW -> {
|
||||||
if(fillvalue !in 0..65535)
|
if(fillvalue !in 0..65535)
|
||||||
errors.add(ExpressionError("uword value overflow", litval?.position
|
errors.add(ExpressionError("uword value overflow", numericLv.position))
|
||||||
?: decl.position))
|
|
||||||
}
|
}
|
||||||
DataType.ARRAY_W -> {
|
DataType.ARRAY_W -> {
|
||||||
if(fillvalue !in -32768..32767)
|
if(fillvalue !in -32768..32767)
|
||||||
errors.add(ExpressionError("word value overflow", litval?.position
|
errors.add(ExpressionError("word value overflow", numericLv.position))
|
||||||
?: decl.position))
|
|
||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
val heapId = program.heap.addIntegerArray(decl.datatype, Array(size) { IntegerOrAddressOf(fillvalue, null) })
|
// create the array itself, filled with the fillvalue.
|
||||||
decl.value = LiteralValue(decl.datatype, initHeapId = heapId, position = litval?.position
|
val array = Array(size) {fillvalue}.map { NumericLiteralValue.optimalInteger(it, numericLv.position) as Expression}.toTypedArray()
|
||||||
?: decl.position)
|
val refValue = ArrayLiteralValue(decl.datatype, array, position = numericLv.position)
|
||||||
|
refValue.addToHeap()
|
||||||
|
decl.value = refValue
|
||||||
|
refValue.parent=decl
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
return decl
|
return super.visit(decl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.ARRAY_F -> {
|
DataType.ARRAY_F -> {
|
||||||
val size = decl.arraysize?.size() ?: return decl
|
val size = decl.arraysize?.size() ?: return decl
|
||||||
if (litval==null || !litval.isArray) {
|
val litval = decl.value as? NumericLiteralValue
|
||||||
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
|
if(litval==null) {
|
||||||
val fillvalue = if (litval == null) 0.0 else litval.asNumericValue?.toDouble() ?: 0.0
|
// there's no initialization value, but the size is known, so we're ok.
|
||||||
if(fillvalue< FLOAT_MAX_NEGATIVE || fillvalue> FLOAT_MAX_POSITIVE)
|
return super.visit(decl)
|
||||||
errors.add(ExpressionError("float value overflow", litval?.position
|
} else {
|
||||||
?: decl.position))
|
// arraysize initializer is a single int, and we know the size.
|
||||||
|
val fillvalue = litval.number.toDouble()
|
||||||
|
if (fillvalue < FLOAT_MAX_NEGATIVE || fillvalue > FLOAT_MAX_POSITIVE)
|
||||||
|
errors.add(ExpressionError("float value overflow", litval.position))
|
||||||
else {
|
else {
|
||||||
val heapId = program.heap.addDoublesArray(DoubleArray(size) { fillvalue })
|
// create the array itself, filled with the fillvalue.
|
||||||
decl.value = LiteralValue(DataType.ARRAY_F, initHeapId = heapId, position = litval?.position
|
val array = Array(size) {fillvalue}.map { NumericLiteralValue(DataType.FLOAT, it, litval.position) as Expression}.toTypedArray()
|
||||||
?: decl.position)
|
val refValue = ArrayLiteralValue(DataType.ARRAY_F, array, position = litval.position)
|
||||||
|
refValue.addToHeap()
|
||||||
|
decl.value = refValue
|
||||||
|
refValue.parent=decl
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
return decl
|
return super.visit(decl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -152,55 +163,40 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
|
|||||||
return super.visit(decl)
|
return super.visit(decl)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fixupArrayTypeOnHeap(decl: VarDecl, litval: LiteralValue) {
|
|
||||||
// fix the type of the array value that's on the heap, to match the vardecl.
|
|
||||||
// notice that checking the bounds of the actual values is not done here, but in the AstChecker later.
|
|
||||||
|
|
||||||
if(decl.datatype==litval.type)
|
|
||||||
return // already correct datatype
|
|
||||||
val heapId = litval.heapId ?: throw FatalAstException("expected array to be on heap $litval")
|
|
||||||
val array = program.heap.get(heapId)
|
|
||||||
when(decl.datatype) {
|
|
||||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
|
||||||
if(array.array!=null) {
|
|
||||||
program.heap.update(heapId, HeapValues.HeapValue(decl.datatype, null, array.array, null))
|
|
||||||
decl.value = LiteralValue(decl.datatype, initHeapId = heapId, position = litval.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.ARRAY_F -> {
|
|
||||||
if(array.array!=null) {
|
|
||||||
// convert a non-float array to floats
|
|
||||||
val doubleArray = array.array.map { it.integer!!.toDouble() }.toDoubleArray()
|
|
||||||
program.heap.update(heapId, HeapValues.HeapValue(DataType.ARRAY_F, null, null, doubleArray))
|
|
||||||
decl.value = LiteralValue(decl.datatype, initHeapId = heapId, position = litval.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> throw FatalAstException("invalid array vardecl type ${decl.datatype}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* replace identifiers that refer to const value, with the value itself (if it's a simple type)
|
* replace identifiers that refer to const value, with the value itself (if it's a simple type)
|
||||||
*/
|
*/
|
||||||
override fun visit(identifier: IdentifierReference): IExpression {
|
override fun visit(identifier: IdentifierReference): Expression {
|
||||||
|
// don't replace when it's an assignment target or loop variable
|
||||||
|
if(identifier.parent is AssignTarget)
|
||||||
|
return identifier
|
||||||
|
var forloop = identifier.parent as? ForLoop
|
||||||
|
if(forloop==null)
|
||||||
|
forloop = identifier.parent.parent as? ForLoop
|
||||||
|
if(forloop!=null && identifier===forloop.loopVar)
|
||||||
|
return identifier
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
val cval = identifier.constValue(program) ?: return identifier
|
val cval = identifier.constValue(program) ?: return identifier
|
||||||
return if(cval.isNumeric) {
|
return when {
|
||||||
val copy = LiteralValue(cval.type, cval.bytevalue, cval.wordvalue, cval.floatvalue, null, cval.arrayvalue, position = identifier.position)
|
cval.type in NumericDatatypes -> {
|
||||||
copy.parent = identifier.parent
|
val copy = NumericLiteralValue(cval.type, cval.number, identifier.position)
|
||||||
copy
|
copy.parent = identifier.parent
|
||||||
} else
|
copy
|
||||||
identifier
|
}
|
||||||
|
cval.type in PassByReferenceDatatypes -> throw AssemblyError("pass-by-reference type should not be considered a constant")
|
||||||
|
else -> identifier
|
||||||
|
}
|
||||||
} catch (ax: AstException) {
|
} catch (ax: AstException) {
|
||||||
addError(ax)
|
addError(ax)
|
||||||
identifier
|
identifier
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(functionCall: FunctionCall): IExpression {
|
override fun visit(functionCall: FunctionCall): Expression {
|
||||||
|
super.visit(functionCall)
|
||||||
|
typeCastConstArguments(functionCall)
|
||||||
return try {
|
return try {
|
||||||
super.visit(functionCall)
|
|
||||||
typeCastConstArguments(functionCall)
|
|
||||||
functionCall.constValue(program) ?: functionCall
|
functionCall.constValue(program) ?: functionCall
|
||||||
} catch (ax: AstException) {
|
} catch (ax: AstException) {
|
||||||
addError(ax)
|
addError(ax)
|
||||||
@ -208,13 +204,30 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(functionCallStatement: FunctionCallStatement): IStatement {
|
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
|
||||||
super.visit(functionCallStatement)
|
super.visit(functionCallStatement)
|
||||||
typeCastConstArguments(functionCallStatement)
|
typeCastConstArguments(functionCallStatement)
|
||||||
return functionCallStatement
|
return functionCallStatement
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun typeCastConstArguments(functionCall: IFunctionCall) {
|
private fun typeCastConstArguments(functionCall: IFunctionCall) {
|
||||||
|
if(functionCall.target.nameInSource.size==1) {
|
||||||
|
val builtinFunction = BuiltinFunctions[functionCall.target.nameInSource.single()]
|
||||||
|
if(builtinFunction!=null) {
|
||||||
|
// match the arguments of a builtin function signature.
|
||||||
|
for(arg in functionCall.arglist.withIndex().zip(builtinFunction.parameters)) {
|
||||||
|
val possibleDts = arg.second.possibleDatatypes
|
||||||
|
val argConst = arg.first.value.constValue(program)
|
||||||
|
if(argConst!=null && argConst.type !in possibleDts) {
|
||||||
|
val convertedValue = argConst.cast(possibleDts.first())
|
||||||
|
functionCall.arglist[arg.first.index] = convertedValue
|
||||||
|
optimizationsDone++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// match the arguments of a subroutine.
|
||||||
val subroutine = functionCall.target.targetSubroutine(program.namespace)
|
val subroutine = functionCall.target.targetSubroutine(program.namespace)
|
||||||
if(subroutine!=null) {
|
if(subroutine!=null) {
|
||||||
// if types differ, try to typecast constant arguments to the function call to the desired data type of the parameter
|
// if types differ, try to typecast constant arguments to the function call to the desired data type of the parameter
|
||||||
@ -223,16 +236,14 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
|
|||||||
val argConst = arg.first.value.constValue(program)
|
val argConst = arg.first.value.constValue(program)
|
||||||
if(argConst!=null && argConst.type!=expectedDt) {
|
if(argConst!=null && argConst.type!=expectedDt) {
|
||||||
val convertedValue = argConst.cast(expectedDt)
|
val convertedValue = argConst.cast(expectedDt)
|
||||||
if(convertedValue!=null) {
|
functionCall.arglist[arg.first.index] = convertedValue
|
||||||
functionCall.arglist[arg.first.index] = convertedValue
|
optimizationsDone++
|
||||||
optimizationsDone++
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(memread: DirectMemoryRead): IExpression {
|
override fun visit(memread: DirectMemoryRead): Expression {
|
||||||
// @( &thing ) --> thing
|
// @( &thing ) --> thing
|
||||||
val addrOf = memread.addressExpression as? AddressOf
|
val addrOf = memread.addressExpression as? AddressOf
|
||||||
if(addrOf!=null)
|
if(addrOf!=null)
|
||||||
@ -245,48 +256,43 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
|
|||||||
* Compile-time constant sub expressions will be evaluated on the spot.
|
* 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
|
* For instance, the expression for "- 4.5" will be optimized into the float literal -4.5
|
||||||
*/
|
*/
|
||||||
override fun visit(expr: PrefixExpression): IExpression {
|
override fun visit(expr: PrefixExpression): Expression {
|
||||||
return try {
|
return try {
|
||||||
super.visit(expr)
|
val prefixExpr=super.visit(expr)
|
||||||
|
if(prefixExpr !is PrefixExpression)
|
||||||
|
return prefixExpr
|
||||||
|
|
||||||
val subexpr = expr.expression
|
val subexpr = prefixExpr.expression
|
||||||
if (subexpr is LiteralValue) {
|
if (subexpr is NumericLiteralValue) {
|
||||||
// accept prefixed literal values (such as -3, not true)
|
// accept prefixed literal values (such as -3, not true)
|
||||||
return when {
|
return when {
|
||||||
expr.operator == "+" -> subexpr
|
prefixExpr.operator == "+" -> subexpr
|
||||||
expr.operator == "-" -> when {
|
prefixExpr.operator == "-" -> when {
|
||||||
subexpr.asIntegerValue!= null -> {
|
subexpr.type in IntegerDatatypes -> {
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
LiteralValue.optimalNumeric(-subexpr.asIntegerValue, subexpr.position)
|
NumericLiteralValue.optimalNumeric(-subexpr.number.toInt(), subexpr.position)
|
||||||
}
|
}
|
||||||
subexpr.floatvalue != null -> {
|
subexpr.type == DataType.FLOAT -> {
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
LiteralValue(DataType.FLOAT, floatvalue = -subexpr.floatvalue, position = subexpr.position)
|
NumericLiteralValue(DataType.FLOAT, -subexpr.number.toDouble(), subexpr.position)
|
||||||
}
|
}
|
||||||
else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
|
else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
|
||||||
}
|
}
|
||||||
expr.operator == "~" -> when {
|
prefixExpr.operator == "~" -> when {
|
||||||
subexpr.asIntegerValue != null -> {
|
subexpr.type in IntegerDatatypes -> {
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
LiteralValue.optimalNumeric(subexpr.asIntegerValue.inv(), subexpr.position)
|
NumericLiteralValue.optimalNumeric(subexpr.number.toInt().inv(), subexpr.position)
|
||||||
}
|
}
|
||||||
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
|
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
|
||||||
}
|
}
|
||||||
expr.operator == "not" -> when {
|
prefixExpr.operator == "not" -> {
|
||||||
subexpr.asIntegerValue != null -> {
|
optimizationsDone++
|
||||||
optimizationsDone++
|
NumericLiteralValue.fromBoolean(subexpr.number.toDouble() == 0.0, subexpr.position)
|
||||||
LiteralValue.fromBoolean(subexpr.asIntegerValue == 0, subexpr.position)
|
|
||||||
}
|
|
||||||
subexpr.floatvalue != null -> {
|
|
||||||
optimizationsDone++
|
|
||||||
LiteralValue.fromBoolean(subexpr.floatvalue == 0.0, subexpr.position)
|
|
||||||
}
|
|
||||||
else -> throw ExpressionError("can not take logical not of $subexpr", subexpr.position)
|
|
||||||
}
|
}
|
||||||
else -> throw ExpressionError(expr.operator, subexpr.position)
|
else -> throw ExpressionError(prefixExpr.operator, subexpr.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return expr
|
return prefixExpr
|
||||||
} catch (ax: AstException) {
|
} catch (ax: AstException) {
|
||||||
addError(ax)
|
addError(ax)
|
||||||
expr
|
expr
|
||||||
@ -310,9 +316,14 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
|
|||||||
* (X / c1) * c2 -> X / (c2/c1)
|
* (X / c1) * c2 -> X / (c2/c1)
|
||||||
* (X + c1) - c2 -> X + (c1-c2)
|
* (X + c1) - c2 -> X + (c1-c2)
|
||||||
*/
|
*/
|
||||||
override fun visit(expr: BinaryExpression): IExpression {
|
override fun visit(expr: BinaryExpression): Expression {
|
||||||
return try {
|
return try {
|
||||||
super.visit(expr)
|
super.visit(expr)
|
||||||
|
|
||||||
|
if(expr.left is StringLiteralValue || expr.left is ArrayLiteralValue
|
||||||
|
|| expr.right is StringLiteralValue || expr.right is ArrayLiteralValue)
|
||||||
|
throw FatalAstException("binexpr with reference litval instead of numeric")
|
||||||
|
|
||||||
val leftconst = expr.left.constValue(program)
|
val leftconst = expr.left.constValue(program)
|
||||||
val rightconst = expr.right.constValue(program)
|
val rightconst = expr.right.constValue(program)
|
||||||
|
|
||||||
@ -333,12 +344,13 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// const fold when both operands are a const
|
// const fold when both operands are a const
|
||||||
val evaluator = ConstExprEvaluator()
|
|
||||||
return when {
|
return when {
|
||||||
leftconst != null && rightconst != null -> {
|
leftconst != null && rightconst != null -> {
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
|
val evaluator = ConstExprEvaluator()
|
||||||
evaluator.evaluate(leftconst, expr.operator, rightconst)
|
evaluator.evaluate(leftconst, expr.operator, rightconst)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> expr
|
else -> expr
|
||||||
}
|
}
|
||||||
} catch (ax: AstException) {
|
} catch (ax: AstException) {
|
||||||
@ -352,9 +364,9 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
|
|||||||
leftIsConst: Boolean,
|
leftIsConst: Boolean,
|
||||||
rightIsConst: Boolean,
|
rightIsConst: Boolean,
|
||||||
subleftIsConst: Boolean,
|
subleftIsConst: Boolean,
|
||||||
subrightIsConst: Boolean): IExpression
|
subrightIsConst: Boolean): Expression
|
||||||
{
|
{
|
||||||
// @todo this implements only a small set of possible reorderings for now
|
// todo: this implements only a small set of possible reorderings at this time
|
||||||
if(expr.operator==subExpr.operator) {
|
if(expr.operator==subExpr.operator) {
|
||||||
// both operators are the isSameAs.
|
// both operators are the isSameAs.
|
||||||
// If + or *, we can simply swap the const of expr and Var in subexpr.
|
// If + or *, we can simply swap the const of expr and Var in subexpr.
|
||||||
@ -539,177 +551,168 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(forLoop: ForLoop): IStatement {
|
override fun visit(forLoop: ForLoop): Statement {
|
||||||
|
|
||||||
fun adjustRangeDt(rangeFrom: LiteralValue, targetDt: DataType, rangeTo: LiteralValue, stepLiteral: LiteralValue?, range: RangeExpr): RangeExpr {
|
fun adjustRangeDt(rangeFrom: NumericLiteralValue, targetDt: DataType, rangeTo: NumericLiteralValue, stepLiteral: NumericLiteralValue?, range: RangeExpr): RangeExpr {
|
||||||
val newFrom = rangeFrom.cast(targetDt)
|
val newFrom: NumericLiteralValue
|
||||||
val newTo = rangeTo.cast(targetDt)
|
val newTo: NumericLiteralValue
|
||||||
if (newFrom != null && newTo != null) {
|
try {
|
||||||
val newStep: IExpression =
|
newFrom = rangeFrom.cast(targetDt)
|
||||||
if (stepLiteral != null) (stepLiteral.cast(targetDt) ?: stepLiteral) else range.step
|
newTo = rangeTo.cast(targetDt)
|
||||||
return RangeExpr(newFrom, newTo, newStep, range.position)
|
} catch (x: ExpressionError) {
|
||||||
|
return range
|
||||||
|
}
|
||||||
|
val newStep: Expression = try {
|
||||||
|
stepLiteral?.cast(targetDt)?: range.step
|
||||||
|
} catch(ee: ExpressionError) {
|
||||||
|
range.step
|
||||||
|
}
|
||||||
|
return RangeExpr(newFrom, newTo, newStep, range.position)
|
||||||
|
}
|
||||||
|
|
||||||
|
val forLoop2 = super.visit(forLoop) as ForLoop
|
||||||
|
|
||||||
|
// check if we need to adjust an array literal to the loop variable's datatype
|
||||||
|
val array = forLoop2.iterable as? ArrayLiteralValue
|
||||||
|
if(array!=null) {
|
||||||
|
val loopvarDt: DataType = when {
|
||||||
|
forLoop.loopVar!=null -> forLoop.loopVar!!.inferType(program).typeOrElse(DataType.UBYTE)
|
||||||
|
forLoop.loopRegister!=null -> DataType.UBYTE
|
||||||
|
else -> throw FatalAstException("weird for loop")
|
||||||
|
}
|
||||||
|
|
||||||
|
val arrayType = when(loopvarDt) {
|
||||||
|
DataType.UBYTE -> DataType.ARRAY_UB
|
||||||
|
DataType.BYTE -> DataType.ARRAY_B
|
||||||
|
DataType.UWORD -> DataType.ARRAY_UW
|
||||||
|
DataType.WORD -> DataType.ARRAY_W
|
||||||
|
DataType.FLOAT -> DataType.ARRAY_F
|
||||||
|
else -> throw FatalAstException("invalid array elt type")
|
||||||
|
}
|
||||||
|
val array2 = array.cast(arrayType)
|
||||||
|
if(array2!=null && array2!==array) {
|
||||||
|
forLoop2.iterable = array2
|
||||||
|
array2.linkParents(forLoop2)
|
||||||
|
array2.addToHeap()
|
||||||
}
|
}
|
||||||
return range
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// adjust the datatype of a range expression in for loops to the loop variable.
|
// adjust the datatype of a range expression in for loops to the loop variable.
|
||||||
val resultStmt = super.visit(forLoop) as ForLoop
|
val iterableRange = forLoop2.iterable as? RangeExpr ?: return forLoop2
|
||||||
val iterableRange = resultStmt.iterable as? RangeExpr ?: return resultStmt
|
val rangeFrom = iterableRange.from as? NumericLiteralValue
|
||||||
val rangeFrom = iterableRange.from as? LiteralValue
|
val rangeTo = iterableRange.to as? NumericLiteralValue
|
||||||
val rangeTo = iterableRange.to as? LiteralValue
|
if(rangeFrom==null || rangeTo==null) return forLoop2
|
||||||
if(rangeFrom==null || rangeTo==null) return resultStmt
|
|
||||||
|
|
||||||
val loopvar = resultStmt.loopVar?.targetVarDecl(program.namespace)
|
val loopvar = forLoop2.loopVar?.targetVarDecl(program.namespace)
|
||||||
if(loopvar!=null) {
|
if(loopvar!=null) {
|
||||||
val stepLiteral = iterableRange.step as? LiteralValue
|
val stepLiteral = iterableRange.step as? NumericLiteralValue
|
||||||
when(loopvar.datatype) {
|
when(loopvar.datatype) {
|
||||||
DataType.UBYTE -> {
|
DataType.UBYTE -> {
|
||||||
if(rangeFrom.type!= DataType.UBYTE) {
|
if(rangeFrom.type!= DataType.UBYTE) {
|
||||||
// attempt to translate the iterable into ubyte values
|
// attempt to translate the iterable into ubyte values
|
||||||
resultStmt.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.BYTE -> {
|
DataType.BYTE -> {
|
||||||
if(rangeFrom.type!= DataType.BYTE) {
|
if(rangeFrom.type!= DataType.BYTE) {
|
||||||
// attempt to translate the iterable into byte values
|
// attempt to translate the iterable into byte values
|
||||||
resultStmt.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.UWORD -> {
|
DataType.UWORD -> {
|
||||||
if(rangeFrom.type!= DataType.UWORD) {
|
if(rangeFrom.type!= DataType.UWORD) {
|
||||||
// attempt to translate the iterable into uword values
|
// attempt to translate the iterable into uword values
|
||||||
resultStmt.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.WORD -> {
|
DataType.WORD -> {
|
||||||
if(rangeFrom.type!= DataType.WORD) {
|
if(rangeFrom.type!= DataType.WORD) {
|
||||||
// attempt to translate the iterable into word values
|
// attempt to translate the iterable into word values
|
||||||
resultStmt.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> throw FatalAstException("invalid loopvar datatype $loopvar")
|
else -> throw FatalAstException("invalid loopvar datatype $loopvar")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return resultStmt
|
return forLoop2
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(literalValue: LiteralValue): LiteralValue {
|
override fun visit(arrayLiteral: ArrayLiteralValue): Expression {
|
||||||
val litval = super.visit(literalValue)
|
val array = super.visit(arrayLiteral)
|
||||||
if(litval.isString) {
|
if(array is ArrayLiteralValue) {
|
||||||
// intern the string; move it into the heap
|
array.addToHeap()
|
||||||
if(litval.strvalue!!.length !in 1..255)
|
val vardecl = array.parent as? VarDecl
|
||||||
addError(ExpressionError("string literal length must be between 1 and 255", litval.position))
|
return if (vardecl!=null) {
|
||||||
else {
|
fixupArrayDatatype(array, vardecl, program)
|
||||||
litval.addToHeap(program.heap) // TODO: we don't know the actual string type yet, STR != STR_S etc...
|
} else {
|
||||||
|
// it's not an array associated with a vardecl, attempt to guess the data type from the array values
|
||||||
|
fixupArrayDatatype(array, program)
|
||||||
}
|
}
|
||||||
} else if(litval.arrayvalue!=null) {
|
|
||||||
// first, adjust the array datatype
|
|
||||||
val litval2 = adjustArrayValDatatype(litval)
|
|
||||||
litval2.addToHeap(program.heap)
|
|
||||||
return litval2
|
|
||||||
}
|
}
|
||||||
return litval
|
return array
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun adjustArrayValDatatype(litval: LiteralValue): LiteralValue {
|
override fun visit(assignment: Assignment): Statement {
|
||||||
val array = litval.arrayvalue!!
|
|
||||||
val typesInArray = array.mapNotNull { it.inferType(program) }.toSet()
|
|
||||||
val arrayDt =
|
|
||||||
when {
|
|
||||||
array.any { it is AddressOf } -> DataType.ARRAY_UW
|
|
||||||
DataType.FLOAT in typesInArray -> DataType.ARRAY_F
|
|
||||||
DataType.WORD in typesInArray -> DataType.ARRAY_W
|
|
||||||
else -> {
|
|
||||||
val allElementsAreConstantOrAddressOf = array.fold(true) { c, expr-> c and (expr is LiteralValue || expr is AddressOf)}
|
|
||||||
if(!allElementsAreConstantOrAddressOf) {
|
|
||||||
addError(ExpressionError("array literal can only consist of constant primitive numerical values or memory pointers", litval.position))
|
|
||||||
return litval
|
|
||||||
} else {
|
|
||||||
val integerArray = array.map { it.constValue(program)!!.asIntegerValue!! }
|
|
||||||
val maxValue = integerArray.max()!!
|
|
||||||
val minValue = integerArray.min()!!
|
|
||||||
if (minValue >= 0) {
|
|
||||||
// unsigned
|
|
||||||
if (maxValue <= 255)
|
|
||||||
DataType.ARRAY_UB
|
|
||||||
else
|
|
||||||
DataType.ARRAY_UW
|
|
||||||
} else {
|
|
||||||
// signed
|
|
||||||
if (maxValue <= 127)
|
|
||||||
DataType.ARRAY_B
|
|
||||||
else
|
|
||||||
DataType.ARRAY_W
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(arrayDt!=litval.type) {
|
|
||||||
return LiteralValue(arrayDt, arrayvalue = litval.arrayvalue, position = litval.position)
|
|
||||||
}
|
|
||||||
return litval
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(assignment: Assignment): IStatement {
|
|
||||||
super.visit(assignment)
|
super.visit(assignment)
|
||||||
val lv = assignment.value as? LiteralValue
|
val lv = assignment.value as? NumericLiteralValue
|
||||||
if(lv!=null) {
|
if(lv!=null) {
|
||||||
// see if we can promote/convert a literal value to the required datatype
|
// see if we can promote/convert a literal value to the required datatype
|
||||||
when(assignment.target.inferType(program, assignment)) {
|
val idt = assignment.target.inferType(program, assignment)
|
||||||
|
if(!idt.isKnown)
|
||||||
|
return assignment
|
||||||
|
when(idt.typeOrElse(DataType.STRUCT)) {
|
||||||
DataType.UWORD -> {
|
DataType.UWORD -> {
|
||||||
// we can convert to UWORD: any UBYTE, BYTE/WORD that are >=0, FLOAT that's an integer 0..65535,
|
// we can convert to UWORD: any UBYTE, BYTE/WORD that are >=0, FLOAT that's an integer 0..65535,
|
||||||
if(lv.type== DataType.UBYTE)
|
if(lv.type== DataType.UBYTE)
|
||||||
assignment.value = LiteralValue(DataType.UWORD, wordvalue = lv.asIntegerValue, position = lv.position)
|
assignment.value = NumericLiteralValue(DataType.UWORD, lv.number.toInt(), lv.position)
|
||||||
else if(lv.type== DataType.BYTE && lv.bytevalue!!>=0)
|
else if(lv.type== DataType.BYTE && lv.number.toInt()>=0)
|
||||||
assignment.value = LiteralValue(DataType.UWORD, wordvalue = lv.asIntegerValue, position = lv.position)
|
assignment.value = NumericLiteralValue(DataType.UWORD, lv.number.toInt(), lv.position)
|
||||||
else if(lv.type== DataType.WORD && lv.wordvalue!!>=0)
|
else if(lv.type== DataType.WORD && lv.number.toInt()>=0)
|
||||||
assignment.value = LiteralValue(DataType.UWORD, wordvalue = lv.asIntegerValue, position = lv.position)
|
assignment.value = NumericLiteralValue(DataType.UWORD, lv.number.toInt(), lv.position)
|
||||||
else if(lv.type== DataType.FLOAT) {
|
else if(lv.type== DataType.FLOAT) {
|
||||||
val d = lv.floatvalue!!
|
val d = lv.number.toDouble()
|
||||||
if(floor(d)==d && d>=0 && d<=65535)
|
if(floor(d)==d && d>=0 && d<=65535)
|
||||||
assignment.value = LiteralValue(DataType.UWORD, wordvalue = floor(d).toInt(), position = lv.position)
|
assignment.value = NumericLiteralValue(DataType.UWORD, floor(d).toInt(), lv.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.UBYTE -> {
|
DataType.UBYTE -> {
|
||||||
// we can convert to UBYTE: UWORD <=255, BYTE >=0, FLOAT that's an integer 0..255,
|
// we can convert to UBYTE: UWORD <=255, BYTE >=0, FLOAT that's an integer 0..255,
|
||||||
if(lv.type== DataType.UWORD && lv.wordvalue!! <= 255)
|
if(lv.type== DataType.UWORD && lv.number.toInt() <= 255)
|
||||||
assignment.value = LiteralValue(DataType.UBYTE, lv.wordvalue.toShort(), position = lv.position)
|
assignment.value = NumericLiteralValue(DataType.UBYTE, lv.number.toShort(), lv.position)
|
||||||
else if(lv.type== DataType.BYTE && lv.bytevalue!! >=0)
|
else if(lv.type== DataType.BYTE && lv.number.toInt() >=0)
|
||||||
assignment.value = LiteralValue(DataType.UBYTE, lv.bytevalue.toShort(), position = lv.position)
|
assignment.value = NumericLiteralValue(DataType.UBYTE, lv.number.toShort(), lv.position)
|
||||||
else if(lv.type== DataType.FLOAT) {
|
else if(lv.type== DataType.FLOAT) {
|
||||||
val d = lv.floatvalue!!
|
val d = lv.number.toDouble()
|
||||||
if(floor(d)==d && d >=0 && d<=255)
|
if(floor(d)==d && d >=0 && d<=255)
|
||||||
assignment.value = LiteralValue(DataType.UBYTE, floor(d).toShort(), position = lv.position)
|
assignment.value = NumericLiteralValue(DataType.UBYTE, floor(d).toShort(), lv.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.BYTE -> {
|
DataType.BYTE -> {
|
||||||
// we can convert to BYTE: UWORD/UBYTE <= 127, FLOAT that's an integer 0..127
|
// we can convert to BYTE: UWORD/UBYTE <= 127, FLOAT that's an integer 0..127
|
||||||
if(lv.type== DataType.UWORD && lv.wordvalue!! <= 127)
|
if(lv.type== DataType.UWORD && lv.number.toInt() <= 127)
|
||||||
assignment.value = LiteralValue(DataType.BYTE, lv.wordvalue.toShort(), position = lv.position)
|
assignment.value = NumericLiteralValue(DataType.BYTE, lv.number.toShort(), lv.position)
|
||||||
else if(lv.type== DataType.UBYTE && lv.bytevalue!! <= 127)
|
else if(lv.type== DataType.UBYTE && lv.number.toInt() <= 127)
|
||||||
assignment.value = LiteralValue(DataType.BYTE, lv.bytevalue, position = lv.position)
|
assignment.value = NumericLiteralValue(DataType.BYTE, lv.number.toShort(), lv.position)
|
||||||
else if(lv.type== DataType.FLOAT) {
|
else if(lv.type== DataType.FLOAT) {
|
||||||
val d = lv.floatvalue!!
|
val d = lv.number.toDouble()
|
||||||
if(floor(d)==d && d>=0 && d<=127)
|
if(floor(d)==d && d>=0 && d<=127)
|
||||||
assignment.value = LiteralValue(DataType.BYTE, floor(d).toShort(), position = lv.position)
|
assignment.value = NumericLiteralValue(DataType.BYTE, floor(d).toShort(), lv.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.WORD -> {
|
DataType.WORD -> {
|
||||||
// we can convert to WORD: any UBYTE/BYTE, UWORD <= 32767, FLOAT that's an integer -32768..32767,
|
// we can convert to WORD: any UBYTE/BYTE, UWORD <= 32767, FLOAT that's an integer -32768..32767,
|
||||||
if(lv.type== DataType.UBYTE || lv.type== DataType.BYTE)
|
if(lv.type== DataType.UBYTE || lv.type== DataType.BYTE)
|
||||||
assignment.value = LiteralValue(DataType.WORD, wordvalue = lv.bytevalue!!.toInt(), position = lv.position)
|
assignment.value = NumericLiteralValue(DataType.WORD, lv.number.toInt(), lv.position)
|
||||||
else if(lv.type== DataType.UWORD && lv.wordvalue!! <= 32767)
|
else if(lv.type== DataType.UWORD && lv.number.toInt() <= 32767)
|
||||||
assignment.value = LiteralValue(DataType.WORD, wordvalue = lv.wordvalue, position = lv.position)
|
assignment.value = NumericLiteralValue(DataType.WORD, lv.number.toInt(), lv.position)
|
||||||
else if(lv.type== DataType.FLOAT) {
|
else if(lv.type== DataType.FLOAT) {
|
||||||
val d = lv.floatvalue!!
|
val d = lv.number.toDouble()
|
||||||
if(floor(d)==d && d>=-32768 && d<=32767)
|
if(floor(d)==d && d>=-32768 && d<=32767)
|
||||||
assignment.value = LiteralValue(DataType.BYTE, floor(d).toShort(), position = lv.position)
|
assignment.value = NumericLiteralValue(DataType.BYTE, floor(d).toShort(), lv.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
if(lv.isNumeric)
|
assignment.value = NumericLiteralValue(DataType.FLOAT, lv.number.toDouble(), lv.position)
|
||||||
assignment.value = LiteralValue(DataType.FLOAT, floatvalue = lv.asNumericValue?.toDouble(), position = lv.position)
|
|
||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
package prog8.optimizer
|
package prog8.optimizer
|
||||||
|
|
||||||
import prog8.ast.*
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.AstException
|
import prog8.ast.base.AstException
|
||||||
import prog8.ast.statements.NopStatement
|
|
||||||
import prog8.parser.ParsingFailedError
|
import prog8.parser.ParsingFailedError
|
||||||
|
|
||||||
|
|
||||||
@ -28,17 +27,9 @@ internal fun Program.constantFold() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal fun Program.optimizeStatements(optimizeInlining: Boolean): Int {
|
internal fun Program.optimizeStatements(): Int {
|
||||||
val optimizer = StatementOptimizer(this, optimizeInlining)
|
val optimizer = StatementOptimizer(this)
|
||||||
optimizer.visit(this)
|
optimizer.visit(this)
|
||||||
for(scope in optimizer.scopesToFlatten.reversed()) {
|
|
||||||
val namescope = scope.parent as INameScope
|
|
||||||
val idx = namescope.statements.indexOf(scope as IStatement)
|
|
||||||
if(idx>=0) {
|
|
||||||
namescope.statements[idx] = NopStatement.insteadOf(namescope.statements[idx])
|
|
||||||
namescope.statements.addAll(idx, scope.statements)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
modules.forEach { it.linkParents(this.namespace) } // re-link in final configuration
|
modules.forEach { it.linkParents(this.namespace) } // re-link in final configuration
|
||||||
|
|
||||||
return optimizer.optimizationsDone
|
return optimizer.optimizationsDone
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
package prog8.optimizer
|
package prog8.optimizer
|
||||||
|
|
||||||
import prog8.ast.*
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.AstException
|
import prog8.ast.base.*
|
||||||
import prog8.ast.base.DataType
|
|
||||||
import prog8.ast.base.IntegerDatatypes
|
|
||||||
import prog8.ast.base.NumericDatatypes
|
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.processing.IAstModifyingVisitor
|
import prog8.ast.processing.IAstModifyingVisitor
|
||||||
import prog8.ast.statements.Assignment
|
import prog8.ast.statements.Assignment
|
||||||
|
import prog8.ast.statements.Statement
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.log2
|
import kotlin.math.log2
|
||||||
|
import kotlin.math.pow
|
||||||
|
|
||||||
/*
|
/*
|
||||||
todo advanced expression optimization: common (sub) expression elimination (turn common expressions into single subroutine call + introduce variable to hold it)
|
todo add more expression optimizations
|
||||||
|
|
||||||
Also see https://egorbo.com/peephole-optimizations.html
|
Also see https://egorbo.com/peephole-optimizations.html
|
||||||
|
|
||||||
@ -21,13 +20,13 @@ import kotlin.math.log2
|
|||||||
internal class SimplifyExpressions(private val program: Program) : IAstModifyingVisitor {
|
internal class SimplifyExpressions(private val program: Program) : IAstModifyingVisitor {
|
||||||
var optimizationsDone: Int = 0
|
var optimizationsDone: Int = 0
|
||||||
|
|
||||||
override fun visit(assignment: Assignment): IStatement {
|
override fun visit(assignment: Assignment): Statement {
|
||||||
if (assignment.aug_op != null)
|
if (assignment.aug_op != null)
|
||||||
throw AstException("augmented assignments should have been converted to normal assignments before this optimizer")
|
throw AstException("augmented assignments should have been converted to normal assignments before this optimizer")
|
||||||
return super.visit(assignment)
|
return super.visit(assignment)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(memread: DirectMemoryRead): IExpression {
|
override fun visit(memread: DirectMemoryRead): Expression {
|
||||||
// @( &thing ) --> thing
|
// @( &thing ) --> thing
|
||||||
val addrOf = memread.addressExpression as? AddressOf
|
val addrOf = memread.addressExpression as? AddressOf
|
||||||
if(addrOf!=null)
|
if(addrOf!=null)
|
||||||
@ -35,14 +34,14 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
|||||||
return super.visit(memread)
|
return super.visit(memread)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(typecast: TypecastExpression): IExpression {
|
override fun visit(typecast: TypecastExpression): Expression {
|
||||||
var tc = typecast
|
var tc = typecast
|
||||||
|
|
||||||
// try to statically convert a literal value into one of the desired type
|
// try to statically convert a literal value into one of the desired type
|
||||||
val literal = tc.expression as? LiteralValue
|
val literal = tc.expression as? NumericLiteralValue
|
||||||
if(literal!=null) {
|
if(literal!=null) {
|
||||||
val newLiteral = literal.cast(tc.type)
|
val newLiteral = literal.cast(tc.type)
|
||||||
if(newLiteral!=null && newLiteral!==literal) {
|
if(newLiteral!==literal) {
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
return newLiteral
|
return newLiteral
|
||||||
}
|
}
|
||||||
@ -81,7 +80,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(expr: PrefixExpression): IExpression {
|
override fun visit(expr: PrefixExpression): Expression {
|
||||||
if (expr.operator == "+") {
|
if (expr.operator == "+") {
|
||||||
// +X --> X
|
// +X --> X
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
@ -128,16 +127,21 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
|||||||
return super.visit(expr)
|
return super.visit(expr)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(expr: BinaryExpression): IExpression {
|
override fun visit(expr: BinaryExpression): Expression {
|
||||||
super.visit(expr)
|
super.visit(expr)
|
||||||
val leftVal = expr.left.constValue(program)
|
val leftVal = expr.left.constValue(program)
|
||||||
val rightVal = expr.right.constValue(program)
|
val rightVal = expr.right.constValue(program)
|
||||||
val constTrue = LiteralValue.fromBoolean(true, expr.position)
|
val constTrue = NumericLiteralValue.fromBoolean(true, expr.position)
|
||||||
val constFalse = LiteralValue.fromBoolean(false, expr.position)
|
val constFalse = NumericLiteralValue.fromBoolean(false, expr.position)
|
||||||
|
|
||||||
val leftDt = expr.left.inferType(program)
|
val leftIDt = expr.left.inferType(program)
|
||||||
val rightDt = expr.right.inferType(program)
|
val rightIDt = expr.right.inferType(program)
|
||||||
if (leftDt != null && rightDt != null && leftDt != rightDt) {
|
if(!leftIDt.isKnown || !rightIDt.isKnown)
|
||||||
|
throw FatalAstException("can't determine datatype of both expression operands $expr")
|
||||||
|
|
||||||
|
val leftDt = leftIDt.typeOrElse(DataType.STRUCT)
|
||||||
|
val rightDt = rightIDt.typeOrElse(DataType.STRUCT)
|
||||||
|
if (leftDt != rightDt) {
|
||||||
// try to convert a datatype into the other (where ddd
|
// try to convert a datatype into the other (where ddd
|
||||||
if (adjustDatatypes(expr, leftVal, leftDt, rightVal, rightDt)) {
|
if (adjustDatatypes(expr, leftVal, leftDt, rightVal, rightDt)) {
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
@ -174,10 +178,10 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
|||||||
|
|
||||||
// X + (-value) --> X - value
|
// X + (-value) --> X - value
|
||||||
if (expr.operator == "+" && rightVal != null) {
|
if (expr.operator == "+" && rightVal != null) {
|
||||||
val rv = rightVal.asNumericValue?.toDouble()
|
val rv = rightVal.number.toDouble()
|
||||||
if (rv != null && rv < 0.0) {
|
if (rv < 0.0) {
|
||||||
expr.operator = "-"
|
expr.operator = "-"
|
||||||
expr.right = LiteralValue.fromNumber(-rv, rightVal.type, rightVal.position)
|
expr.right = NumericLiteralValue(rightVal.type, -rv, rightVal.position)
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
return expr
|
return expr
|
||||||
}
|
}
|
||||||
@ -185,10 +189,10 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
|||||||
|
|
||||||
// (-value) + X --> X - value
|
// (-value) + X --> X - value
|
||||||
if (expr.operator == "+" && leftVal != null) {
|
if (expr.operator == "+" && leftVal != null) {
|
||||||
val lv = leftVal.asNumericValue?.toDouble()
|
val lv = leftVal.number.toDouble()
|
||||||
if (lv != null && lv < 0.0) {
|
if (lv < 0.0) {
|
||||||
expr.operator = "-"
|
expr.operator = "-"
|
||||||
expr.right = LiteralValue.fromNumber(-lv, leftVal.type, leftVal.position)
|
expr.right = NumericLiteralValue(leftVal.type, -lv, leftVal.position)
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
return expr
|
return expr
|
||||||
}
|
}
|
||||||
@ -204,10 +208,10 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
|||||||
|
|
||||||
// X - (-value) --> X + value
|
// X - (-value) --> X + value
|
||||||
if (expr.operator == "-" && rightVal != null) {
|
if (expr.operator == "-" && rightVal != null) {
|
||||||
val rv = rightVal.asNumericValue?.toDouble()
|
val rv = rightVal.number.toDouble()
|
||||||
if (rv != null && rv < 0.0) {
|
if (rv < 0.0) {
|
||||||
expr.operator = "+"
|
expr.operator = "+"
|
||||||
expr.right = LiteralValue.fromNumber(-rv, rightVal.type, rightVal.position)
|
expr.right = NumericLiteralValue(rightVal.type, -rv, rightVal.position)
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
return expr
|
return expr
|
||||||
}
|
}
|
||||||
@ -225,7 +229,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
|||||||
val x = expr.right
|
val x = expr.right
|
||||||
val y = determineY(x, leftBinExpr)
|
val y = determineY(x, leftBinExpr)
|
||||||
if(y!=null) {
|
if(y!=null) {
|
||||||
val yPlus1 = BinaryExpression(y, "+", LiteralValue.fromNumber(1, leftDt!!, y.position), y.position)
|
val yPlus1 = BinaryExpression(y, "+", NumericLiteralValue(leftDt, 1, y.position), y.position)
|
||||||
return BinaryExpression(x, "*", yPlus1, x.position)
|
return BinaryExpression(x, "*", yPlus1, x.position)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -234,7 +238,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
|||||||
val x = expr.right
|
val x = expr.right
|
||||||
val y = determineY(x, leftBinExpr)
|
val y = determineY(x, leftBinExpr)
|
||||||
if(y!=null) {
|
if(y!=null) {
|
||||||
val yMinus1 = BinaryExpression(y, "-", LiteralValue.fromNumber(1, leftDt!!, y.position), y.position)
|
val yMinus1 = BinaryExpression(y, "-", NumericLiteralValue(leftDt, 1, y.position), y.position)
|
||||||
return BinaryExpression(x, "*", yMinus1, x.position)
|
return BinaryExpression(x, "*", yMinus1, x.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -246,7 +250,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
|||||||
val x = expr.left
|
val x = expr.left
|
||||||
val y = determineY(x, rightBinExpr)
|
val y = determineY(x, rightBinExpr)
|
||||||
if(y!=null) {
|
if(y!=null) {
|
||||||
val yPlus1 = BinaryExpression(y, "+", LiteralValue.optimalInteger(1, y.position), y.position)
|
val yPlus1 = BinaryExpression(y, "+", NumericLiteralValue.optimalInteger(1, y.position), y.position)
|
||||||
return BinaryExpression(x, "*", yPlus1, x.position)
|
return BinaryExpression(x, "*", yPlus1, x.position)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -255,7 +259,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
|||||||
val x = expr.left
|
val x = expr.left
|
||||||
val y = determineY(x, rightBinExpr)
|
val y = determineY(x, rightBinExpr)
|
||||||
if(y!=null) {
|
if(y!=null) {
|
||||||
val oneMinusY = BinaryExpression(LiteralValue.optimalInteger(1, y.position), "-", y, y.position)
|
val oneMinusY = BinaryExpression(NumericLiteralValue.optimalInteger(1, y.position), "-", y, y.position)
|
||||||
return BinaryExpression(x, "*", oneMinusY, x.position)
|
return BinaryExpression(x, "*", oneMinusY, x.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -337,11 +341,13 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
|||||||
"-" -> return optimizeSub(expr, leftVal, rightVal)
|
"-" -> return optimizeSub(expr, leftVal, rightVal)
|
||||||
"**" -> return optimizePower(expr, leftVal, rightVal)
|
"**" -> return optimizePower(expr, leftVal, rightVal)
|
||||||
"%" -> return optimizeRemainder(expr, leftVal, rightVal)
|
"%" -> return optimizeRemainder(expr, leftVal, rightVal)
|
||||||
|
">>" -> return optimizeShiftRight(expr, rightVal)
|
||||||
|
"<<" -> return optimizeShiftLeft(expr, rightVal)
|
||||||
}
|
}
|
||||||
return expr
|
return expr
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun determineY(x: IExpression, subBinExpr: BinaryExpression): IExpression? {
|
private fun determineY(x: Expression, subBinExpr: BinaryExpression): Expression? {
|
||||||
return when {
|
return when {
|
||||||
subBinExpr.left isSameAs x -> subBinExpr.right
|
subBinExpr.left isSameAs x -> subBinExpr.right
|
||||||
subBinExpr.right isSameAs x -> subBinExpr.left
|
subBinExpr.right isSameAs x -> subBinExpr.left
|
||||||
@ -350,58 +356,58 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun adjustDatatypes(expr: BinaryExpression,
|
private fun adjustDatatypes(expr: BinaryExpression,
|
||||||
leftConstVal: LiteralValue?, leftDt: DataType,
|
leftConstVal: NumericLiteralValue?, leftDt: DataType,
|
||||||
rightConstVal: LiteralValue?, rightDt: DataType): Boolean {
|
rightConstVal: NumericLiteralValue?, rightDt: DataType): Boolean {
|
||||||
|
|
||||||
fun adjust(value: LiteralValue, targetDt: DataType): Pair<Boolean, LiteralValue>{
|
fun adjust(value: NumericLiteralValue, targetDt: DataType): Pair<Boolean, NumericLiteralValue>{
|
||||||
if(value.type==targetDt)
|
if(value.type==targetDt)
|
||||||
return Pair(false, value)
|
return Pair(false, value)
|
||||||
when(value.type) {
|
when(value.type) {
|
||||||
DataType.UBYTE -> {
|
DataType.UBYTE -> {
|
||||||
if (targetDt == DataType.BYTE) {
|
if (targetDt == DataType.BYTE) {
|
||||||
if(value.bytevalue!! < 127)
|
if(value.number.toInt() < 127)
|
||||||
return Pair(true, LiteralValue(targetDt, value.bytevalue, position = value.position))
|
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
|
||||||
}
|
}
|
||||||
else if (targetDt == DataType.UWORD || targetDt == DataType.WORD)
|
else if (targetDt == DataType.UWORD || targetDt == DataType.WORD)
|
||||||
return Pair(true, LiteralValue(targetDt, wordvalue = value.bytevalue!!.toInt(), position = value.position))
|
return Pair(true, NumericLiteralValue(targetDt, value.number.toInt(), value.position))
|
||||||
}
|
}
|
||||||
DataType.BYTE -> {
|
DataType.BYTE -> {
|
||||||
if (targetDt == DataType.UBYTE) {
|
if (targetDt == DataType.UBYTE) {
|
||||||
if(value.bytevalue!! >= 0)
|
if(value.number.toInt() >= 0)
|
||||||
return Pair(true, LiteralValue(targetDt, value.bytevalue, position = value.position))
|
return Pair(true, NumericLiteralValue(targetDt, value.number.toInt(), value.position))
|
||||||
}
|
}
|
||||||
else if (targetDt == DataType.UWORD) {
|
else if (targetDt == DataType.UWORD) {
|
||||||
if(value.bytevalue!! >= 0)
|
if(value.number.toInt() >= 0)
|
||||||
return Pair(true, LiteralValue(targetDt, wordvalue = value.bytevalue.toInt(), position = value.position))
|
return Pair(true, NumericLiteralValue(targetDt, value.number.toInt(), value.position))
|
||||||
}
|
}
|
||||||
else if (targetDt == DataType.WORD) return Pair(true, LiteralValue(targetDt, wordvalue = value.bytevalue!!.toInt(), position = value.position))
|
else if (targetDt == DataType.WORD) return Pair(true, NumericLiteralValue(targetDt, value.number.toInt(), value.position))
|
||||||
}
|
}
|
||||||
DataType.UWORD -> {
|
DataType.UWORD -> {
|
||||||
if (targetDt == DataType.UBYTE) {
|
if (targetDt == DataType.UBYTE) {
|
||||||
if(value.wordvalue!! <= 255)
|
if(value.number.toInt() <= 255)
|
||||||
return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position = value.position))
|
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
|
||||||
}
|
}
|
||||||
else if (targetDt == DataType.BYTE) {
|
else if (targetDt == DataType.BYTE) {
|
||||||
if(value.wordvalue!! <= 127)
|
if(value.number.toInt() <= 127)
|
||||||
return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position = value.position))
|
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
|
||||||
}
|
}
|
||||||
else if (targetDt == DataType.WORD) {
|
else if (targetDt == DataType.WORD) {
|
||||||
if(value.wordvalue!! <= 32767)
|
if(value.number.toInt() <= 32767)
|
||||||
return Pair(true, LiteralValue(targetDt, wordvalue = value.wordvalue, position = value.position))
|
return Pair(true, NumericLiteralValue(targetDt, value.number.toInt(), value.position))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.WORD -> {
|
DataType.WORD -> {
|
||||||
if (targetDt == DataType.UBYTE) {
|
if (targetDt == DataType.UBYTE) {
|
||||||
if(value.wordvalue!! in 0..255)
|
if(value.number.toInt() in 0..255)
|
||||||
return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position = value.position))
|
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
|
||||||
}
|
}
|
||||||
else if (targetDt == DataType.BYTE) {
|
else if (targetDt == DataType.BYTE) {
|
||||||
if(value.wordvalue!! in -128..127)
|
if(value.number.toInt() in -128..127)
|
||||||
return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position = value.position))
|
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
|
||||||
}
|
}
|
||||||
else if (targetDt == DataType.UWORD) {
|
else if (targetDt == DataType.UWORD) {
|
||||||
if(value.wordvalue!! >= 0)
|
if(value.number.toInt() >= 0)
|
||||||
return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position = value.position))
|
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
@ -434,9 +440,9 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class ReorderedAssociativeBinaryExpr(val expr: BinaryExpression, val leftVal: LiteralValue?, val rightVal: LiteralValue?)
|
private data class ReorderedAssociativeBinaryExpr(val expr: BinaryExpression, val leftVal: NumericLiteralValue?, val rightVal: NumericLiteralValue?)
|
||||||
|
|
||||||
private fun reorderAssociative(expr: BinaryExpression, leftVal: LiteralValue?): ReorderedAssociativeBinaryExpr {
|
private fun reorderAssociative(expr: BinaryExpression, leftVal: NumericLiteralValue?): ReorderedAssociativeBinaryExpr {
|
||||||
if(expr.operator in associativeOperators && leftVal!=null) {
|
if(expr.operator in associativeOperators && leftVal!=null) {
|
||||||
// swap left and right so that right is always the constant
|
// swap left and right so that right is always the constant
|
||||||
val tmp = expr.left
|
val tmp = expr.left
|
||||||
@ -448,15 +454,15 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
|||||||
return ReorderedAssociativeBinaryExpr(expr, leftVal, expr.right.constValue(program))
|
return ReorderedAssociativeBinaryExpr(expr, leftVal, expr.right.constValue(program))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun optimizeAdd(pexpr: BinaryExpression, pleftVal: LiteralValue?, prightVal: LiteralValue?): IExpression {
|
private fun optimizeAdd(pexpr: BinaryExpression, pleftVal: NumericLiteralValue?, prightVal: NumericLiteralValue?): Expression {
|
||||||
if(pleftVal==null && prightVal==null)
|
if(pleftVal==null && prightVal==null)
|
||||||
return pexpr
|
return pexpr
|
||||||
|
|
||||||
val (expr, _, rightVal) = reorderAssociative(pexpr, pleftVal)
|
val (expr, _, rightVal) = reorderAssociative(pexpr, pleftVal)
|
||||||
if(rightVal!=null) {
|
if(rightVal!=null) {
|
||||||
// right value is a constant, see if we can optimize
|
// right value is a constant, see if we can optimize
|
||||||
val rightConst: LiteralValue = rightVal
|
val rightConst: NumericLiteralValue = rightVal
|
||||||
when(rightConst.asNumericValue?.toDouble()) {
|
when(rightConst.number.toDouble()) {
|
||||||
0.0 -> {
|
0.0 -> {
|
||||||
// left
|
// left
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
@ -469,14 +475,14 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
|||||||
return expr
|
return expr
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun optimizeSub(expr: BinaryExpression, leftVal: LiteralValue?, rightVal: LiteralValue?): IExpression {
|
private fun optimizeSub(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression {
|
||||||
if(leftVal==null && rightVal==null)
|
if(leftVal==null && rightVal==null)
|
||||||
return expr
|
return expr
|
||||||
|
|
||||||
if(rightVal!=null) {
|
if(rightVal!=null) {
|
||||||
// right value is a constant, see if we can optimize
|
// right value is a constant, see if we can optimize
|
||||||
val rightConst: LiteralValue = rightVal
|
val rightConst: NumericLiteralValue = rightVal
|
||||||
when(rightConst.asNumericValue?.toDouble()) {
|
when(rightConst.number.toDouble()) {
|
||||||
0.0 -> {
|
0.0 -> {
|
||||||
// left
|
// left
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
@ -486,7 +492,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
|||||||
}
|
}
|
||||||
if(leftVal!=null) {
|
if(leftVal!=null) {
|
||||||
// left value is a constant, see if we can optimize
|
// left value is a constant, see if we can optimize
|
||||||
when(leftVal.asNumericValue?.toDouble()) {
|
when(leftVal.number.toDouble()) {
|
||||||
0.0 -> {
|
0.0 -> {
|
||||||
// -right
|
// -right
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
@ -498,38 +504,38 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
|||||||
return expr
|
return expr
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun optimizePower(expr: BinaryExpression, leftVal: LiteralValue?, rightVal: LiteralValue?): IExpression {
|
private fun optimizePower(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression {
|
||||||
if(leftVal==null && rightVal==null)
|
if(leftVal==null && rightVal==null)
|
||||||
return expr
|
return expr
|
||||||
|
|
||||||
if(rightVal!=null) {
|
if(rightVal!=null) {
|
||||||
// right value is a constant, see if we can optimize
|
// right value is a constant, see if we can optimize
|
||||||
val rightConst: LiteralValue = rightVal
|
val rightConst: NumericLiteralValue = rightVal
|
||||||
when(rightConst.asNumericValue?.toDouble()) {
|
when(rightConst.number.toDouble()) {
|
||||||
-3.0 -> {
|
-3.0 -> {
|
||||||
// -1/(left*left*left)
|
// -1/(left*left*left)
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
return BinaryExpression(LiteralValue(DataType.FLOAT, floatvalue = -1.0, position = expr.position), "/",
|
return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/",
|
||||||
BinaryExpression(expr.left, "*", BinaryExpression(expr.left, "*", expr.left, expr.position), expr.position),
|
BinaryExpression(expr.left, "*", BinaryExpression(expr.left, "*", expr.left, expr.position), expr.position),
|
||||||
expr.position)
|
expr.position)
|
||||||
}
|
}
|
||||||
-2.0 -> {
|
-2.0 -> {
|
||||||
// -1/(left*left)
|
// -1/(left*left)
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
return BinaryExpression(LiteralValue(DataType.FLOAT, floatvalue = -1.0, position = expr.position), "/",
|
return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/",
|
||||||
BinaryExpression(expr.left, "*", expr.left, expr.position),
|
BinaryExpression(expr.left, "*", expr.left, expr.position),
|
||||||
expr.position)
|
expr.position)
|
||||||
}
|
}
|
||||||
-1.0 -> {
|
-1.0 -> {
|
||||||
// -1/left
|
// -1/left
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
return BinaryExpression(LiteralValue(DataType.FLOAT, floatvalue = -1.0, position = expr.position), "/",
|
return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/",
|
||||||
expr.left, expr.position)
|
expr.left, expr.position)
|
||||||
}
|
}
|
||||||
0.0 -> {
|
0.0 -> {
|
||||||
// 1
|
// 1
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
return LiteralValue.fromNumber(1, rightConst.type, expr.position)
|
return NumericLiteralValue(rightConst.type, 1, expr.position)
|
||||||
}
|
}
|
||||||
0.5 -> {
|
0.5 -> {
|
||||||
// sqrt(left)
|
// sqrt(left)
|
||||||
@ -555,21 +561,21 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
|||||||
}
|
}
|
||||||
if(leftVal!=null) {
|
if(leftVal!=null) {
|
||||||
// left value is a constant, see if we can optimize
|
// left value is a constant, see if we can optimize
|
||||||
when(leftVal.asNumericValue?.toDouble()) {
|
when(leftVal.number.toDouble()) {
|
||||||
-1.0 -> {
|
-1.0 -> {
|
||||||
// -1
|
// -1
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
return LiteralValue(DataType.FLOAT, floatvalue = -1.0, position = expr.position)
|
return NumericLiteralValue(DataType.FLOAT, -1.0, expr.position)
|
||||||
}
|
}
|
||||||
0.0 -> {
|
0.0 -> {
|
||||||
// 0
|
// 0
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
return LiteralValue.fromNumber(0, leftVal.type, expr.position)
|
return NumericLiteralValue(leftVal.type, 0, expr.position)
|
||||||
}
|
}
|
||||||
1.0 -> {
|
1.0 -> {
|
||||||
//1
|
//1
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
return LiteralValue.fromNumber(1, leftVal.type, expr.position)
|
return NumericLiteralValue(leftVal.type, 1, expr.position)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -578,22 +584,22 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
|||||||
return expr
|
return expr
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun optimizeRemainder(expr: BinaryExpression, leftVal: LiteralValue?, rightVal: LiteralValue?): IExpression {
|
private fun optimizeRemainder(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression {
|
||||||
if(leftVal==null && rightVal==null)
|
if(leftVal==null && rightVal==null)
|
||||||
return expr
|
return expr
|
||||||
|
|
||||||
// simplify assignments A = B <operator> C
|
// simplify assignments A = B <operator> C
|
||||||
|
|
||||||
val cv = rightVal?.asIntegerValue?.toDouble()
|
val cv = rightVal?.number?.toInt()?.toDouble()
|
||||||
when(expr.operator) {
|
when(expr.operator) {
|
||||||
"%" -> {
|
"%" -> {
|
||||||
if (cv == 1.0) {
|
if (cv == 1.0) {
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
return LiteralValue.fromNumber(0, expr.inferType(program)!!, expr.position)
|
return NumericLiteralValue(expr.inferType(program).typeOrElse(DataType.STRUCT), 0, expr.position)
|
||||||
} else if (cv == 2.0) {
|
} else if (cv == 2.0) {
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
expr.operator = "&"
|
expr.operator = "&"
|
||||||
expr.right = LiteralValue.optimalInteger(1, expr.position)
|
expr.right = NumericLiteralValue.optimalInteger(1, expr.position)
|
||||||
return expr
|
return expr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -602,17 +608,22 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun optimizeDivision(expr: BinaryExpression, leftVal: LiteralValue?, rightVal: LiteralValue?): IExpression {
|
private val powersOfTwo = (1 .. 16).map { (2.0).pow(it) }.toSet()
|
||||||
|
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
|
||||||
|
|
||||||
|
private fun optimizeDivision(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression {
|
||||||
if(leftVal==null && rightVal==null)
|
if(leftVal==null && rightVal==null)
|
||||||
return expr
|
return expr
|
||||||
|
|
||||||
// cannot shuffle assiciativity with division!
|
// cannot shuffle assiciativity with division!
|
||||||
|
|
||||||
if(rightVal!=null) {
|
if(rightVal!=null) {
|
||||||
// right value is a constant, see if we can optimize
|
// right value is a constant, see if we can optimize
|
||||||
val rightConst: LiteralValue = rightVal
|
val rightConst: NumericLiteralValue = rightVal
|
||||||
val cv = rightConst.asNumericValue?.toDouble()
|
val cv = rightConst.number.toDouble()
|
||||||
val leftDt = expr.left.inferType(program)
|
val leftIDt = expr.left.inferType(program)
|
||||||
|
if(!leftIDt.isKnown)
|
||||||
|
return expr
|
||||||
|
val leftDt = leftIDt.typeOrElse(DataType.STRUCT)
|
||||||
when(cv) {
|
when(cv) {
|
||||||
-1.0 -> {
|
-1.0 -> {
|
||||||
// '/' -> -left
|
// '/' -> -left
|
||||||
@ -628,45 +639,45 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
|||||||
return expr.left
|
return expr.left
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0, 256.0, 512.0, 1024.0, 2048.0, 4096.0, 8192.0, 16384.0, 32768.0, 65536.0 -> {
|
in powersOfTwo -> {
|
||||||
if(leftDt in IntegerDatatypes) {
|
if(leftDt in IntegerDatatypes) {
|
||||||
// divided by a power of two => shift right
|
// divided by a power of two => shift right
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
val numshifts = log2(cv).toInt()
|
val numshifts = log2(cv).toInt()
|
||||||
return BinaryExpression(expr.left, ">>", LiteralValue.optimalInteger(numshifts, expr.position), expr.position)
|
return BinaryExpression(expr.left, ">>", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
-2.0, -4.0, -8.0, -16.0, -32.0, -64.0, -128.0, -256.0, -512.0, -1024.0, -2048.0, -4096.0, -8192.0, -16384.0, -32768.0, -65536.0 -> {
|
in negativePowersOfTwo -> {
|
||||||
if(leftDt in IntegerDatatypes) {
|
if(leftDt in IntegerDatatypes) {
|
||||||
// divided by a negative power of two => negate, then shift right
|
// divided by a negative power of two => negate, then shift right
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
val numshifts = log2(-cv).toInt()
|
val numshifts = log2(-cv).toInt()
|
||||||
return BinaryExpression(PrefixExpression("-", expr.left, expr.position), ">>", LiteralValue.optimalInteger(numshifts, expr.position), expr.position)
|
return BinaryExpression(PrefixExpression("-", expr.left, expr.position), ">>", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (leftDt == DataType.UBYTE) {
|
if (leftDt == DataType.UBYTE) {
|
||||||
if(abs(rightConst.asNumericValue!!.toDouble()) >= 256.0) {
|
if(abs(rightConst.number.toDouble()) >= 256.0) {
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
return LiteralValue(DataType.UBYTE, 0, position = expr.position)
|
return NumericLiteralValue(DataType.UBYTE, 0, expr.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (leftDt == DataType.UWORD) {
|
else if (leftDt == DataType.UWORD) {
|
||||||
if(abs(rightConst.asNumericValue!!.toDouble()) >= 65536.0) {
|
if(abs(rightConst.number.toDouble()) >= 65536.0) {
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
return LiteralValue(DataType.UBYTE, 0, position = expr.position)
|
return NumericLiteralValue(DataType.UBYTE, 0, expr.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(leftVal!=null) {
|
if(leftVal!=null) {
|
||||||
// left value is a constant, see if we can optimize
|
// left value is a constant, see if we can optimize
|
||||||
when(leftVal.asNumericValue?.toDouble()) {
|
when(leftVal.number.toDouble()) {
|
||||||
0.0 -> {
|
0.0 -> {
|
||||||
// 0
|
// 0
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
return LiteralValue.fromNumber(0, leftVal.type, expr.position)
|
return NumericLiteralValue(leftVal.type, 0, expr.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -674,16 +685,16 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
|||||||
return expr
|
return expr
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun optimizeMultiplication(pexpr: BinaryExpression, pleftVal: LiteralValue?, prightVal: LiteralValue?): IExpression {
|
private fun optimizeMultiplication(pexpr: BinaryExpression, pleftVal: NumericLiteralValue?, prightVal: NumericLiteralValue?): Expression {
|
||||||
if(pleftVal==null && prightVal==null)
|
if(pleftVal==null && prightVal==null)
|
||||||
return pexpr
|
return pexpr
|
||||||
|
|
||||||
val (expr, _, rightVal) = reorderAssociative(pexpr, pleftVal)
|
val (expr, _, rightVal) = reorderAssociative(pexpr, pleftVal)
|
||||||
if(rightVal!=null) {
|
if(rightVal!=null) {
|
||||||
// right value is a constant, see if we can optimize
|
// right value is a constant, see if we can optimize
|
||||||
val leftValue: IExpression = expr.left
|
val leftValue: Expression = expr.left
|
||||||
val rightConst: LiteralValue = rightVal
|
val rightConst: NumericLiteralValue = rightVal
|
||||||
when(val cv = rightConst.asNumericValue?.toDouble()) {
|
when(val cv = rightConst.number.toDouble()) {
|
||||||
-1.0 -> {
|
-1.0 -> {
|
||||||
// -left
|
// -left
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
@ -692,27 +703,27 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
|||||||
0.0 -> {
|
0.0 -> {
|
||||||
// 0
|
// 0
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
return LiteralValue.fromNumber(0, rightConst.type, expr.position)
|
return NumericLiteralValue(rightConst.type, 0, expr.position)
|
||||||
}
|
}
|
||||||
1.0 -> {
|
1.0 -> {
|
||||||
// left
|
// left
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
return expr.left
|
return expr.left
|
||||||
}
|
}
|
||||||
2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0, 256.0, 512.0, 1024.0, 2048.0, 4096.0, 8192.0, 16384.0, 32768.0, 65536.0 -> {
|
in powersOfTwo -> {
|
||||||
if(leftValue.inferType(program) in IntegerDatatypes) {
|
if(leftValue.inferType(program).typeOrElse(DataType.STRUCT) in IntegerDatatypes) {
|
||||||
// times a power of two => shift left
|
// times a power of two => shift left
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
val numshifts = log2(cv).toInt()
|
val numshifts = log2(cv).toInt()
|
||||||
return BinaryExpression(expr.left, "<<", LiteralValue.optimalInteger(numshifts, expr.position), expr.position)
|
return BinaryExpression(expr.left, "<<", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
-2.0, -4.0, -8.0, -16.0, -32.0, -64.0, -128.0, -256.0, -512.0, -1024.0, -2048.0, -4096.0, -8192.0, -16384.0, -32768.0, -65536.0 -> {
|
in negativePowersOfTwo -> {
|
||||||
if(leftValue.inferType(program) in IntegerDatatypes) {
|
if(leftValue.inferType(program).typeOrElse(DataType.STRUCT) in IntegerDatatypes) {
|
||||||
// times a negative power of two => negate, then shift left
|
// times a negative power of two => negate, then shift left
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
val numshifts = log2(-cv).toInt()
|
val numshifts = log2(-cv).toInt()
|
||||||
return BinaryExpression(PrefixExpression("-", expr.left, expr.position), "<<", LiteralValue.optimalInteger(numshifts, expr.position), expr.position)
|
return BinaryExpression(PrefixExpression("-", expr.left, expr.position), "<<", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -721,4 +732,97 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
|||||||
|
|
||||||
return expr
|
return expr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun optimizeShiftLeft(expr: BinaryExpression, amountLv: NumericLiteralValue?): Expression {
|
||||||
|
if(amountLv==null)
|
||||||
|
return expr
|
||||||
|
|
||||||
|
val amount=amountLv.number.toInt()
|
||||||
|
if(amount==0) {
|
||||||
|
optimizationsDone++
|
||||||
|
return expr.left
|
||||||
|
}
|
||||||
|
val targetDt = expr.left.inferType(program).typeOrElse(DataType.STRUCT)
|
||||||
|
when(targetDt) {
|
||||||
|
DataType.UBYTE, DataType.BYTE -> {
|
||||||
|
if(amount>=8) {
|
||||||
|
optimizationsDone++
|
||||||
|
return NumericLiteralValue.optimalInteger(0, expr.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.UWORD, DataType.WORD -> {
|
||||||
|
if(amount>=16) {
|
||||||
|
optimizationsDone++
|
||||||
|
return NumericLiteralValue.optimalInteger(0, expr.position)
|
||||||
|
}
|
||||||
|
else if(amount>=8) {
|
||||||
|
optimizationsDone++
|
||||||
|
val lsb=TypecastExpression(expr.left, DataType.UBYTE, true, expr.position)
|
||||||
|
if(amount==8) {
|
||||||
|
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(NumericLiteralValue.optimalInteger(0, expr.position), lsb), expr.position)
|
||||||
|
}
|
||||||
|
val shifted = BinaryExpression(lsb, "<<", NumericLiteralValue.optimalInteger(amount-8, expr.position), expr.position)
|
||||||
|
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(NumericLiteralValue.optimalInteger(0, expr.position), shifted), expr.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
return expr
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun optimizeShiftRight(expr: BinaryExpression, amountLv: NumericLiteralValue?): Expression {
|
||||||
|
if(amountLv==null)
|
||||||
|
return expr
|
||||||
|
|
||||||
|
val amount=amountLv.number.toInt()
|
||||||
|
if(amount==0) {
|
||||||
|
optimizationsDone++
|
||||||
|
return expr.left
|
||||||
|
}
|
||||||
|
val targetDt = expr.left.inferType(program).typeOrElse(DataType.STRUCT)
|
||||||
|
when(targetDt) {
|
||||||
|
DataType.UBYTE -> {
|
||||||
|
if(amount>=8) {
|
||||||
|
optimizationsDone++
|
||||||
|
return NumericLiteralValue.optimalInteger(0, expr.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.BYTE -> {
|
||||||
|
if(amount>8) {
|
||||||
|
expr.right = NumericLiteralValue.optimalInteger(8, expr.right.position)
|
||||||
|
return expr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.UWORD -> {
|
||||||
|
if(amount>=16) {
|
||||||
|
optimizationsDone++
|
||||||
|
return NumericLiteralValue.optimalInteger(0, expr.position)
|
||||||
|
}
|
||||||
|
else if(amount>=8) {
|
||||||
|
optimizationsDone++
|
||||||
|
val msb=FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
|
||||||
|
if(amount==8)
|
||||||
|
return msb
|
||||||
|
return BinaryExpression(msb, ">>", NumericLiteralValue.optimalInteger(amount-8, expr.position), expr.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.WORD -> {
|
||||||
|
if(amount>16) {
|
||||||
|
expr.right = NumericLiteralValue.optimalInteger(16, expr.right.position)
|
||||||
|
return expr
|
||||||
|
} else if(amount>=8) {
|
||||||
|
optimizationsDone++
|
||||||
|
val msbAsByte = TypecastExpression(
|
||||||
|
FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position),
|
||||||
|
DataType.BYTE,
|
||||||
|
true, expr.position)
|
||||||
|
if(amount==8)
|
||||||
|
return msbAsByte
|
||||||
|
return BinaryExpression(msbAsByte, ">>", NumericLiteralValue.optimalInteger(amount-8, expr.position), expr.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
return expr
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,103 +1,43 @@
|
|||||||
package prog8.optimizer
|
package prog8.optimizer
|
||||||
|
|
||||||
import prog8.ast.*
|
import prog8.ast.INameScope
|
||||||
|
import prog8.ast.Module
|
||||||
|
import prog8.ast.Node
|
||||||
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.processing.IAstModifyingVisitor
|
import prog8.ast.processing.IAstModifyingVisitor
|
||||||
import prog8.ast.processing.IAstVisitor
|
import prog8.ast.processing.IAstVisitor
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.compiler.target.c64.Petscii
|
import prog8.compiler.target.c64.Petscii
|
||||||
|
import prog8.compiler.target.c64.codegen.AssemblyError
|
||||||
import prog8.functions.BuiltinFunctions
|
import prog8.functions.BuiltinFunctions
|
||||||
import kotlin.math.floor
|
import kotlin.math.floor
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
todo: subroutines with 1 or 2 byte args or 1 word arg can be converted to asm sub calling convention (args in registers)
|
TODO: remove unreachable code?
|
||||||
todo analyse for unreachable code and remove that (f.i. code after goto or return that has no label so can never be jumped to) + print warning about this
|
TODO: proper inlining of tiny subroutines (correctly renaming/relocating all variables in them and refs to those as well, or restrict to subs without variables?)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
internal class StatementOptimizer(private val program: Program, private val optimizeInlining: Boolean) : IAstModifyingVisitor {
|
internal class StatementOptimizer(private val program: Program) : IAstModifyingVisitor {
|
||||||
var optimizationsDone: Int = 0
|
var optimizationsDone: Int = 0
|
||||||
private set
|
private set
|
||||||
var scopesToFlatten = mutableListOf<INameScope>()
|
|
||||||
|
|
||||||
private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure }
|
private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure }
|
||||||
private val callgraph = CallGraph(program)
|
private val callgraph = CallGraph(program)
|
||||||
|
private val vardeclsToRemove = mutableListOf<VarDecl>()
|
||||||
companion object {
|
|
||||||
private var generatedLabelSequenceNumber = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(program: Program) {
|
override fun visit(program: Program) {
|
||||||
removeUnusedCode(callgraph)
|
removeUnusedCode(callgraph)
|
||||||
if(optimizeInlining) {
|
|
||||||
inlineSubroutines(callgraph)
|
|
||||||
}
|
|
||||||
super.visit(program)
|
super.visit(program)
|
||||||
}
|
|
||||||
|
|
||||||
private fun inlineSubroutines(callgraph: CallGraph) {
|
for(decl in vardeclsToRemove) {
|
||||||
val entrypoint = program.entrypoint()
|
decl.definingScope().remove(decl)
|
||||||
program.modules.forEach {
|
|
||||||
callgraph.forAllSubroutines(it) { sub ->
|
|
||||||
if(sub!==entrypoint && !sub.isAsmSubroutine) {
|
|
||||||
if (sub.statements.size <= 3 && !sub.expensiveToInline) {
|
|
||||||
sub.calledBy.toList().forEach { caller -> inlineSubroutine(sub, caller) }
|
|
||||||
} else if (sub.calledBy.size==1 && sub.statements.size < 50) {
|
|
||||||
inlineSubroutine(sub, sub.calledBy[0])
|
|
||||||
} else if(sub.calledBy.size<=3 && sub.statements.size < 10 && !sub.expensiveToInline) {
|
|
||||||
sub.calledBy.toList().forEach { caller -> inlineSubroutine(sub, caller) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun inlineSubroutine(sub: Subroutine, caller: Node) {
|
|
||||||
// if the sub is called multiple times from the isSameAs scope, we can't inline (would result in duplicate definitions)
|
|
||||||
// (unless we add a sequence number to all vars/labels and references to them in the inlined code, but I skip that for now)
|
|
||||||
val scope = caller.definingScope()
|
|
||||||
if(sub.calledBy.count { it.definingScope()===scope } > 1)
|
|
||||||
return
|
|
||||||
if(caller !is IFunctionCall || caller !is IStatement || sub.statements.any { it is Subroutine })
|
|
||||||
return
|
|
||||||
|
|
||||||
if(sub.parameters.isEmpty() && sub.returntypes.isEmpty()) {
|
|
||||||
// sub without params and without return value can be easily inlined
|
|
||||||
val parent = caller.parent as INameScope
|
|
||||||
val inlined = AnonymousScope(sub.statements.toMutableList(), caller.position)
|
|
||||||
parent.statements[parent.statements.indexOf(caller)] = inlined
|
|
||||||
// replace return statements in the inlined sub by a jump to the end of it
|
|
||||||
var haveNewEndLabel = false
|
|
||||||
var endLabelUsed = false
|
|
||||||
var endlabel = inlined.statements.last() as? Label
|
|
||||||
if(endlabel==null) {
|
|
||||||
endlabel = makeLabel("_prog8_auto_sub_end", inlined.statements.last().position)
|
|
||||||
endlabel.parent = inlined
|
|
||||||
haveNewEndLabel = true
|
|
||||||
}
|
|
||||||
val returns = inlined.statements.withIndex().filter { iv -> iv.value is Return }.map { iv -> Pair(iv.index, iv.value as Return)}
|
|
||||||
for(returnIdx in returns) {
|
|
||||||
val jump = Jump(null, IdentifierReference(listOf(endlabel.name), returnIdx.second.position), null, returnIdx.second.position)
|
|
||||||
inlined.statements[returnIdx.first] = jump
|
|
||||||
endLabelUsed = true
|
|
||||||
}
|
|
||||||
if(endLabelUsed && haveNewEndLabel)
|
|
||||||
inlined.statements.add(endlabel)
|
|
||||||
inlined.linkParents(caller.parent)
|
|
||||||
sub.calledBy.remove(caller) // if there are no callers left, the sub will be removed automatically later
|
|
||||||
optimizationsDone++
|
|
||||||
} else {
|
|
||||||
// TODO inline subroutine that has params or returnvalues or both
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun makeLabel(name: String, position: Position): Label {
|
|
||||||
generatedLabelSequenceNumber++
|
|
||||||
return Label("${name}_$generatedLabelSequenceNumber", position)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun removeUnusedCode(callgraph: CallGraph) {
|
private fun removeUnusedCode(callgraph: CallGraph) {
|
||||||
// remove all subroutines that aren't called, or are empty
|
// remove all subroutines that aren't called, or are empty
|
||||||
val removeSubroutines = mutableSetOf<Subroutine>()
|
val removeSubroutines = mutableSetOf<Subroutine>()
|
||||||
@ -137,7 +77,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(block: Block): IStatement {
|
override fun visit(block: Block): Statement {
|
||||||
if("force_output" !in block.options()) {
|
if("force_output" !in block.options()) {
|
||||||
if (block.containsNoCodeNorVars()) {
|
if (block.containsNoCodeNorVars()) {
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
@ -155,7 +95,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
|||||||
return super.visit(block)
|
return super.visit(block)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(subroutine: Subroutine): IStatement {
|
override fun visit(subroutine: Subroutine): Statement {
|
||||||
super.visit(subroutine)
|
super.visit(subroutine)
|
||||||
val forceOutput = "force_output" in subroutine.definingBlock().options()
|
val forceOutput = "force_output" in subroutine.definingBlock().options()
|
||||||
if(subroutine.asmAddress==null && !forceOutput) {
|
if(subroutine.asmAddress==null && !forceOutput) {
|
||||||
@ -171,18 +111,6 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
|||||||
linesToRemove.reversed().forEach{subroutine.statements.removeAt(it)}
|
linesToRemove.reversed().forEach{subroutine.statements.removeAt(it)}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(subroutine.canBeAsmSubroutine) {
|
|
||||||
optimizationsDone++
|
|
||||||
return subroutine.intoAsmSubroutine() // TODO this doesn't work yet due to parameter vardecl issue
|
|
||||||
|
|
||||||
// TODO fix parameter passing so this also works:
|
|
||||||
// asmsub aa(byte arg @ Y) -> clobbers() -> () {
|
|
||||||
// byte local = arg ; @todo fix 'undefined symbol arg' by some sort of alias name for the parameter
|
|
||||||
// A=44
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if(subroutine !in callgraph.usedSymbols && !forceOutput) {
|
if(subroutine !in callgraph.usedSymbols && !forceOutput) {
|
||||||
printWarning("removing unused subroutine '${subroutine.name}'", subroutine.position)
|
printWarning("removing unused subroutine '${subroutine.name}'", subroutine.position)
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
@ -192,11 +120,11 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
|||||||
return subroutine
|
return subroutine
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(decl: VarDecl): IStatement {
|
override fun visit(decl: VarDecl): Statement {
|
||||||
val forceOutput = "force_output" in decl.definingBlock().options()
|
val forceOutput = "force_output" in decl.definingBlock().options()
|
||||||
if(decl !in callgraph.usedSymbols && !forceOutput) {
|
if(decl !in callgraph.usedSymbols && !forceOutput) {
|
||||||
if(decl.type!=VarDeclType.CONST)
|
if(decl.type == VarDeclType.VAR)
|
||||||
printWarning("removing unused variable '${decl.name}'", decl.position)
|
printWarning("removing unused variable ${decl.type} '${decl.name}'", decl.position)
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
return NopStatement.insteadOf(decl)
|
return NopStatement.insteadOf(decl)
|
||||||
}
|
}
|
||||||
@ -204,13 +132,13 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
|||||||
return super.visit(decl)
|
return super.visit(decl)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun deduplicateAssignments(statements: List<IStatement>): MutableList<Int> {
|
private fun deduplicateAssignments(statements: List<Statement>): MutableList<Int> {
|
||||||
// removes 'duplicate' assignments that assign the isSameAs target
|
// removes 'duplicate' assignments that assign the isSameAs target
|
||||||
val linesToRemove = mutableListOf<Int>()
|
val linesToRemove = mutableListOf<Int>()
|
||||||
var previousAssignmentLine: Int? = null
|
var previousAssignmentLine: Int? = null
|
||||||
for (i in 0 until statements.size) {
|
for (i in statements.indices) {
|
||||||
val stmt = statements[i] as? Assignment
|
val stmt = statements[i] as? Assignment
|
||||||
if (stmt != null && stmt.value is LiteralValue) {
|
if (stmt != null && stmt.value is NumericLiteralValue) {
|
||||||
if (previousAssignmentLine == null) {
|
if (previousAssignmentLine == null) {
|
||||||
previousAssignmentLine = i
|
previousAssignmentLine = i
|
||||||
continue
|
continue
|
||||||
@ -229,7 +157,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
|||||||
return linesToRemove
|
return linesToRemove
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(functionCallStatement: FunctionCallStatement): IStatement {
|
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
|
||||||
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in BuiltinFunctions) {
|
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in BuiltinFunctions) {
|
||||||
val functionName = functionCallStatement.target.nameInSource[0]
|
val functionName = functionCallStatement.target.nameInSource[0]
|
||||||
if (functionName in pureBuiltinFunctions) {
|
if (functionName in pureBuiltinFunctions) {
|
||||||
@ -242,26 +170,31 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
|||||||
if(functionCallStatement.target.nameInSource==listOf("c64scr", "print") ||
|
if(functionCallStatement.target.nameInSource==listOf("c64scr", "print") ||
|
||||||
functionCallStatement.target.nameInSource==listOf("c64scr", "print_p")) {
|
functionCallStatement.target.nameInSource==listOf("c64scr", "print_p")) {
|
||||||
// printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters
|
// printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters
|
||||||
if(functionCallStatement.arglist.single() is LiteralValue)
|
val arg = functionCallStatement.arglist.single()
|
||||||
throw AstException("string argument should be on heap already")
|
val stringVar: IdentifierReference?
|
||||||
val stringVar = functionCallStatement.arglist.single() as? IdentifierReference
|
stringVar = if(arg is AddressOf) {
|
||||||
|
arg.identifier
|
||||||
|
} else {
|
||||||
|
arg as? IdentifierReference
|
||||||
|
}
|
||||||
if(stringVar!=null) {
|
if(stringVar!=null) {
|
||||||
val heapId = stringVar.heapId(program.namespace)
|
val vardecl = stringVar.targetVarDecl(program.namespace)!!
|
||||||
val string = program.heap.get(heapId).str!!
|
val string = vardecl.value!! as StringLiteralValue
|
||||||
if(string.length==1) {
|
val encodedString = Petscii.encodePetscii(string.value, true)
|
||||||
val petscii = Petscii.encodePetscii(string, true)[0]
|
if(string.value.length==1) {
|
||||||
functionCallStatement.arglist.clear()
|
functionCallStatement.arglist.clear()
|
||||||
functionCallStatement.arglist.add(LiteralValue.optimalInteger(petscii, functionCallStatement.position))
|
functionCallStatement.arglist.add(NumericLiteralValue.optimalInteger(encodedString[0].toInt(), functionCallStatement.position))
|
||||||
functionCallStatement.target = IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position)
|
functionCallStatement.target = IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position)
|
||||||
|
vardeclsToRemove.add(vardecl)
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
return functionCallStatement
|
return functionCallStatement
|
||||||
} else if(string.length==2) {
|
} else if(string.value.length==2) {
|
||||||
val petscii = Petscii.encodePetscii(string, true)
|
|
||||||
val scope = AnonymousScope(mutableListOf(), functionCallStatement.position)
|
val scope = AnonymousScope(mutableListOf(), functionCallStatement.position)
|
||||||
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position),
|
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position),
|
||||||
mutableListOf(LiteralValue.optimalInteger(petscii[0], functionCallStatement.position)), functionCallStatement.position))
|
mutableListOf(NumericLiteralValue.optimalInteger(encodedString[0].toInt(), functionCallStatement.position)), functionCallStatement.position))
|
||||||
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position),
|
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position),
|
||||||
mutableListOf(LiteralValue.optimalInteger(petscii[1], functionCallStatement.position)), functionCallStatement.position))
|
mutableListOf(NumericLiteralValue.optimalInteger(encodedString[1].toInt(), functionCallStatement.position)), functionCallStatement.position))
|
||||||
|
vardeclsToRemove.add(vardecl)
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
return scope
|
return scope
|
||||||
}
|
}
|
||||||
@ -287,7 +220,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
|||||||
return super.visit(functionCallStatement)
|
return super.visit(functionCallStatement)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(functionCall: FunctionCall): IExpression {
|
override fun visit(functionCall: FunctionCall): Expression {
|
||||||
// if it calls a subroutine,
|
// if it calls a subroutine,
|
||||||
// and the first instruction in the subroutine is a jump, call that jump target instead
|
// and the first instruction in the subroutine is a jump, call that jump target instead
|
||||||
// if the first instruction in the subroutine is a return statement with constant value, replace with the constant value
|
// if the first instruction in the subroutine is a return statement with constant value, replace with the constant value
|
||||||
@ -307,7 +240,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
|||||||
return super.visit(functionCall)
|
return super.visit(functionCall)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(ifStatement: IfStatement): IStatement {
|
override fun visit(ifStatement: IfStatement): Statement {
|
||||||
super.visit(ifStatement)
|
super.visit(ifStatement)
|
||||||
|
|
||||||
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsNoCodeNorVars()) {
|
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsNoCodeNorVars()) {
|
||||||
@ -341,7 +274,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
|||||||
return ifStatement
|
return ifStatement
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(forLoop: ForLoop): IStatement {
|
override fun visit(forLoop: ForLoop): Statement {
|
||||||
super.visit(forLoop)
|
super.visit(forLoop)
|
||||||
if(forLoop.body.containsNoCodeNorVars()) {
|
if(forLoop.body.containsNoCodeNorVars()) {
|
||||||
// remove empty for loop
|
// remove empty for loop
|
||||||
@ -371,7 +304,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
|||||||
return forLoop
|
return forLoop
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(whileLoop: WhileLoop): IStatement {
|
override fun visit(whileLoop: WhileLoop): Statement {
|
||||||
super.visit(whileLoop)
|
super.visit(whileLoop)
|
||||||
val constvalue = whileLoop.condition.constValue(program)
|
val constvalue = whileLoop.condition.constValue(program)
|
||||||
if(constvalue!=null) {
|
if(constvalue!=null) {
|
||||||
@ -380,10 +313,10 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
|||||||
printWarning("condition is always true", whileLoop.position)
|
printWarning("condition is always true", whileLoop.position)
|
||||||
if(hasContinueOrBreak(whileLoop.body))
|
if(hasContinueOrBreak(whileLoop.body))
|
||||||
return whileLoop
|
return whileLoop
|
||||||
val label = Label("__back", whileLoop.condition.position)
|
val label = Label("_prog8_back", whileLoop.condition.position)
|
||||||
whileLoop.body.statements.add(0, label)
|
whileLoop.body.statements.add(0, label)
|
||||||
whileLoop.body.statements.add(Jump(null,
|
whileLoop.body.statements.add(Jump(null,
|
||||||
IdentifierReference(listOf("__back"), whileLoop.condition.position),
|
IdentifierReference(listOf("_prog8_back"), whileLoop.condition.position),
|
||||||
null, whileLoop.condition.position))
|
null, whileLoop.condition.position))
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
return whileLoop.body
|
return whileLoop.body
|
||||||
@ -397,7 +330,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
|||||||
return whileLoop
|
return whileLoop
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(repeatLoop: RepeatLoop): IStatement {
|
override fun visit(repeatLoop: RepeatLoop): Statement {
|
||||||
super.visit(repeatLoop)
|
super.visit(repeatLoop)
|
||||||
val constvalue = repeatLoop.untilCondition.constValue(program)
|
val constvalue = repeatLoop.untilCondition.constValue(program)
|
||||||
if(constvalue!=null) {
|
if(constvalue!=null) {
|
||||||
@ -427,7 +360,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
|||||||
return repeatLoop
|
return repeatLoop
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(whenStatement: WhenStatement): IStatement {
|
override fun visit(whenStatement: WhenStatement): Statement {
|
||||||
val choices = whenStatement.choices.toList()
|
val choices = whenStatement.choices.toList()
|
||||||
for(choice in choices) {
|
for(choice in choices) {
|
||||||
if(choice.statements.containsNoCodeNorVars())
|
if(choice.statements.containsNoCodeNorVars())
|
||||||
@ -442,12 +375,12 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
|||||||
{
|
{
|
||||||
var count=0
|
var count=0
|
||||||
|
|
||||||
override fun visit(breakStmt: Break): IStatement {
|
override fun visit(breakStmt: Break): Statement {
|
||||||
count++
|
count++
|
||||||
return super.visit(breakStmt)
|
return super.visit(breakStmt)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(contStmt: Continue): IStatement {
|
override fun visit(contStmt: Continue): Statement {
|
||||||
count++
|
count++
|
||||||
return super.visit(contStmt)
|
return super.visit(contStmt)
|
||||||
}
|
}
|
||||||
@ -461,7 +394,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
|||||||
return s.count > 0
|
return s.count > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(jump: Jump): IStatement {
|
override fun visit(jump: Jump): Statement {
|
||||||
val subroutine = jump.identifier?.targetSubroutine(program.namespace)
|
val subroutine = jump.identifier?.targetSubroutine(program.namespace)
|
||||||
if(subroutine!=null) {
|
if(subroutine!=null) {
|
||||||
// if the first instruction in the subroutine is another jump, shortcut this one
|
// if the first instruction in the subroutine is another jump, shortcut this one
|
||||||
@ -485,23 +418,28 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
|||||||
return jump
|
return jump
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(assignment: Assignment): IStatement {
|
override fun visit(assignment: Assignment): Statement {
|
||||||
if(assignment.aug_op!=null)
|
if(assignment.aug_op!=null)
|
||||||
throw AstException("augmented assignments should have been converted to normal assignments before this optimizer")
|
throw AstException("augmented assignments should have been converted to normal assignments before this optimizer")
|
||||||
|
|
||||||
if(assignment.target isSameAs assignment.value) {
|
if(assignment.target isSameAs assignment.value) {
|
||||||
optimizationsDone++
|
if(assignment.target.isNotMemory(program.namespace)) {
|
||||||
return NopStatement.insteadOf(assignment)
|
optimizationsDone++
|
||||||
|
return NopStatement.insteadOf(assignment)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val targetDt = assignment.target.inferType(program, assignment)
|
val targetIDt = assignment.target.inferType(program, assignment)
|
||||||
|
if(!targetIDt.isKnown)
|
||||||
|
throw AssemblyError("can't infer type of assignment target")
|
||||||
|
val targetDt = targetIDt.typeOrElse(DataType.STRUCT)
|
||||||
val bexpr=assignment.value as? BinaryExpression
|
val bexpr=assignment.value as? BinaryExpression
|
||||||
if(bexpr!=null) {
|
if(bexpr!=null) {
|
||||||
val cv = bexpr.right.constValue(program)?.asNumericValue?.toDouble()
|
val cv = bexpr.right.constValue(program)?.number?.toDouble()
|
||||||
if (cv == null) {
|
if (cv == null) {
|
||||||
if (bexpr.operator == "+" && targetDt != DataType.FLOAT) {
|
if (bexpr.operator == "+" && targetDt != DataType.FLOAT) {
|
||||||
if (bexpr.left isSameAs bexpr.right && assignment.target isSameAs bexpr.left) {
|
if (bexpr.left isSameAs bexpr.right && assignment.target isSameAs bexpr.left) {
|
||||||
bexpr.operator = "*"
|
bexpr.operator = "*"
|
||||||
bexpr.right = LiteralValue.optimalInteger(2, assignment.value.position)
|
bexpr.right = NumericLiteralValue.optimalInteger(2, assignment.value.position)
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
return assignment
|
return assignment
|
||||||
}
|
}
|
||||||
@ -570,7 +508,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
|||||||
}
|
}
|
||||||
if (((targetDt == DataType.UWORD || targetDt == DataType.WORD) && cv > 15.0) ||
|
if (((targetDt == DataType.UWORD || targetDt == DataType.WORD) && cv > 15.0) ||
|
||||||
((targetDt == DataType.UBYTE || targetDt == DataType.BYTE) && cv > 7.0)) {
|
((targetDt == DataType.UBYTE || targetDt == DataType.BYTE) && cv > 7.0)) {
|
||||||
assignment.value = LiteralValue.optimalInteger(0, assignment.value.position)
|
assignment.value = NumericLiteralValue.optimalInteger(0, assignment.value.position)
|
||||||
assignment.value.linkParents(assignment)
|
assignment.value.linkParents(assignment)
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
} else {
|
} else {
|
||||||
@ -590,9 +528,8 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
|||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
return NopStatement.insteadOf(assignment)
|
return NopStatement.insteadOf(assignment)
|
||||||
}
|
}
|
||||||
if (((targetDt == DataType.UWORD || targetDt == DataType.WORD) && cv > 15.0) ||
|
if ((targetDt == DataType.UWORD && cv > 15.0) || (targetDt == DataType.UBYTE && cv > 7.0)) {
|
||||||
((targetDt == DataType.UBYTE || targetDt == DataType.BYTE) && cv > 7.0)) {
|
assignment.value = NumericLiteralValue.optimalInteger(0, assignment.value.position)
|
||||||
assignment.value = LiteralValue.optimalInteger(0, assignment.value.position)
|
|
||||||
assignment.value.linkParents(assignment)
|
assignment.value.linkParents(assignment)
|
||||||
optimizationsDone++
|
optimizationsDone++
|
||||||
} else {
|
} else {
|
||||||
@ -616,20 +553,15 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
|||||||
return super.visit(assignment)
|
return super.visit(assignment)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(scope: AnonymousScope): IStatement {
|
override fun visit(scope: AnonymousScope): Statement {
|
||||||
val linesToRemove = deduplicateAssignments(scope.statements)
|
val linesToRemove = deduplicateAssignments(scope.statements)
|
||||||
if(linesToRemove.isNotEmpty()) {
|
if(linesToRemove.isNotEmpty()) {
|
||||||
linesToRemove.reversed().forEach{scope.statements.removeAt(it)}
|
linesToRemove.reversed().forEach{scope.statements.removeAt(it)}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(scope.parent is INameScope) {
|
|
||||||
scopesToFlatten.add(scope) // get rid of the anonymous scope
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.visit(scope)
|
return super.visit(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(label: Label): IStatement {
|
override fun visit(label: Label): Statement {
|
||||||
// remove duplicate labels
|
// remove duplicate labels
|
||||||
val stmts = label.definingScope().statements
|
val stmts = label.definingScope().statements
|
||||||
val startIdx = stmts.indexOf(label)
|
val startIdx = stmts.indexOf(label)
|
||||||
@ -642,17 +574,38 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
internal class RemoveNops: IAstVisitor {
|
internal class FlattenAnonymousScopesAndRemoveNops: IAstVisitor {
|
||||||
val nopStatements = mutableListOf<NopStatement>()
|
private var scopesToFlatten = mutableListOf<INameScope>()
|
||||||
|
private val nopStatements = mutableListOf<NopStatement>()
|
||||||
|
|
||||||
override fun visit(program: Program) {
|
override fun visit(program: Program) {
|
||||||
super.visit(program)
|
super.visit(program)
|
||||||
// at the end, remove the encountered NOP statements
|
for(scope in scopesToFlatten.reversed()) {
|
||||||
|
val namescope = scope.parent as INameScope
|
||||||
|
val idx = namescope.statements.indexOf(scope as Statement)
|
||||||
|
if(idx>=0) {
|
||||||
|
val nop = NopStatement.insteadOf(namescope.statements[idx])
|
||||||
|
nop.parent = namescope as Node
|
||||||
|
namescope.statements[idx] = nop
|
||||||
|
namescope.statements.addAll(idx, scope.statements)
|
||||||
|
scope.statements.forEach { it.parent = namescope }
|
||||||
|
visit(nop)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.nopStatements.forEach {
|
this.nopStatements.forEach {
|
||||||
it.definingScope().remove(it)
|
it.definingScope().remove(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun visit(scope: AnonymousScope) {
|
||||||
|
if(scope.parent is INameScope) {
|
||||||
|
scopesToFlatten.add(scope) // get rid of the anonymous scope
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.visit(scope)
|
||||||
|
}
|
||||||
|
|
||||||
override fun visit(nopStatement: NopStatement) {
|
override fun visit(nopStatement: NopStatement) {
|
||||||
nopStatements.add(nopStatement)
|
nopStatements.add(nopStatement)
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
package prog8.parser
|
package prog8.parser
|
||||||
|
|
||||||
import org.antlr.v4.runtime.*
|
import org.antlr.v4.runtime.*
|
||||||
import prog8.ast.*
|
import prog8.ast.Module
|
||||||
|
import prog8.ast.Program
|
||||||
import prog8.ast.antlr.toAst
|
import prog8.ast.antlr.toAst
|
||||||
import prog8.ast.base.Position
|
import prog8.ast.base.Position
|
||||||
import prog8.ast.base.SyntaxError
|
import prog8.ast.base.SyntaxError
|
||||||
import prog8.ast.base.checkImportedValid
|
import prog8.ast.base.checkImportedValid
|
||||||
import prog8.ast.statements.Directive
|
import prog8.ast.statements.Directive
|
||||||
import prog8.ast.statements.DirectiveArg
|
import prog8.ast.statements.DirectiveArg
|
||||||
|
import prog8.pathFrom
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
@ -90,18 +92,18 @@ internal fun importModule(program: Program, stream: CharStream, modulePath: Path
|
|||||||
|
|
||||||
private fun discoverImportedModuleFile(name: String, source: Path, position: Position?): Path {
|
private fun discoverImportedModuleFile(name: String, source: Path, position: Position?): Path {
|
||||||
val fileName = "$name.p8"
|
val fileName = "$name.p8"
|
||||||
val locations = mutableListOf(Paths.get(source.parent.toString()))
|
val locations = mutableListOf(source.parent)
|
||||||
|
|
||||||
val propPath = System.getProperty("prog8.libdir")
|
val propPath = System.getProperty("prog8.libdir")
|
||||||
if(propPath!=null)
|
if(propPath!=null)
|
||||||
locations.add(Paths.get(propPath))
|
locations.add(pathFrom(propPath))
|
||||||
val envPath = System.getenv("PROG8_LIBDIR")
|
val envPath = System.getenv("PROG8_LIBDIR")
|
||||||
if(envPath!=null)
|
if(envPath!=null)
|
||||||
locations.add(Paths.get(envPath))
|
locations.add(pathFrom(envPath))
|
||||||
locations.add(Paths.get(Paths.get("").toAbsolutePath().toString(), "prog8lib"))
|
locations.add(Paths.get(Paths.get("").toAbsolutePath().toString(), "prog8lib"))
|
||||||
|
|
||||||
locations.forEach {
|
locations.forEach {
|
||||||
val file = Paths.get(it.toString(), fileName)
|
val file = pathFrom(it.toString(), fileName)
|
||||||
if (Files.isReadable(file)) return file
|
if (Files.isReadable(file)) return file
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,7 +121,7 @@ private fun executeImportDirective(program: Program, import: Directive, source:
|
|||||||
if(existing!=null)
|
if(existing!=null)
|
||||||
return null
|
return null
|
||||||
|
|
||||||
val resource = tryGetEmbeddedResource(moduleName+".p8")
|
val resource = tryGetEmbeddedResource("$moduleName.p8")
|
||||||
val importedModule =
|
val importedModule =
|
||||||
if(resource!=null) {
|
if(resource!=null) {
|
||||||
// load the module from the embedded resource
|
// load the module from the embedded resource
|
||||||
|
@ -1,19 +1,31 @@
|
|||||||
package prog8.vm
|
package prog8.vm
|
||||||
|
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.ByteDatatypes
|
||||||
import prog8.ast.expressions.LiteralValue
|
import prog8.ast.base.DataType
|
||||||
import prog8.compiler.HeapValues
|
import prog8.ast.base.WordDatatypes
|
||||||
|
import prog8.ast.expressions.ArrayLiteralValue
|
||||||
|
import prog8.ast.expressions.NumericLiteralValue
|
||||||
|
import prog8.ast.expressions.StringLiteralValue
|
||||||
import prog8.compiler.target.c64.Petscii
|
import prog8.compiler.target.c64.Petscii
|
||||||
|
import prog8.vm.astvm.VmExecutionException
|
||||||
|
import java.util.Objects
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rather than a literal value (LiteralValue) that occurs in the parsed source code,
|
* Rather than a literal value (NumericLiteralValue) that occurs in the parsed source code,
|
||||||
* this runtime value can be used to *execute* the parsed Ast (or another intermediary form)
|
* this runtime value can be used to *execute* the parsed Ast (or another intermediary form)
|
||||||
* It contains a value of a variable during run time of the program and provides arithmetic operations on the value.
|
* It contains a value of a variable during run time of the program and provides arithmetic operations on the value.
|
||||||
*/
|
*/
|
||||||
open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=null, val array: Array<Number>?=null, val heapId: Int?=null) {
|
|
||||||
|
abstract class RuntimeValueBase(val type: DataType) {
|
||||||
|
abstract fun numericValue(): Number
|
||||||
|
abstract fun integerValue(): Int
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class RuntimeValueNumeric(type: DataType, num: Number): RuntimeValueBase(type) {
|
||||||
|
|
||||||
val byteval: Short?
|
val byteval: Short?
|
||||||
val wordval: Int?
|
val wordval: Int?
|
||||||
@ -21,134 +33,78 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
|
|||||||
val asBoolean: Boolean
|
val asBoolean: Boolean
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun from(literalValue: LiteralValue, heap: HeapValues): RuntimeValue {
|
fun fromLv(literalValue: NumericLiteralValue): RuntimeValueNumeric {
|
||||||
return when(literalValue.type) {
|
return RuntimeValueNumeric(literalValue.type, num = literalValue.number)
|
||||||
in NumericDatatypes -> RuntimeValue(literalValue.type, num = literalValue.asNumericValue!!)
|
|
||||||
in StringDatatypes -> from(literalValue.heapId!!, heap)
|
|
||||||
in ArrayDatatypes -> from(literalValue.heapId!!, heap)
|
|
||||||
else -> throw IllegalArgumentException("weird source value $literalValue")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun from(heapId: Int, heap: HeapValues): RuntimeValue {
|
|
||||||
val value = heap.get(heapId)
|
|
||||||
return when {
|
|
||||||
value.type in StringDatatypes ->
|
|
||||||
RuntimeValue(value.type, str = value.str!!, heapId = heapId)
|
|
||||||
value.type in ArrayDatatypes ->
|
|
||||||
if (value.type == DataType.ARRAY_F) {
|
|
||||||
RuntimeValue(value.type, array = value.doubleArray!!.toList().toTypedArray(), heapId = heapId)
|
|
||||||
} else {
|
|
||||||
val array = value.array!!
|
|
||||||
val resultArray = mutableListOf<Number>()
|
|
||||||
for(elt in array.withIndex()){
|
|
||||||
if(elt.value.integer!=null)
|
|
||||||
resultArray.add(elt.value.integer!!)
|
|
||||||
else {
|
|
||||||
println("ADDRESSOF ${elt.value}")
|
|
||||||
resultArray.add(0x8000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RuntimeValue(value.type, array = resultArray.toTypedArray(), heapId = heapId)
|
|
||||||
//RuntimeValue(value.type, array = array.map { it.integer!! }.toTypedArray(), heapId = heapId)
|
|
||||||
}
|
|
||||||
else -> throw IllegalArgumentException("weird value type on heap $value")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
when(type) {
|
when (type) {
|
||||||
DataType.UBYTE -> {
|
DataType.UBYTE -> {
|
||||||
val inum = num!!.toInt()
|
val inum = num.toInt()
|
||||||
if(inum !in 0 .. 255)
|
require(inum in 0..255) { "invalid value for ubyte: $inum" }
|
||||||
throw IllegalArgumentException("invalid value for ubyte: $inum")
|
|
||||||
byteval = inum.toShort()
|
byteval = inum.toShort()
|
||||||
wordval = null
|
wordval = null
|
||||||
floatval = null
|
floatval = null
|
||||||
asBoolean = byteval != 0.toShort()
|
asBoolean = byteval != 0.toShort()
|
||||||
}
|
}
|
||||||
DataType.BYTE -> {
|
DataType.BYTE -> {
|
||||||
val inum = num!!.toInt()
|
val inum = num.toInt()
|
||||||
if(inum !in -128 .. 127)
|
require(inum in -128..127) { "invalid value for byte: $inum" }
|
||||||
throw IllegalArgumentException("invalid value for byte: $inum")
|
|
||||||
byteval = inum.toShort()
|
byteval = inum.toShort()
|
||||||
wordval = null
|
wordval = null
|
||||||
floatval = null
|
floatval = null
|
||||||
asBoolean = byteval != 0.toShort()
|
asBoolean = byteval != 0.toShort()
|
||||||
}
|
}
|
||||||
DataType.UWORD -> {
|
DataType.UWORD -> {
|
||||||
val inum = num!!.toInt()
|
val inum = num.toInt()
|
||||||
if(inum !in 0 .. 65535)
|
require(inum in 0..65535) { "invalid value for uword: $inum" }
|
||||||
throw IllegalArgumentException("invalid value for uword: $inum")
|
|
||||||
wordval = inum
|
wordval = inum
|
||||||
byteval = null
|
byteval = null
|
||||||
floatval = null
|
floatval = null
|
||||||
asBoolean = wordval != 0
|
asBoolean = wordval != 0
|
||||||
}
|
}
|
||||||
DataType.WORD -> {
|
DataType.WORD -> {
|
||||||
val inum = num!!.toInt()
|
val inum = num.toInt()
|
||||||
if(inum !in -32768 .. 32767)
|
require(inum in -32768..32767) { "invalid value for word: $inum" }
|
||||||
throw IllegalArgumentException("invalid value for word: $inum")
|
|
||||||
wordval = inum
|
wordval = inum
|
||||||
byteval = null
|
byteval = null
|
||||||
floatval = null
|
floatval = null
|
||||||
asBoolean = wordval != 0
|
asBoolean = wordval != 0
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
floatval = num!!.toDouble()
|
floatval = num.toDouble()
|
||||||
byteval = null
|
byteval = null
|
||||||
wordval = null
|
wordval = null
|
||||||
asBoolean = floatval != 0.0
|
asBoolean = floatval != 0.0
|
||||||
}
|
}
|
||||||
else -> {
|
else -> throw VmExecutionException("not a numeric value")
|
||||||
if(heapId==null)
|
|
||||||
throw IllegalArgumentException("for non-numeric types, a heapId should be given")
|
|
||||||
byteval = null
|
|
||||||
wordval = null
|
|
||||||
floatval = null
|
|
||||||
asBoolean = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun asLiteralValue(): LiteralValue {
|
|
||||||
return when(type) {
|
|
||||||
in ByteDatatypes -> LiteralValue(type, byteval, position = Position("", 0, 0, 0))
|
|
||||||
in WordDatatypes -> LiteralValue(type, wordvalue = wordval, position = Position("", 0, 0, 0))
|
|
||||||
DataType.FLOAT -> LiteralValue(type, floatvalue = floatval, position = Position("", 0, 0, 0))
|
|
||||||
in StringDatatypes -> LiteralValue(type, strvalue = str, position = Position("", 0, 0, 0))
|
|
||||||
in ArrayDatatypes -> LiteralValue(type,
|
|
||||||
arrayvalue = array?.map { LiteralValue.optimalNumeric(it, Position("", 0, 0, 0)) }?.toTypedArray(),
|
|
||||||
position = Position("", 0, 0, 0))
|
|
||||||
else -> throw IllegalArgumentException("weird source value $this")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return when(type) {
|
return when (type) {
|
||||||
DataType.UBYTE -> "ub:%02x".format(byteval)
|
DataType.UBYTE -> "ub:%02x".format(byteval)
|
||||||
DataType.BYTE -> {
|
DataType.BYTE -> {
|
||||||
if(byteval!!<0)
|
if (byteval!! < 0)
|
||||||
"b:-%02x".format(abs(byteval.toInt()))
|
"b:-%02x".format(abs(byteval.toInt()))
|
||||||
else
|
else
|
||||||
"b:%02x".format(byteval)
|
"b:%02x".format(byteval)
|
||||||
}
|
}
|
||||||
DataType.UWORD -> "uw:%04x".format(wordval)
|
DataType.UWORD -> "uw:%04x".format(wordval)
|
||||||
DataType.WORD -> {
|
DataType.WORD -> {
|
||||||
if(wordval!!<0)
|
if (wordval!! < 0)
|
||||||
"w:-%04x".format(abs(wordval))
|
"w:-%04x".format(abs(wordval))
|
||||||
else
|
else
|
||||||
"w:%04x".format(wordval)
|
"w:%04x".format(wordval)
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> "f:$floatval"
|
DataType.FLOAT -> "f:$floatval"
|
||||||
else -> "heap:$heapId"
|
else -> "???"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun numericValue(): Number {
|
override fun numericValue(): Number {
|
||||||
return when(type) {
|
return when (type) {
|
||||||
in ByteDatatypes -> byteval!!
|
in ByteDatatypes -> byteval!!
|
||||||
in WordDatatypes -> wordval!!
|
in WordDatatypes -> wordval!!
|
||||||
DataType.FLOAT -> floatval!!
|
DataType.FLOAT -> floatval!!
|
||||||
@ -156,8 +112,8 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun integerValue(): Int {
|
override fun integerValue(): Int {
|
||||||
return when(type) {
|
return when (type) {
|
||||||
in ByteDatatypes -> byteval!!.toInt()
|
in ByteDatatypes -> byteval!!.toInt()
|
||||||
in WordDatatypes -> wordval!!
|
in WordDatatypes -> wordval!!
|
||||||
DataType.FLOAT -> throw ArithmeticException("float to integer loss of precision")
|
DataType.FLOAT -> throw ArithmeticException("float to integer loss of precision")
|
||||||
@ -165,83 +121,72 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int = Objects.hash(byteval, wordval, floatval, type)
|
||||||
val bh = byteval?.hashCode() ?: 0x10001234
|
|
||||||
val wh = wordval?.hashCode() ?: 0x01002345
|
|
||||||
val fh = floatval?.hashCode() ?: 0x00103456
|
|
||||||
return bh xor wh xor fh xor heapId.hashCode() xor type.hashCode()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if(other==null || other !is RuntimeValue)
|
if (other == null || other !is RuntimeValueNumeric)
|
||||||
return false
|
return false
|
||||||
if(type==other.type)
|
return compareTo(other) == 0 // note: datatype doesn't matter
|
||||||
return if (type in IterableDatatypes) heapId==other.heapId else compareTo(other)==0
|
|
||||||
return compareTo(other)==0 // note: datatype doesn't matter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun compareTo(other: RuntimeValue): Int {
|
operator fun compareTo(other: RuntimeValueNumeric): Int = numericValue().toDouble().compareTo(other.numericValue().toDouble())
|
||||||
return if (type in NumericDatatypes && other.type in NumericDatatypes)
|
|
||||||
numericValue().toDouble().compareTo(other.numericValue().toDouble())
|
|
||||||
else throw ArithmeticException("comparison can only be done between two numeric values")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun arithResult(leftDt: DataType, result: Number, rightDt: DataType, op: String): RuntimeValue {
|
private fun arithResult(leftDt: DataType, result: Number, rightDt: DataType, op: String): RuntimeValueNumeric {
|
||||||
if(leftDt!=rightDt)
|
if (leftDt != rightDt)
|
||||||
throw ArithmeticException("left and right datatypes are not the same")
|
throw ArithmeticException("left and right datatypes are not the same")
|
||||||
if(result.toDouble() < 0 ) {
|
if (result.toDouble() < 0) {
|
||||||
return when(leftDt) {
|
return when (leftDt) {
|
||||||
DataType.UBYTE, DataType.UWORD -> {
|
DataType.UBYTE, DataType.UWORD -> {
|
||||||
// storing a negative number in an unsigned one is done by storing the 2's complement instead
|
// storing a negative number in an unsigned one is done by storing the 2's complement instead
|
||||||
val number = abs(result.toDouble().toInt())
|
val number = abs(result.toDouble().toInt())
|
||||||
if(leftDt== DataType.UBYTE)
|
if (leftDt == DataType.UBYTE)
|
||||||
RuntimeValue(DataType.UBYTE, (number xor 255) + 1)
|
RuntimeValueNumeric(DataType.UBYTE, (number xor 255) + 1)
|
||||||
else
|
else
|
||||||
RuntimeValue(DataType.UWORD, (number xor 65535) + 1)
|
RuntimeValueNumeric(DataType.UWORD, (number xor 65535) + 1)
|
||||||
}
|
}
|
||||||
DataType.BYTE -> {
|
DataType.BYTE -> {
|
||||||
val v=result.toInt() and 255
|
val v = result.toInt() and 255
|
||||||
if(v<128)
|
if (v < 128)
|
||||||
RuntimeValue(DataType.BYTE, v)
|
RuntimeValueNumeric(DataType.BYTE, v)
|
||||||
else
|
else
|
||||||
RuntimeValue(DataType.BYTE, v-256)
|
RuntimeValueNumeric(DataType.BYTE, v - 256)
|
||||||
}
|
}
|
||||||
DataType.WORD -> {
|
DataType.WORD -> {
|
||||||
val v=result.toInt() and 65535
|
val v = result.toInt() and 65535
|
||||||
if(v<32768)
|
if (v < 32768)
|
||||||
RuntimeValue(DataType.WORD, v)
|
RuntimeValueNumeric(DataType.WORD, v)
|
||||||
else
|
else
|
||||||
RuntimeValue(DataType.WORD, v-65536)
|
RuntimeValueNumeric(DataType.WORD, v - 65536)
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, result)
|
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, result)
|
||||||
else -> throw ArithmeticException("$op on non-numeric type")
|
else -> throw ArithmeticException("$op on non-numeric type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return when(leftDt) {
|
return when (leftDt) {
|
||||||
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, result.toInt() and 255)
|
DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, result.toInt() and 255)
|
||||||
DataType.BYTE -> {
|
DataType.BYTE -> {
|
||||||
val v = result.toInt() and 255
|
val v = result.toInt() and 255
|
||||||
if(v<128)
|
if (v < 128)
|
||||||
RuntimeValue(DataType.BYTE, v)
|
RuntimeValueNumeric(DataType.BYTE, v)
|
||||||
else
|
else
|
||||||
RuntimeValue(DataType.BYTE, v-256)
|
RuntimeValueNumeric(DataType.BYTE, v - 256)
|
||||||
}
|
}
|
||||||
DataType.UWORD -> RuntimeValue(DataType.UWORD, result.toInt() and 65535)
|
DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, result.toInt() and 65535)
|
||||||
DataType.WORD -> {
|
DataType.WORD -> {
|
||||||
val v = result.toInt() and 65535
|
val v = result.toInt() and 65535
|
||||||
if(v<32768)
|
if (v < 32768)
|
||||||
RuntimeValue(DataType.WORD, v)
|
RuntimeValueNumeric(DataType.WORD, v)
|
||||||
else
|
else
|
||||||
RuntimeValue(DataType.WORD, v-65536)
|
RuntimeValueNumeric(DataType.WORD, v - 65536)
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, result)
|
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, result)
|
||||||
else -> throw ArithmeticException("$op on non-numeric type")
|
else -> throw ArithmeticException("$op on non-numeric type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun add(other: RuntimeValue): RuntimeValue {
|
fun add(other: RuntimeValueNumeric): RuntimeValueNumeric {
|
||||||
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT))
|
if (other.type == DataType.FLOAT && (type != DataType.FLOAT))
|
||||||
throw ArithmeticException("floating point loss of precision on type $type")
|
throw ArithmeticException("floating point loss of precision on type $type")
|
||||||
val v1 = numericValue()
|
val v1 = numericValue()
|
||||||
val v2 = other.numericValue()
|
val v2 = other.numericValue()
|
||||||
@ -249,8 +194,8 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
|
|||||||
return arithResult(type, result, other.type, "add")
|
return arithResult(type, result, other.type, "add")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sub(other: RuntimeValue): RuntimeValue {
|
fun sub(other: RuntimeValueNumeric): RuntimeValueNumeric {
|
||||||
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT))
|
if (other.type == DataType.FLOAT && (type != DataType.FLOAT))
|
||||||
throw ArithmeticException("floating point loss of precision on type $type")
|
throw ArithmeticException("floating point loss of precision on type $type")
|
||||||
val v1 = numericValue()
|
val v1 = numericValue()
|
||||||
val v2 = other.numericValue()
|
val v2 = other.numericValue()
|
||||||
@ -258,8 +203,8 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
|
|||||||
return arithResult(type, result, other.type, "sub")
|
return arithResult(type, result, other.type, "sub")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun mul(other: RuntimeValue): RuntimeValue {
|
fun mul(other: RuntimeValueNumeric): RuntimeValueNumeric {
|
||||||
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT))
|
if (other.type == DataType.FLOAT && (type != DataType.FLOAT))
|
||||||
throw ArithmeticException("floating point loss of precision on type $type")
|
throw ArithmeticException("floating point loss of precision on type $type")
|
||||||
val v1 = numericValue()
|
val v1 = numericValue()
|
||||||
val v2 = other.numericValue()
|
val v2 = other.numericValue()
|
||||||
@ -267,317 +212,318 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
|
|||||||
return arithResult(type, result, other.type, "mul")
|
return arithResult(type, result, other.type, "mul")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun div(other: RuntimeValue): RuntimeValue {
|
fun div(other: RuntimeValueNumeric): RuntimeValueNumeric {
|
||||||
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT))
|
if (other.type == DataType.FLOAT && (type != DataType.FLOAT))
|
||||||
throw ArithmeticException("floating point loss of precision on type $type")
|
throw ArithmeticException("floating point loss of precision on type $type")
|
||||||
val v1 = numericValue()
|
val v1 = numericValue()
|
||||||
val v2 = other.numericValue()
|
val v2 = other.numericValue()
|
||||||
if(v2.toDouble()==0.0) {
|
if (v2.toDouble() == 0.0) {
|
||||||
when (type) {
|
when (type) {
|
||||||
DataType.UBYTE -> return RuntimeValue(DataType.UBYTE, 255)
|
DataType.UBYTE -> return RuntimeValueNumeric(DataType.UBYTE, 255)
|
||||||
DataType.BYTE -> return RuntimeValue(DataType.BYTE, 127)
|
DataType.BYTE -> return RuntimeValueNumeric(DataType.BYTE, 127)
|
||||||
DataType.UWORD -> return RuntimeValue(DataType.UWORD, 65535)
|
DataType.UWORD -> return RuntimeValueNumeric(DataType.UWORD, 65535)
|
||||||
DataType.WORD -> return RuntimeValue(DataType.WORD, 32767)
|
DataType.WORD -> return RuntimeValueNumeric(DataType.WORD, 32767)
|
||||||
else -> {}
|
else -> {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val result = v1.toDouble() / v2.toDouble()
|
val result = v1.toDouble() / v2.toDouble()
|
||||||
// NOTE: integer division returns integer result!
|
// NOTE: integer division returns integer result!
|
||||||
return when(type) {
|
return when (type) {
|
||||||
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, result)
|
DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, result)
|
||||||
DataType.BYTE -> RuntimeValue(DataType.BYTE, result)
|
DataType.BYTE -> RuntimeValueNumeric(DataType.BYTE, result)
|
||||||
DataType.UWORD -> RuntimeValue(DataType.UWORD, result)
|
DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, result)
|
||||||
DataType.WORD -> RuntimeValue(DataType.WORD, result)
|
DataType.WORD -> RuntimeValueNumeric(DataType.WORD, result)
|
||||||
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, result)
|
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, result)
|
||||||
else -> throw ArithmeticException("div on non-numeric type")
|
else -> throw ArithmeticException("div on non-numeric type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun remainder(other: RuntimeValue): RuntimeValue {
|
fun remainder(other: RuntimeValueNumeric): RuntimeValueNumeric {
|
||||||
val v1 = numericValue()
|
val v1 = numericValue()
|
||||||
val v2 = other.numericValue()
|
val v2 = other.numericValue()
|
||||||
val result = v1.toDouble() % v2.toDouble()
|
val result = v1.toDouble() % v2.toDouble()
|
||||||
return arithResult(type, result, other.type, "remainder")
|
return arithResult(type, result, other.type, "remainder")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pow(other: RuntimeValue): RuntimeValue {
|
fun pow(other: RuntimeValueNumeric): RuntimeValueNumeric {
|
||||||
val v1 = numericValue()
|
val v1 = numericValue()
|
||||||
val v2 = other.numericValue()
|
val v2 = other.numericValue()
|
||||||
val result = v1.toDouble().pow(v2.toDouble())
|
val result = v1.toDouble().pow(v2.toDouble())
|
||||||
return arithResult(type, result, other.type,"pow")
|
return arithResult(type, result, other.type, "pow")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun shl(): RuntimeValue {
|
fun shl(): RuntimeValueNumeric {
|
||||||
val v = integerValue()
|
val v = integerValue()
|
||||||
return when (type) {
|
return when (type) {
|
||||||
DataType.UBYTE -> RuntimeValue(type, (v shl 1) and 255)
|
DataType.UBYTE -> RuntimeValueNumeric(type, (v shl 1) and 255)
|
||||||
DataType.UWORD -> RuntimeValue(type, (v shl 1) and 65535)
|
DataType.UWORD -> RuntimeValueNumeric(type, (v shl 1) and 65535)
|
||||||
DataType.BYTE -> {
|
DataType.BYTE -> {
|
||||||
val value = v shl 1
|
val value = v shl 1
|
||||||
if(value<128)
|
if (value < 128)
|
||||||
RuntimeValue(type, value)
|
RuntimeValueNumeric(type, value)
|
||||||
else
|
else
|
||||||
RuntimeValue(type, value-256)
|
RuntimeValueNumeric(type, value - 256)
|
||||||
}
|
}
|
||||||
DataType.WORD -> {
|
DataType.WORD -> {
|
||||||
val value = v shl 1
|
val value = v shl 1
|
||||||
if(value<32768)
|
if (value < 32768)
|
||||||
RuntimeValue(type, value)
|
RuntimeValueNumeric(type, value)
|
||||||
else
|
else
|
||||||
RuntimeValue(type, value-65536)
|
RuntimeValueNumeric(type, value - 65536)
|
||||||
}
|
}
|
||||||
else -> throw ArithmeticException("invalid type for shl: $type")
|
else -> throw ArithmeticException("invalid type for shl: $type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun shr(): RuntimeValue {
|
fun shr(): RuntimeValueNumeric {
|
||||||
val v = integerValue()
|
val v = integerValue()
|
||||||
return when(type){
|
return when (type) {
|
||||||
DataType.UBYTE -> RuntimeValue(type, v ushr 1)
|
DataType.UBYTE -> RuntimeValueNumeric(type, v ushr 1)
|
||||||
DataType.BYTE -> RuntimeValue(type, v shr 1)
|
DataType.BYTE -> RuntimeValueNumeric(type, v shr 1)
|
||||||
DataType.UWORD -> RuntimeValue(type, v ushr 1)
|
DataType.UWORD -> RuntimeValueNumeric(type, v ushr 1)
|
||||||
DataType.WORD -> RuntimeValue(type, v shr 1)
|
DataType.WORD -> RuntimeValueNumeric(type, v shr 1)
|
||||||
else -> throw ArithmeticException("invalid type for shr: $type")
|
else -> throw ArithmeticException("invalid type for shr: $type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun rol(carry: Boolean): Pair<RuntimeValue, Boolean> {
|
fun rol(carry: Boolean): Pair<RuntimeValueNumeric, Boolean> {
|
||||||
// 9 or 17 bit rotate left (with carry))
|
// 9 or 17 bit rotate left (with carry))
|
||||||
return when(type) {
|
return when (type) {
|
||||||
DataType.UBYTE, DataType.BYTE -> {
|
DataType.UBYTE, DataType.BYTE -> {
|
||||||
val v = byteval!!.toInt()
|
val v = byteval!!.toInt()
|
||||||
val newCarry = (v and 0x80) != 0
|
val newCarry = (v and 0x80) != 0
|
||||||
val newval = (v and 0x7f shl 1) or (if(carry) 1 else 0)
|
val newval = (v and 0x7f shl 1) or (if (carry) 1 else 0)
|
||||||
Pair(RuntimeValue(DataType.UBYTE, newval), newCarry)
|
Pair(RuntimeValueNumeric(DataType.UBYTE, newval), newCarry)
|
||||||
}
|
}
|
||||||
DataType.UWORD, DataType.WORD -> {
|
DataType.UWORD, DataType.WORD -> {
|
||||||
val v = wordval!!
|
val v = wordval!!
|
||||||
val newCarry = (v and 0x8000) != 0
|
val newCarry = (v and 0x8000) != 0
|
||||||
val newval = (v and 0x7fff shl 1) or (if(carry) 1 else 0)
|
val newval = (v and 0x7fff shl 1) or (if (carry) 1 else 0)
|
||||||
Pair(RuntimeValue(DataType.UWORD, newval), newCarry)
|
Pair(RuntimeValueNumeric(DataType.UWORD, newval), newCarry)
|
||||||
}
|
}
|
||||||
else -> throw ArithmeticException("rol can only work on byte/word")
|
else -> throw ArithmeticException("rol can only work on byte/word")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ror(carry: Boolean): Pair<RuntimeValue, Boolean> {
|
fun ror(carry: Boolean): Pair<RuntimeValueNumeric, Boolean> {
|
||||||
// 9 or 17 bit rotate right (with carry)
|
// 9 or 17 bit rotate right (with carry)
|
||||||
return when(type) {
|
return when (type) {
|
||||||
DataType.UBYTE, DataType.BYTE -> {
|
DataType.UBYTE, DataType.BYTE -> {
|
||||||
val v = byteval!!.toInt()
|
val v = byteval!!.toInt()
|
||||||
val newCarry = v and 1 != 0
|
val newCarry = v and 1 != 0
|
||||||
val newval = (v ushr 1) or (if(carry) 0x80 else 0)
|
val newval = (v ushr 1) or (if (carry) 0x80 else 0)
|
||||||
Pair(RuntimeValue(DataType.UBYTE, newval), newCarry)
|
Pair(RuntimeValueNumeric(DataType.UBYTE, newval), newCarry)
|
||||||
}
|
}
|
||||||
DataType.UWORD, DataType.WORD -> {
|
DataType.UWORD, DataType.WORD -> {
|
||||||
val v = wordval!!
|
val v = wordval!!
|
||||||
val newCarry = v and 1 != 0
|
val newCarry = v and 1 != 0
|
||||||
val newval = (v ushr 1) or (if(carry) 0x8000 else 0)
|
val newval = (v ushr 1) or (if (carry) 0x8000 else 0)
|
||||||
Pair(RuntimeValue(DataType.UWORD, newval), newCarry)
|
Pair(RuntimeValueNumeric(DataType.UWORD, newval), newCarry)
|
||||||
}
|
}
|
||||||
else -> throw ArithmeticException("ror2 can only work on byte/word")
|
else -> throw ArithmeticException("ror2 can only work on byte/word")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun rol2(): RuntimeValue {
|
fun rol2(): RuntimeValueNumeric {
|
||||||
// 8 or 16 bit rotate left
|
// 8 or 16 bit rotate left
|
||||||
return when(type) {
|
return when (type) {
|
||||||
DataType.UBYTE, DataType.BYTE -> {
|
DataType.UBYTE, DataType.BYTE -> {
|
||||||
val v = byteval!!.toInt()
|
val v = byteval!!.toInt()
|
||||||
val carry = (v and 0x80) ushr 7
|
val carry = (v and 0x80) ushr 7
|
||||||
val newval = (v and 0x7f shl 1) or carry
|
val newval = (v and 0x7f shl 1) or carry
|
||||||
RuntimeValue(DataType.UBYTE, newval)
|
RuntimeValueNumeric(DataType.UBYTE, newval)
|
||||||
}
|
}
|
||||||
DataType.UWORD, DataType.WORD -> {
|
DataType.UWORD, DataType.WORD -> {
|
||||||
val v = wordval!!
|
val v = wordval!!
|
||||||
val carry = (v and 0x8000) ushr 15
|
val carry = (v and 0x8000) ushr 15
|
||||||
val newval = (v and 0x7fff shl 1) or carry
|
val newval = (v and 0x7fff shl 1) or carry
|
||||||
RuntimeValue(DataType.UWORD, newval)
|
RuntimeValueNumeric(DataType.UWORD, newval)
|
||||||
}
|
}
|
||||||
else -> throw ArithmeticException("rol2 can only work on byte/word")
|
else -> throw ArithmeticException("rol2 can only work on byte/word")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ror2(): RuntimeValue {
|
fun ror2(): RuntimeValueNumeric {
|
||||||
// 8 or 16 bit rotate right
|
// 8 or 16 bit rotate right
|
||||||
return when(type) {
|
return when (type) {
|
||||||
DataType.UBYTE, DataType.BYTE -> {
|
DataType.UBYTE, DataType.BYTE -> {
|
||||||
val v = byteval!!.toInt()
|
val v = byteval!!.toInt()
|
||||||
val carry = v and 1 shl 7
|
val carry = v and 1 shl 7
|
||||||
val newval = (v ushr 1) or carry
|
val newval = (v ushr 1) or carry
|
||||||
RuntimeValue(DataType.UBYTE, newval)
|
RuntimeValueNumeric(DataType.UBYTE, newval)
|
||||||
}
|
}
|
||||||
DataType.UWORD, DataType.WORD -> {
|
DataType.UWORD, DataType.WORD -> {
|
||||||
val v = wordval!!
|
val v = wordval!!
|
||||||
val carry = v and 1 shl 15
|
val carry = v and 1 shl 15
|
||||||
val newval = (v ushr 1) or carry
|
val newval = (v ushr 1) or carry
|
||||||
RuntimeValue(DataType.UWORD, newval)
|
RuntimeValueNumeric(DataType.UWORD, newval)
|
||||||
}
|
}
|
||||||
else -> throw ArithmeticException("ror2 can only work on byte/word")
|
else -> throw ArithmeticException("ror2 can only work on byte/word")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun neg(): RuntimeValue {
|
fun neg(): RuntimeValueNumeric {
|
||||||
return when(type) {
|
return when (type) {
|
||||||
DataType.BYTE -> RuntimeValue(DataType.BYTE, -(byteval!!))
|
DataType.BYTE -> RuntimeValueNumeric(DataType.BYTE, -(byteval!!))
|
||||||
DataType.WORD -> RuntimeValue(DataType.WORD, -(wordval!!))
|
DataType.WORD -> RuntimeValueNumeric(DataType.WORD, -(wordval!!))
|
||||||
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, -(floatval)!!)
|
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, -(floatval)!!)
|
||||||
else -> throw ArithmeticException("neg can only work on byte/word/float")
|
else -> throw ArithmeticException("neg can only work on byte/word/float")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun abs(): RuntimeValue {
|
fun abs(): RuntimeValueNumeric {
|
||||||
return when(type) {
|
return when (type) {
|
||||||
DataType.BYTE -> RuntimeValue(DataType.BYTE, abs(byteval!!.toInt()))
|
DataType.BYTE -> RuntimeValueNumeric(DataType.BYTE, abs(byteval!!.toInt()))
|
||||||
DataType.WORD -> RuntimeValue(DataType.WORD, abs(wordval!!))
|
DataType.WORD -> RuntimeValueNumeric(DataType.WORD, abs(wordval!!))
|
||||||
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, abs(floatval!!))
|
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, abs(floatval!!))
|
||||||
else -> throw ArithmeticException("abs can only work on byte/word/float")
|
else -> throw ArithmeticException("abs can only work on byte/word/float")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bitand(other: RuntimeValue): RuntimeValue {
|
fun bitand(other: RuntimeValueNumeric): RuntimeValueNumeric {
|
||||||
val v1 = integerValue()
|
val v1 = integerValue()
|
||||||
val v2 = other.integerValue()
|
val v2 = other.integerValue()
|
||||||
val result = v1 and v2
|
val result = v1 and v2
|
||||||
return RuntimeValue(type, result)
|
return RuntimeValueNumeric(type, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bitor(other: RuntimeValue): RuntimeValue {
|
fun bitor(other: RuntimeValueNumeric): RuntimeValueNumeric {
|
||||||
val v1 = integerValue()
|
val v1 = integerValue()
|
||||||
val v2 = other.integerValue()
|
val v2 = other.integerValue()
|
||||||
val result = v1 or v2
|
val result = v1 or v2
|
||||||
return RuntimeValue(type, result)
|
return RuntimeValueNumeric(type, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bitxor(other: RuntimeValue): RuntimeValue {
|
fun bitxor(other: RuntimeValueNumeric): RuntimeValueNumeric {
|
||||||
val v1 = integerValue()
|
val v1 = integerValue()
|
||||||
val v2 = other.integerValue()
|
val v2 = other.integerValue()
|
||||||
val result = v1 xor v2
|
val result = v1 xor v2
|
||||||
return RuntimeValue(type, result)
|
return RuntimeValueNumeric(type, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun and(other: RuntimeValue) = RuntimeValue(DataType.UBYTE, if (this.asBoolean && other.asBoolean) 1 else 0)
|
fun and(other: RuntimeValueNumeric) = RuntimeValueNumeric(DataType.UBYTE, if (this.asBoolean && other.asBoolean) 1 else 0)
|
||||||
fun or(other: RuntimeValue) = RuntimeValue(DataType.UBYTE, if (this.asBoolean || other.asBoolean) 1 else 0)
|
fun or(other: RuntimeValueNumeric) = RuntimeValueNumeric(DataType.UBYTE, if (this.asBoolean || other.asBoolean) 1 else 0)
|
||||||
fun xor(other: RuntimeValue) = RuntimeValue(DataType.UBYTE, if (this.asBoolean xor other.asBoolean) 1 else 0)
|
fun xor(other: RuntimeValueNumeric) = RuntimeValueNumeric(DataType.UBYTE, if (this.asBoolean xor other.asBoolean) 1 else 0)
|
||||||
fun not() = RuntimeValue(DataType.UBYTE, if (this.asBoolean) 0 else 1)
|
fun not() = RuntimeValueNumeric(DataType.UBYTE, if (this.asBoolean) 0 else 1)
|
||||||
|
|
||||||
fun inv(): RuntimeValue {
|
fun inv(): RuntimeValueNumeric {
|
||||||
return when(type) {
|
return when (type) {
|
||||||
DataType.UBYTE -> RuntimeValue(type, byteval!!.toInt().inv() and 255)
|
DataType.UBYTE -> RuntimeValueNumeric(type, byteval!!.toInt().inv() and 255)
|
||||||
DataType.UWORD -> RuntimeValue(type, wordval!!.inv() and 65535)
|
DataType.UWORD -> RuntimeValueNumeric(type, wordval!!.inv() and 65535)
|
||||||
DataType.BYTE -> RuntimeValue(type, byteval!!.toInt().inv())
|
DataType.BYTE -> RuntimeValueNumeric(type, byteval!!.toInt().inv())
|
||||||
DataType.WORD -> RuntimeValue(type, wordval!!.inv())
|
DataType.WORD -> RuntimeValueNumeric(type, wordval!!.inv())
|
||||||
else -> throw ArithmeticException("inv can only work on byte/word")
|
else -> throw ArithmeticException("inv can only work on byte/word")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun inc(): RuntimeValue {
|
fun inc(): RuntimeValueNumeric {
|
||||||
return when(type) {
|
return when (type) {
|
||||||
DataType.UBYTE -> RuntimeValue(type, (byteval!! + 1) and 255)
|
DataType.UBYTE -> RuntimeValueNumeric(type, (byteval!! + 1) and 255)
|
||||||
DataType.UWORD -> RuntimeValue(type, (wordval!! + 1) and 65535)
|
DataType.UWORD -> RuntimeValueNumeric(type, (wordval!! + 1) and 65535)
|
||||||
DataType.BYTE -> {
|
DataType.BYTE -> {
|
||||||
val newval = byteval!! + 1
|
val newval = byteval!! + 1
|
||||||
if(newval == 128)
|
if (newval == 128)
|
||||||
RuntimeValue(type, -128)
|
RuntimeValueNumeric(type, -128)
|
||||||
else
|
else
|
||||||
RuntimeValue(type, newval)
|
RuntimeValueNumeric(type, newval)
|
||||||
}
|
}
|
||||||
DataType.WORD -> {
|
DataType.WORD -> {
|
||||||
val newval = wordval!! + 1
|
val newval = wordval!! + 1
|
||||||
if(newval == 32768)
|
if (newval == 32768)
|
||||||
RuntimeValue(type, -32768)
|
RuntimeValueNumeric(type, -32768)
|
||||||
else
|
else
|
||||||
RuntimeValue(type, newval)
|
RuntimeValueNumeric(type, newval)
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, floatval!! + 1)
|
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, floatval!! + 1)
|
||||||
else -> throw ArithmeticException("inc can only work on numeric types")
|
else -> throw ArithmeticException("inc can only work on numeric types")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun dec(): RuntimeValue {
|
fun dec(): RuntimeValueNumeric {
|
||||||
return when(type) {
|
return when (type) {
|
||||||
DataType.UBYTE -> RuntimeValue(type, (byteval!! - 1) and 255)
|
DataType.UBYTE -> RuntimeValueNumeric(type, (byteval!! - 1) and 255)
|
||||||
DataType.UWORD -> RuntimeValue(type, (wordval!! - 1) and 65535)
|
DataType.UWORD -> RuntimeValueNumeric(type, (wordval!! - 1) and 65535)
|
||||||
DataType.BYTE -> {
|
DataType.BYTE -> {
|
||||||
val newval = byteval!! - 1
|
val newval = byteval!! - 1
|
||||||
if(newval == -129)
|
if (newval == -129)
|
||||||
RuntimeValue(type, 127)
|
RuntimeValueNumeric(type, 127)
|
||||||
else
|
else
|
||||||
RuntimeValue(type, newval)
|
RuntimeValueNumeric(type, newval)
|
||||||
}
|
}
|
||||||
DataType.WORD -> {
|
DataType.WORD -> {
|
||||||
val newval = wordval!! - 1
|
val newval = wordval!! - 1
|
||||||
if(newval == -32769)
|
if (newval == -32769)
|
||||||
RuntimeValue(type, 32767)
|
RuntimeValueNumeric(type, 32767)
|
||||||
else
|
else
|
||||||
RuntimeValue(type, newval)
|
RuntimeValueNumeric(type, newval)
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, floatval!! - 1)
|
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, floatval!! - 1)
|
||||||
else -> throw ArithmeticException("dec can only work on numeric types")
|
else -> throw ArithmeticException("dec can only work on numeric types")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun msb(): RuntimeValue {
|
fun msb(): RuntimeValueNumeric {
|
||||||
return when(type) {
|
return when (type) {
|
||||||
in ByteDatatypes -> RuntimeValue(DataType.UBYTE, 0)
|
in ByteDatatypes -> RuntimeValueNumeric(DataType.UBYTE, 0)
|
||||||
in WordDatatypes -> RuntimeValue(DataType.UBYTE, wordval!! ushr 8 and 255)
|
in WordDatatypes -> RuntimeValueNumeric(DataType.UBYTE, wordval!! ushr 8 and 255)
|
||||||
else -> throw ArithmeticException("msb can only work on (u)byte/(u)word")
|
else -> throw ArithmeticException("msb can only work on (u)byte/(u)word")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cast(targetType: DataType): RuntimeValue {
|
fun cast(targetType: DataType): RuntimeValueNumeric {
|
||||||
return when (type) {
|
return when (type) {
|
||||||
DataType.UBYTE -> {
|
DataType.UBYTE -> {
|
||||||
when (targetType) {
|
when (targetType) {
|
||||||
DataType.UBYTE -> this
|
DataType.UBYTE -> this
|
||||||
DataType.BYTE -> {
|
DataType.BYTE -> {
|
||||||
val nval=byteval!!.toInt()
|
val nval = byteval!!.toInt()
|
||||||
if(nval<128)
|
if (nval < 128)
|
||||||
RuntimeValue(DataType.BYTE, nval)
|
RuntimeValueNumeric(DataType.BYTE, nval)
|
||||||
else
|
else
|
||||||
RuntimeValue(DataType.BYTE, nval-256)
|
RuntimeValueNumeric(DataType.BYTE, nval - 256)
|
||||||
}
|
}
|
||||||
DataType.UWORD -> RuntimeValue(DataType.UWORD, numericValue())
|
DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, numericValue())
|
||||||
DataType.WORD -> {
|
DataType.WORD -> {
|
||||||
val nval = numericValue().toInt()
|
val nval = numericValue().toInt()
|
||||||
if(nval<32768)
|
if (nval < 32768)
|
||||||
RuntimeValue(DataType.WORD, nval)
|
RuntimeValueNumeric(DataType.WORD, nval)
|
||||||
else
|
else
|
||||||
RuntimeValue(DataType.WORD, nval-65536)
|
RuntimeValueNumeric(DataType.WORD, nval - 65536)
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, numericValue())
|
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, numericValue())
|
||||||
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
|
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.BYTE -> {
|
DataType.BYTE -> {
|
||||||
when (targetType) {
|
when (targetType) {
|
||||||
DataType.BYTE -> this
|
DataType.BYTE -> this
|
||||||
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, integerValue() and 255)
|
DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, integerValue() and 255)
|
||||||
DataType.UWORD -> RuntimeValue(DataType.UWORD, integerValue() and 65535)
|
DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, integerValue() and 65535)
|
||||||
DataType.WORD -> RuntimeValue(DataType.WORD, integerValue())
|
DataType.WORD -> RuntimeValueNumeric(DataType.WORD, integerValue())
|
||||||
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, numericValue())
|
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, numericValue())
|
||||||
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
|
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.UWORD -> {
|
DataType.UWORD -> {
|
||||||
when (targetType) {
|
when (targetType) {
|
||||||
DataType.BYTE -> {
|
DataType.BYTE -> {
|
||||||
val v=integerValue()
|
val v = integerValue()
|
||||||
if(v<128)
|
if (v < 128)
|
||||||
RuntimeValue(DataType.BYTE, v)
|
RuntimeValueNumeric(DataType.BYTE, v)
|
||||||
else
|
else
|
||||||
RuntimeValue(DataType.BYTE, v-256)
|
RuntimeValueNumeric(DataType.BYTE, v - 256)
|
||||||
}
|
}
|
||||||
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, integerValue() and 255)
|
DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, integerValue() and 255)
|
||||||
DataType.UWORD -> this
|
DataType.UWORD -> this
|
||||||
DataType.WORD -> {
|
DataType.WORD -> {
|
||||||
val v=integerValue()
|
val v = integerValue()
|
||||||
if(v<32768)
|
if (v < 32768)
|
||||||
RuntimeValue(DataType.WORD, v)
|
RuntimeValueNumeric(DataType.WORD, v)
|
||||||
else
|
else
|
||||||
RuntimeValue(DataType.WORD, v-65536)
|
RuntimeValueNumeric(DataType.WORD, v - 65536)
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, numericValue())
|
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, numericValue())
|
||||||
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
|
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -585,33 +531,33 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
|
|||||||
when (targetType) {
|
when (targetType) {
|
||||||
DataType.BYTE -> {
|
DataType.BYTE -> {
|
||||||
val v = integerValue() and 255
|
val v = integerValue() and 255
|
||||||
if(v<128)
|
if (v < 128)
|
||||||
RuntimeValue(DataType.BYTE, v)
|
RuntimeValueNumeric(DataType.BYTE, v)
|
||||||
else
|
else
|
||||||
RuntimeValue(DataType.BYTE, v-256)
|
RuntimeValueNumeric(DataType.BYTE, v - 256)
|
||||||
}
|
}
|
||||||
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, integerValue() and 65535)
|
DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, integerValue() and 65535)
|
||||||
DataType.UWORD -> RuntimeValue(DataType.UWORD, integerValue())
|
DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, integerValue())
|
||||||
DataType.WORD -> this
|
DataType.WORD -> this
|
||||||
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, numericValue())
|
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, numericValue())
|
||||||
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
|
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
when (targetType) {
|
when (targetType) {
|
||||||
DataType.BYTE -> {
|
DataType.BYTE -> {
|
||||||
val integer=numericValue().toInt()
|
val integer = numericValue().toInt()
|
||||||
if(integer in -128..127)
|
if (integer in -128..127)
|
||||||
RuntimeValue(DataType.BYTE, integer)
|
RuntimeValueNumeric(DataType.BYTE, integer)
|
||||||
else
|
else
|
||||||
throw ArithmeticException("overflow when casting float to byte: $this")
|
throw ArithmeticException("overflow when casting float to byte: $this")
|
||||||
}
|
}
|
||||||
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, numericValue().toInt())
|
DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, numericValue().toInt())
|
||||||
DataType.UWORD -> RuntimeValue(DataType.UWORD, numericValue().toInt())
|
DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, numericValue().toInt())
|
||||||
DataType.WORD -> {
|
DataType.WORD -> {
|
||||||
val integer=numericValue().toInt()
|
val integer = numericValue().toInt()
|
||||||
if(integer in -32768..32767)
|
if (integer in -32768..32767)
|
||||||
RuntimeValue(DataType.WORD, integer)
|
RuntimeValueNumeric(DataType.WORD, integer)
|
||||||
else
|
else
|
||||||
throw ArithmeticException("overflow when casting float to word: $this")
|
throw ArithmeticException("overflow when casting float to word: $this")
|
||||||
}
|
}
|
||||||
@ -622,22 +568,97 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
|
|||||||
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
|
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
open fun iterator(): Iterator<Number> {
|
|
||||||
return when (type) {
|
class RuntimeValueString(type: DataType, val str: String, val heapId: Int?): RuntimeValueBase(type) {
|
||||||
in StringDatatypes -> {
|
companion object {
|
||||||
Petscii.encodePetscii(str!!, true).iterator()
|
fun fromLv(string: StringLiteralValue): RuntimeValueString {
|
||||||
}
|
return RuntimeValueString(string.type, string.value, string.heapId!!)
|
||||||
in ArrayDatatypes -> {
|
|
||||||
array!!.iterator()
|
|
||||||
}
|
|
||||||
else -> throw IllegalArgumentException("cannot iterate over $this")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return when (type) {
|
||||||
|
DataType.STR -> "str:$str"
|
||||||
|
DataType.STR_S -> "str_s:$str"
|
||||||
|
else -> "???"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int = Objects.hash(type, str)
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (other == null || other !is RuntimeValueString)
|
||||||
|
return false
|
||||||
|
return type == other.type && str == other.str
|
||||||
|
}
|
||||||
|
|
||||||
|
fun iterator(): Iterator<Number> = Petscii.encodePetscii(str, true).iterator()
|
||||||
|
|
||||||
|
override fun numericValue(): Number {
|
||||||
|
throw VmExecutionException("string is not a number")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun integerValue(): Int {
|
||||||
|
throw VmExecutionException("string is not a number")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class RuntimeValueRange(type: DataType, val range: IntProgression): RuntimeValue(type, 0) {
|
open class RuntimeValueArray(type: DataType, val array: Array<Number>, val heapId: Int?): RuntimeValueBase(type) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromLv(array: ArrayLiteralValue): RuntimeValueArray {
|
||||||
|
return if (array.type == DataType.ARRAY_F) {
|
||||||
|
val doubleArray = array.value.map { (it as NumericLiteralValue).number }.toTypedArray()
|
||||||
|
RuntimeValueArray(array.type, doubleArray, array.heapId!!)
|
||||||
|
} else {
|
||||||
|
val resultArray = mutableListOf<Number>()
|
||||||
|
for (elt in array.value.withIndex()) {
|
||||||
|
if (elt.value is NumericLiteralValue)
|
||||||
|
resultArray.add((elt.value as NumericLiteralValue).number.toInt())
|
||||||
|
else {
|
||||||
|
TODO("ADDRESSOF ${elt.value}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RuntimeValueArray(array.type, resultArray.toTypedArray(), array.heapId!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return when (type) {
|
||||||
|
DataType.ARRAY_UB -> "array_ub:..."
|
||||||
|
DataType.ARRAY_B -> "array_b:..."
|
||||||
|
DataType.ARRAY_UW -> "array_uw:..."
|
||||||
|
DataType.ARRAY_W -> "array_w:..."
|
||||||
|
DataType.ARRAY_F -> "array_f:..."
|
||||||
|
else -> "???"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int = Objects.hash(type, array)
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (other == null || other !is RuntimeValueArray)
|
||||||
|
return false
|
||||||
|
return type == other.type && array.contentEquals(other.array)
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun iterator(): Iterator<Number> = array.iterator()
|
||||||
|
|
||||||
|
override fun numericValue(): Number {
|
||||||
|
throw VmExecutionException("array is not a number")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun integerValue(): Int {
|
||||||
|
throw VmExecutionException("array is not a number")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class RuntimeValueRange(type: DataType, val range: IntProgression): RuntimeValueArray(type, range.toList().toTypedArray(), null) {
|
||||||
override fun iterator(): Iterator<Number> {
|
override fun iterator(): Iterator<Number> {
|
||||||
return range.iterator()
|
return range.iterator()
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
package prog8.vm.astvm
|
package prog8.vm.astvm
|
||||||
|
|
||||||
import prog8.ast.*
|
import prog8.ast.INameScope
|
||||||
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.base.initvarsSubName
|
import prog8.ast.expressions.Expression
|
||||||
import prog8.ast.expressions.IdentifierReference
|
import prog8.ast.expressions.IdentifierReference
|
||||||
import prog8.ast.expressions.LiteralValue
|
import prog8.ast.expressions.NumericLiteralValue
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.compiler.target.c64.Mflpt5
|
import prog8.compiler.target.c64.MachineDefinition
|
||||||
import prog8.vm.RuntimeValue
|
|
||||||
import prog8.vm.RuntimeValueRange
|
|
||||||
import prog8.compiler.target.c64.Petscii
|
import prog8.compiler.target.c64.Petscii
|
||||||
|
import prog8.vm.*
|
||||||
import java.awt.EventQueue
|
import java.awt.EventQueue
|
||||||
import java.io.CharConversionException
|
import java.io.CharConversionException
|
||||||
import java.util.*
|
import java.util.ArrayDeque
|
||||||
import kotlin.NoSuchElementException
|
import kotlin.NoSuchElementException
|
||||||
import kotlin.concurrent.fixedRateTimer
|
import kotlin.concurrent.fixedRateTimer
|
||||||
import kotlin.math.*
|
import kotlin.math.*
|
||||||
@ -32,31 +32,31 @@ class StatusFlags {
|
|||||||
var negative: Boolean = false
|
var negative: Boolean = false
|
||||||
var irqd: Boolean = false
|
var irqd: Boolean = false
|
||||||
|
|
||||||
private fun setFlags(value: LiteralValue?) {
|
private fun setFlags(value: NumericLiteralValue?) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
when (value.type) {
|
when (value.type) {
|
||||||
DataType.UBYTE -> {
|
DataType.UBYTE -> {
|
||||||
val v = value.bytevalue!!.toInt()
|
val v = value.number.toInt()
|
||||||
negative = v > 127
|
negative = v > 127
|
||||||
zero = v == 0
|
zero = v == 0
|
||||||
}
|
}
|
||||||
DataType.BYTE -> {
|
DataType.BYTE -> {
|
||||||
val v = value.bytevalue!!.toInt()
|
val v = value.number.toInt()
|
||||||
negative = v < 0
|
negative = v < 0
|
||||||
zero = v == 0
|
zero = v == 0
|
||||||
}
|
}
|
||||||
DataType.UWORD -> {
|
DataType.UWORD -> {
|
||||||
val v = value.wordvalue!!
|
val v = value.number.toInt()
|
||||||
negative = v > 32767
|
negative = v > 32767
|
||||||
zero = v == 0
|
zero = v == 0
|
||||||
}
|
}
|
||||||
DataType.WORD -> {
|
DataType.WORD -> {
|
||||||
val v = value.wordvalue!!
|
val v = value.number.toInt()
|
||||||
negative = v < 0
|
negative = v < 0
|
||||||
zero = v == 0
|
zero = v == 0
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
val flt = value.floatvalue!!
|
val flt = value.number.toDouble()
|
||||||
negative = flt < 0.0
|
negative = flt < 0.0
|
||||||
zero = flt == 0.0
|
zero = flt == 0.0
|
||||||
}
|
}
|
||||||
@ -70,10 +70,14 @@ class StatusFlags {
|
|||||||
|
|
||||||
|
|
||||||
class RuntimeVariables {
|
class RuntimeVariables {
|
||||||
fun define(scope: INameScope, name: String, initialValue: RuntimeValue) {
|
fun define(scope: INameScope, name: String, initialValue: RuntimeValueBase) {
|
||||||
val where = vars.getValue(scope)
|
val where = vars.getValue(scope)
|
||||||
where[name] = initialValue
|
where[name] = initialValue
|
||||||
vars[scope] = where
|
vars[scope] = where
|
||||||
|
if(initialValue is RuntimeValueString)
|
||||||
|
byHeapId[initialValue.heapId!!] = initialValue
|
||||||
|
else if(initialValue is RuntimeValueArray)
|
||||||
|
byHeapId[initialValue.heapId!!] = initialValue
|
||||||
}
|
}
|
||||||
|
|
||||||
fun defineMemory(scope: INameScope, name: String, address: Int) {
|
fun defineMemory(scope: INameScope, name: String, address: Int) {
|
||||||
@ -82,7 +86,7 @@ class RuntimeVariables {
|
|||||||
memvars[scope] = where
|
memvars[scope] = where
|
||||||
}
|
}
|
||||||
|
|
||||||
fun set(scope: INameScope, name: String, value: RuntimeValue) {
|
fun set(scope: INameScope, name: String, value: RuntimeValueBase) {
|
||||||
val where = vars.getValue(scope)
|
val where = vars.getValue(scope)
|
||||||
val existing = where[name]
|
val existing = where[name]
|
||||||
if(existing==null) {
|
if(existing==null) {
|
||||||
@ -94,9 +98,15 @@ class RuntimeVariables {
|
|||||||
throw VmExecutionException("new value is of different datatype ${value.type} expected ${existing.type} for $name")
|
throw VmExecutionException("new value is of different datatype ${value.type} expected ${existing.type} for $name")
|
||||||
where[name] = value
|
where[name] = value
|
||||||
vars[scope] = where
|
vars[scope] = where
|
||||||
|
if(value is RuntimeValueString)
|
||||||
|
byHeapId[value.heapId!!] = value
|
||||||
|
else if(value is RuntimeValueArray)
|
||||||
|
byHeapId[value.heapId!!] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
fun get(scope: INameScope, name: String): RuntimeValue {
|
fun getByHeapId(heapId: Int): RuntimeValueBase = byHeapId.getValue(heapId)
|
||||||
|
|
||||||
|
fun get(scope: INameScope, name: String): RuntimeValueBase {
|
||||||
val where = vars.getValue(scope)
|
val where = vars.getValue(scope)
|
||||||
return where[name] ?: throw NoSuchElementException("no such runtime variable: ${scope.name}.$name")
|
return where[name] ?: throw NoSuchElementException("no such runtime variable: ${scope.name}.$name")
|
||||||
}
|
}
|
||||||
@ -106,8 +116,6 @@ class RuntimeVariables {
|
|||||||
return where[name] ?: throw NoSuchElementException("no such runtime memory-variable: ${scope.name}.$name")
|
return where[name] ?: throw NoSuchElementException("no such runtime memory-variable: ${scope.name}.$name")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun swap(a1: VarDecl, a2: VarDecl) = swap(a1.definingScope(), a1.name, a2.definingScope(), a2.name)
|
|
||||||
|
|
||||||
fun swap(scope1: INameScope, name1: String, scope2: INameScope, name2: String) {
|
fun swap(scope1: INameScope, name1: String, scope2: INameScope, name2: String) {
|
||||||
val v1 = get(scope1, name1)
|
val v1 = get(scope1, name1)
|
||||||
val v2 = get(scope2, name2)
|
val v2 = get(scope2, name2)
|
||||||
@ -115,8 +123,9 @@ class RuntimeVariables {
|
|||||||
set(scope2, name2, v1)
|
set(scope2, name2, v1)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val vars = mutableMapOf<INameScope, MutableMap<String, RuntimeValue>>().withDefault { mutableMapOf() }
|
private val vars = mutableMapOf<INameScope, MutableMap<String, RuntimeValueBase>>().withDefault { mutableMapOf() }
|
||||||
private val memvars = mutableMapOf<INameScope, MutableMap<String, Int>>().withDefault { mutableMapOf() }
|
private val memvars = mutableMapOf<INameScope, MutableMap<String, Int>>().withDefault { mutableMapOf() }
|
||||||
|
private val byHeapId = mutableMapOf<Int, RuntimeValueBase>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -131,10 +140,10 @@ class AstVm(val program: Program) {
|
|||||||
var rtcOffset = bootTime
|
var rtcOffset = bootTime
|
||||||
|
|
||||||
private val rnd = Random(0)
|
private val rnd = Random(0)
|
||||||
private val statusFlagsSave = Stack<StatusFlags>()
|
private val statusFlagsSave = ArrayDeque<StatusFlags>()
|
||||||
private val registerXsave = Stack<RuntimeValue>()
|
private val registerXsave = ArrayDeque<RuntimeValueNumeric>()
|
||||||
private val registerYsave = Stack<RuntimeValue>()
|
private val registerYsave = ArrayDeque<RuntimeValueNumeric>()
|
||||||
private val registerAsave = Stack<RuntimeValue>()
|
private val registerAsave = ArrayDeque<RuntimeValueNumeric>()
|
||||||
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@ -164,10 +173,10 @@ class AstVm(val program: Program) {
|
|||||||
fun memwrite(address: Int, value: Short): Short {
|
fun memwrite(address: Int, value: Short): Short {
|
||||||
if(address==0xa0 || address==0xa1 || address==0xa2) {
|
if(address==0xa0 || address==0xa1 || address==0xa2) {
|
||||||
// a write to the jiffy clock, update the clock offset for the irq
|
// a write to the jiffy clock, update the clock offset for the irq
|
||||||
val time_hi = if(address==0xa0) value else mem.getUByte_DMA(0xa0)
|
val timeHi = if(address==0xa0) value else mem.getUByteDirectly(0xa0)
|
||||||
val time_mid = if(address==0xa1) value else mem.getUByte_DMA(0xa1)
|
val timeMid = if(address==0xa1) value else mem.getUByteDirectly(0xa1)
|
||||||
val time_lo = if(address==0xa2) value else mem.getUByte_DMA(0xa2)
|
val timeLo = if(address==0xa2) value else mem.getUByteDirectly(0xa2)
|
||||||
val jiffies = (time_hi.toInt() shl 16) + (time_mid.toInt() shl 8) + time_lo
|
val jiffies = (timeHi.toInt() shl 16) + (timeMid.toInt() shl 8) + timeLo
|
||||||
rtcOffset = bootTime - (jiffies*1000/60)
|
rtcOffset = bootTime - (jiffies*1000/60)
|
||||||
}
|
}
|
||||||
if(address in 1024..2023) {
|
if(address in 1024..2023) {
|
||||||
@ -180,7 +189,7 @@ class AstVm(val program: Program) {
|
|||||||
|
|
||||||
fun run() {
|
fun run() {
|
||||||
try {
|
try {
|
||||||
val init = VariablesCreator(runtimeVariables, program.heap)
|
val init = VariablesCreator(runtimeVariables)
|
||||||
init.visit(program)
|
init.visit(program)
|
||||||
|
|
||||||
// initialize all global variables
|
// initialize all global variables
|
||||||
@ -251,9 +260,9 @@ class AstVm(val program: Program) {
|
|||||||
rtcOffset = timeStamp
|
rtcOffset = timeStamp
|
||||||
}
|
}
|
||||||
// update the C-64 60hz jiffy clock in the ZP addresses:
|
// update the C-64 60hz jiffy clock in the ZP addresses:
|
||||||
mem.setUByte_DMA(0x00a0, (jiffies ushr 16).toShort())
|
mem.setUByteDirectly(0x00a0, (jiffies ushr 16).toShort())
|
||||||
mem.setUByte_DMA(0x00a1, (jiffies ushr 8 and 255).toShort())
|
mem.setUByteDirectly(0x00a1, (jiffies ushr 8 and 255).toShort())
|
||||||
mem.setUByte_DMA(0x00a2, (jiffies and 255).toShort())
|
mem.setUByteDirectly(0x00a2, (jiffies and 255).toShort())
|
||||||
}
|
}
|
||||||
|
|
||||||
private val runtimeVariables = RuntimeVariables()
|
private val runtimeVariables = RuntimeVariables()
|
||||||
@ -261,11 +270,11 @@ class AstVm(val program: Program) {
|
|||||||
|
|
||||||
class LoopControlBreak : Exception()
|
class LoopControlBreak : Exception()
|
||||||
class LoopControlContinue : Exception()
|
class LoopControlContinue : Exception()
|
||||||
class LoopControlReturn(val returnvalue: RuntimeValue?) : Exception()
|
class LoopControlReturn(val returnvalue: RuntimeValueNumeric?) : Exception()
|
||||||
class LoopControlJump(val identifier: IdentifierReference?, val address: Int?, val generatedLabel: String?) : Exception()
|
class LoopControlJump(val identifier: IdentifierReference?, val address: Int?, val generatedLabel: String?) : Exception()
|
||||||
|
|
||||||
|
|
||||||
internal fun executeSubroutine(sub: Subroutine, arguments: List<RuntimeValue>, startlabel: Label?=null): RuntimeValue? {
|
internal fun executeSubroutine(sub: Subroutine, arguments: List<RuntimeValueNumeric>, startAtLabel: Label?=null): RuntimeValueNumeric? {
|
||||||
if(sub.isAsmSubroutine) {
|
if(sub.isAsmSubroutine) {
|
||||||
return performSyscall(sub, arguments)
|
return performSyscall(sub, arguments)
|
||||||
}
|
}
|
||||||
@ -282,10 +291,10 @@ class AstVm(val program: Program) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val statements = sub.statements.iterator()
|
val statements = sub.statements.iterator()
|
||||||
if(startlabel!=null) {
|
if(startAtLabel!=null) {
|
||||||
do {
|
do {
|
||||||
val stmt = statements.next()
|
val stmt = statements.next()
|
||||||
} while(stmt!==startlabel)
|
} while(stmt!==startAtLabel)
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -312,7 +321,7 @@ class AstVm(val program: Program) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun executeStatement(sub: INameScope, stmt: IStatement) {
|
private fun executeStatement(sub: INameScope, stmt: Statement) {
|
||||||
instructionCounter++
|
instructionCounter++
|
||||||
if (instructionCounter % 200 == 0)
|
if (instructionCounter % 200 == 0)
|
||||||
Thread.sleep(1)
|
Thread.sleep(1)
|
||||||
@ -333,7 +342,7 @@ class AstVm(val program: Program) {
|
|||||||
val target = stmt.target.targetStatement(program.namespace)
|
val target = stmt.target.targetStatement(program.namespace)
|
||||||
when (target) {
|
when (target) {
|
||||||
is Subroutine -> {
|
is Subroutine -> {
|
||||||
val args = evaluate(stmt.arglist)
|
val args = evaluate(stmt.arglist).map { it as RuntimeValueNumeric }
|
||||||
if (target.isAsmSubroutine) {
|
if (target.isAsmSubroutine) {
|
||||||
performSyscall(target, args)
|
performSyscall(target, args)
|
||||||
} else {
|
} else {
|
||||||
@ -346,7 +355,7 @@ class AstVm(val program: Program) {
|
|||||||
// swap cannot be implemented as a function, so inline it here
|
// swap cannot be implemented as a function, so inline it here
|
||||||
executeSwap(stmt)
|
executeSwap(stmt)
|
||||||
} else {
|
} else {
|
||||||
val args = evaluate(stmt.arglist)
|
val args = evaluate(stmt.arglist).map { it as RuntimeValueNumeric }
|
||||||
performBuiltinFunction(target.name, args, statusflags)
|
performBuiltinFunction(target.name, args, statusflags)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -360,7 +369,7 @@ class AstVm(val program: Program) {
|
|||||||
if(stmt.value==null)
|
if(stmt.value==null)
|
||||||
null
|
null
|
||||||
else
|
else
|
||||||
evaluate(stmt.value!!, evalCtx)
|
evaluate(stmt.value!!, evalCtx) as RuntimeValueNumeric
|
||||||
throw LoopControlReturn(value)
|
throw LoopControlReturn(value)
|
||||||
}
|
}
|
||||||
is Continue -> throw LoopControlContinue()
|
is Continue -> throw LoopControlContinue()
|
||||||
@ -378,16 +387,16 @@ class AstVm(val program: Program) {
|
|||||||
val identScope = ident.definingScope()
|
val identScope = ident.definingScope()
|
||||||
when(ident.type){
|
when(ident.type){
|
||||||
VarDeclType.VAR -> {
|
VarDeclType.VAR -> {
|
||||||
var value = runtimeVariables.get(identScope, ident.name)
|
var value = runtimeVariables.get(identScope, ident.name) as RuntimeValueNumeric
|
||||||
value = when {
|
value = when {
|
||||||
stmt.operator == "++" -> value.add(RuntimeValue(value.type, 1))
|
stmt.operator == "++" -> value.add(RuntimeValueNumeric(value.type, 1))
|
||||||
stmt.operator == "--" -> value.sub(RuntimeValue(value.type, 1))
|
stmt.operator == "--" -> value.sub(RuntimeValueNumeric(value.type, 1))
|
||||||
else -> throw VmExecutionException("strange postincdec operator $stmt")
|
else -> throw VmExecutionException("strange postincdec operator $stmt")
|
||||||
}
|
}
|
||||||
runtimeVariables.set(identScope, ident.name, value)
|
runtimeVariables.set(identScope, ident.name, value)
|
||||||
}
|
}
|
||||||
VarDeclType.MEMORY -> {
|
VarDeclType.MEMORY -> {
|
||||||
val addr=ident.value!!.constValue(program)!!.asIntegerValue!!
|
val addr=ident.value!!.constValue(program)!!.number.toInt()
|
||||||
val newval = when {
|
val newval = when {
|
||||||
stmt.operator == "++" -> mem.getUByte(addr)+1 and 255
|
stmt.operator == "++" -> mem.getUByte(addr)+1 and 255
|
||||||
stmt.operator == "--" -> mem.getUByte(addr)-1 and 255
|
stmt.operator == "--" -> mem.getUByte(addr)-1 and 255
|
||||||
@ -399,7 +408,7 @@ class AstVm(val program: Program) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
stmt.target.memoryAddress != null -> {
|
stmt.target.memoryAddress != null -> {
|
||||||
val addr = evaluate(stmt.target.memoryAddress!!.addressExpression, evalCtx).integerValue()
|
val addr = (evaluate(stmt.target.memoryAddress!!.addressExpression, evalCtx) as RuntimeValueNumeric).integerValue()
|
||||||
val newval = when {
|
val newval = when {
|
||||||
stmt.operator == "++" -> mem.getUByte(addr)+1 and 255
|
stmt.operator == "++" -> mem.getUByte(addr)+1 and 255
|
||||||
stmt.operator == "--" -> mem.getUByte(addr)-1 and 255
|
stmt.operator == "--" -> mem.getUByte(addr)-1 and 255
|
||||||
@ -409,22 +418,24 @@ class AstVm(val program: Program) {
|
|||||||
}
|
}
|
||||||
stmt.target.arrayindexed != null -> {
|
stmt.target.arrayindexed != null -> {
|
||||||
val arrayvar = stmt.target.arrayindexed!!.identifier.targetVarDecl(program.namespace)!!
|
val arrayvar = stmt.target.arrayindexed!!.identifier.targetVarDecl(program.namespace)!!
|
||||||
val arrayvalue = runtimeVariables.get(arrayvar.definingScope(), arrayvar.name)
|
val arrayvalue = runtimeVariables.get(arrayvar.definingScope(), arrayvar.name) as RuntimeValueArray
|
||||||
val elementType = stmt.target.arrayindexed!!.inferType(program)!!
|
val index = (evaluate(stmt.target.arrayindexed!!.arrayspec.index, evalCtx) as RuntimeValueNumeric).integerValue()
|
||||||
val index = evaluate(stmt.target.arrayindexed!!.arrayspec.index, evalCtx).integerValue()
|
val elementType = stmt.target.arrayindexed!!.inferType(program)
|
||||||
var value = RuntimeValue(elementType, arrayvalue.array!![index].toInt())
|
if(!elementType.isKnown)
|
||||||
when {
|
throw VmExecutionException("unknown/void elt type")
|
||||||
stmt.operator == "++" -> value=value.inc()
|
var value = RuntimeValueNumeric(elementType.typeOrElse(DataType.BYTE), arrayvalue.array[index].toInt())
|
||||||
stmt.operator == "--" -> value=value.dec()
|
value = when {
|
||||||
|
stmt.operator == "++" -> value.inc()
|
||||||
|
stmt.operator == "--" -> value.dec()
|
||||||
else -> throw VmExecutionException("strange postincdec operator $stmt")
|
else -> throw VmExecutionException("strange postincdec operator $stmt")
|
||||||
}
|
}
|
||||||
arrayvalue.array[index] = value.numericValue()
|
arrayvalue.array[index] = value.numericValue()
|
||||||
}
|
}
|
||||||
stmt.target.register != null -> {
|
stmt.target.register != null -> {
|
||||||
var value = runtimeVariables.get(program.namespace, stmt.target.register!!.name)
|
var value = runtimeVariables.get(program.namespace, stmt.target.register!!.name) as RuntimeValueNumeric
|
||||||
value = when {
|
value = when {
|
||||||
stmt.operator == "++" -> value.add(RuntimeValue(value.type, 1))
|
stmt.operator == "++" -> value.add(RuntimeValueNumeric(value.type, 1))
|
||||||
stmt.operator == "--" -> value.sub(RuntimeValue(value.type, 1))
|
stmt.operator == "--" -> value.sub(RuntimeValueNumeric(value.type, 1))
|
||||||
else -> throw VmExecutionException("strange postincdec operator $stmt")
|
else -> throw VmExecutionException("strange postincdec operator $stmt")
|
||||||
}
|
}
|
||||||
runtimeVariables.set(program.namespace, stmt.target.register!!.name, value)
|
runtimeVariables.set(program.namespace, stmt.target.register!!.name, value)
|
||||||
@ -435,7 +446,7 @@ class AstVm(val program: Program) {
|
|||||||
is Jump -> throw LoopControlJump(stmt.identifier, stmt.address, stmt.generatedLabel)
|
is Jump -> throw LoopControlJump(stmt.identifier, stmt.address, stmt.generatedLabel)
|
||||||
is InlineAssembly -> {
|
is InlineAssembly -> {
|
||||||
if (sub is Subroutine) {
|
if (sub is Subroutine) {
|
||||||
val args = sub.parameters.map { runtimeVariables.get(sub, it.name) }
|
val args = sub.parameters.map { runtimeVariables.get(sub, it.name) as RuntimeValueNumeric }
|
||||||
performSyscall(sub, args)
|
performSyscall(sub, args)
|
||||||
throw LoopControlReturn(null)
|
throw LoopControlReturn(null)
|
||||||
}
|
}
|
||||||
@ -443,7 +454,7 @@ class AstVm(val program: Program) {
|
|||||||
}
|
}
|
||||||
is AnonymousScope -> executeAnonymousScope(stmt)
|
is AnonymousScope -> executeAnonymousScope(stmt)
|
||||||
is IfStatement -> {
|
is IfStatement -> {
|
||||||
val condition = evaluate(stmt.condition, evalCtx)
|
val condition = evaluate(stmt.condition, evalCtx) as RuntimeValueNumeric
|
||||||
if (condition.asBoolean)
|
if (condition.asBoolean)
|
||||||
executeAnonymousScope(stmt.truepart)
|
executeAnonymousScope(stmt.truepart)
|
||||||
else
|
else
|
||||||
@ -470,10 +481,17 @@ class AstVm(val program: Program) {
|
|||||||
loopvarDt = DataType.UBYTE
|
loopvarDt = DataType.UBYTE
|
||||||
loopvar = IdentifierReference(listOf(stmt.loopRegister.name), stmt.position)
|
loopvar = IdentifierReference(listOf(stmt.loopRegister.name), stmt.position)
|
||||||
} else {
|
} else {
|
||||||
loopvarDt = stmt.loopVar!!.inferType(program)!!
|
val dt = stmt.loopVar!!.inferType(program)
|
||||||
loopvar = stmt.loopVar
|
loopvarDt = dt.typeOrElse(DataType.UBYTE)
|
||||||
|
loopvar = stmt.loopVar!!
|
||||||
}
|
}
|
||||||
val iterator = iterable.iterator()
|
val iterator =
|
||||||
|
when (iterable) {
|
||||||
|
is RuntimeValueRange -> iterable.iterator()
|
||||||
|
is RuntimeValueArray -> iterable.iterator()
|
||||||
|
is RuntimeValueString -> iterable.iterator()
|
||||||
|
else -> throw VmExecutionException("not iterable")
|
||||||
|
}
|
||||||
for (loopvalue in iterator) {
|
for (loopvalue in iterator) {
|
||||||
try {
|
try {
|
||||||
oneForCycle(stmt, loopvarDt, loopvalue, loopvar)
|
oneForCycle(stmt, loopvarDt, loopvalue, loopvar)
|
||||||
@ -485,11 +503,11 @@ class AstVm(val program: Program) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
is WhileLoop -> {
|
is WhileLoop -> {
|
||||||
var condition = evaluate(stmt.condition, evalCtx)
|
var condition = evaluate(stmt.condition, evalCtx) as RuntimeValueNumeric
|
||||||
while (condition.asBoolean) {
|
while (condition.asBoolean) {
|
||||||
try {
|
try {
|
||||||
executeAnonymousScope(stmt.body)
|
executeAnonymousScope(stmt.body)
|
||||||
condition = evaluate(stmt.condition, evalCtx)
|
condition = evaluate(stmt.condition, evalCtx) as RuntimeValueNumeric
|
||||||
} catch (b: LoopControlBreak) {
|
} catch (b: LoopControlBreak) {
|
||||||
break
|
break
|
||||||
} catch (c: LoopControlContinue) {
|
} catch (c: LoopControlContinue) {
|
||||||
@ -499,7 +517,7 @@ class AstVm(val program: Program) {
|
|||||||
}
|
}
|
||||||
is RepeatLoop -> {
|
is RepeatLoop -> {
|
||||||
do {
|
do {
|
||||||
val condition = evaluate(stmt.untilCondition, evalCtx)
|
val condition = evaluate(stmt.untilCondition, evalCtx) as RuntimeValueNumeric
|
||||||
try {
|
try {
|
||||||
executeAnonymousScope(stmt.body)
|
executeAnonymousScope(stmt.body)
|
||||||
} catch (b: LoopControlBreak) {
|
} catch (b: LoopControlBreak) {
|
||||||
@ -510,15 +528,15 @@ class AstVm(val program: Program) {
|
|||||||
} while (!condition.asBoolean)
|
} while (!condition.asBoolean)
|
||||||
}
|
}
|
||||||
is WhenStatement -> {
|
is WhenStatement -> {
|
||||||
val condition=evaluate(stmt.condition, evalCtx)
|
val condition=evaluate(stmt.condition, evalCtx) as RuntimeValueNumeric
|
||||||
for(choice in stmt.choices) {
|
for(choice in stmt.choices) {
|
||||||
if(choice.values==null) {
|
if(choice.values==null) {
|
||||||
// the 'else' choice
|
// the 'else' choice
|
||||||
executeAnonymousScope(choice.statements)
|
executeAnonymousScope(choice.statements)
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
val value = choice.values.single().constValue(evalCtx.program) ?: throw VmExecutionException("can only use const values in when choices ${choice.position}")
|
val value = choice.values!!.single().constValue(evalCtx.program) ?: throw VmExecutionException("can only use const values in when choices ${choice.position}")
|
||||||
val rtval = RuntimeValue.from(value, evalCtx.program.heap)
|
val rtval = RuntimeValueNumeric.fromLv(value)
|
||||||
if(condition==rtval) {
|
if(condition==rtval) {
|
||||||
executeAnonymousScope(choice.statements)
|
executeAnonymousScope(choice.statements)
|
||||||
break
|
break
|
||||||
@ -543,35 +561,37 @@ class AstVm(val program: Program) {
|
|||||||
performAssignment(target2, value1, swap, evalCtx)
|
performAssignment(target2, value1, swap, evalCtx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun performAssignment(target: AssignTarget, value: RuntimeValue, contextStmt: IStatement, evalCtx: EvalContext) {
|
fun performAssignment(target: AssignTarget, value: RuntimeValueBase, contextStmt: Statement, evalCtx: EvalContext) {
|
||||||
|
val targetIdent = target.identifier
|
||||||
|
val targetArrayIndexed = target.arrayindexed
|
||||||
when {
|
when {
|
||||||
target.identifier != null -> {
|
targetIdent != null -> {
|
||||||
val decl = contextStmt.definingScope().lookup(target.identifier.nameInSource, contextStmt) as? VarDecl
|
val decl = contextStmt.definingScope().lookup(targetIdent.nameInSource, contextStmt) as? VarDecl
|
||||||
?: throw VmExecutionException("can't find assignment target ${target.identifier}")
|
?: throw VmExecutionException("can't find assignment target $targetIdent")
|
||||||
if (decl.type == VarDeclType.MEMORY) {
|
if (decl.type == VarDeclType.MEMORY) {
|
||||||
val address = runtimeVariables.getMemoryAddress(decl.definingScope(), decl.name)
|
val address = runtimeVariables.getMemoryAddress(decl.definingScope(), decl.name)
|
||||||
when (decl.datatype) {
|
when (decl.datatype) {
|
||||||
DataType.UBYTE -> mem.setUByte(address, value.byteval!!)
|
DataType.UBYTE -> mem.setUByte(address, (value as RuntimeValueNumeric).byteval!!)
|
||||||
DataType.BYTE -> mem.setSByte(address, value.byteval!!)
|
DataType.BYTE -> mem.setSByte(address, (value as RuntimeValueNumeric).byteval!!)
|
||||||
DataType.UWORD -> mem.setUWord(address, value.wordval!!)
|
DataType.UWORD -> mem.setUWord(address, (value as RuntimeValueNumeric).wordval!!)
|
||||||
DataType.WORD -> mem.setSWord(address, value.wordval!!)
|
DataType.WORD -> mem.setSWord(address, (value as RuntimeValueNumeric).wordval!!)
|
||||||
DataType.FLOAT -> mem.setFloat(address, value.floatval!!)
|
DataType.FLOAT -> mem.setFloat(address, (value as RuntimeValueNumeric).floatval!!)
|
||||||
DataType.STR -> mem.setString(address, value.str!!)
|
DataType.STR -> mem.setString(address, (value as RuntimeValueString).str)
|
||||||
DataType.STR_S -> mem.setScreencodeString(address, value.str!!)
|
DataType.STR_S -> mem.setScreencodeString(address, (value as RuntimeValueString).str)
|
||||||
else -> throw VmExecutionException("weird memaddress type $decl")
|
else -> throw VmExecutionException("weird memaddress type $decl")
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
runtimeVariables.set(decl.definingScope(), decl.name, value)
|
runtimeVariables.set(decl.definingScope(), decl.name, value)
|
||||||
}
|
}
|
||||||
target.memoryAddress != null -> {
|
target.memoryAddress != null -> {
|
||||||
val address = evaluate(target.memoryAddress!!.addressExpression, evalCtx).wordval!!
|
val address = (evaluate(target.memoryAddress.addressExpression, evalCtx) as RuntimeValueNumeric).wordval!!
|
||||||
evalCtx.mem.setUByte(address, value.byteval!!)
|
evalCtx.mem.setUByte(address, (value as RuntimeValueNumeric).byteval!!)
|
||||||
}
|
}
|
||||||
target.arrayindexed != null -> {
|
targetArrayIndexed != null -> {
|
||||||
val vardecl = target.arrayindexed.identifier.targetVarDecl(program.namespace)!!
|
val vardecl = targetArrayIndexed.identifier.targetVarDecl(program.namespace)!!
|
||||||
if(vardecl.type==VarDeclType.VAR) {
|
if(vardecl.type==VarDeclType.VAR) {
|
||||||
val array = evaluate(target.arrayindexed.identifier, evalCtx)
|
val array = evaluate(targetArrayIndexed.identifier, evalCtx)
|
||||||
val index = evaluate(target.arrayindexed.arrayspec.index, evalCtx)
|
val index = evaluate(targetArrayIndexed.arrayspec.index, evalCtx) as RuntimeValueNumeric
|
||||||
when (array.type) {
|
when (array.type) {
|
||||||
DataType.ARRAY_UB -> {
|
DataType.ARRAY_UB -> {
|
||||||
if (value.type != DataType.UBYTE)
|
if (value.type != DataType.UBYTE)
|
||||||
@ -600,28 +620,30 @@ class AstVm(val program: Program) {
|
|||||||
else -> throw VmExecutionException("strange array type ${array.type}")
|
else -> throw VmExecutionException("strange array type ${array.type}")
|
||||||
}
|
}
|
||||||
if (array.type in ArrayDatatypes)
|
if (array.type in ArrayDatatypes)
|
||||||
array.array!![index.integerValue()] = value.numericValue()
|
(array as RuntimeValueArray).array[index.integerValue()] = value.numericValue()
|
||||||
else if (array.type in StringDatatypes) {
|
else if (array.type in StringDatatypes) {
|
||||||
val indexInt = index.integerValue()
|
val indexInt = index.integerValue()
|
||||||
val newchr = Petscii.decodePetscii(listOf(value.numericValue().toShort()), true)
|
val newchr = Petscii.decodePetscii(listOf(value.numericValue().toShort()), true)
|
||||||
val newstr = array.str!!.replaceRange(indexInt, indexInt + 1, newchr)
|
val newstr = (array as RuntimeValueString).str.replaceRange(indexInt, indexInt + 1, newchr)
|
||||||
val ident = contextStmt.definingScope().lookup(target.arrayindexed.identifier.nameInSource, contextStmt) as? VarDecl
|
val ident = contextStmt.definingScope().lookup(targetArrayIndexed.identifier.nameInSource, contextStmt) as? VarDecl
|
||||||
?: throw VmExecutionException("can't find assignment target ${target.identifier}")
|
?: throw VmExecutionException("can't find assignment target ${target.identifier}")
|
||||||
val identScope = ident.definingScope()
|
val identScope = ident.definingScope()
|
||||||
program.heap.update(array.heapId!!, newstr)
|
runtimeVariables.set(identScope, ident.name, RuntimeValueString(array.type, newstr, array.heapId))
|
||||||
runtimeVariables.set(identScope, ident.name, RuntimeValue(array.type, str = newstr, heapId = array.heapId))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
val address = (vardecl.value as LiteralValue).asIntegerValue!!
|
value as RuntimeValueNumeric
|
||||||
val index = evaluate(target.arrayindexed.arrayspec.index, evalCtx).integerValue()
|
val address = (vardecl.value as NumericLiteralValue).number.toInt()
|
||||||
val elementType = target.arrayindexed.inferType(program)!!
|
val index = (evaluate(targetArrayIndexed.arrayspec.index, evalCtx) as RuntimeValueNumeric).integerValue()
|
||||||
when(elementType) {
|
val elementType = targetArrayIndexed.inferType(program)
|
||||||
|
if(!elementType.isKnown)
|
||||||
|
throw VmExecutionException("unknown/void array elt type $targetArrayIndexed")
|
||||||
|
when(elementType.typeOrElse(DataType.UBYTE)) {
|
||||||
DataType.UBYTE -> mem.setUByte(address+index, value.byteval!!)
|
DataType.UBYTE -> mem.setUByte(address+index, value.byteval!!)
|
||||||
DataType.BYTE -> mem.setSByte(address+index, value.byteval!!)
|
DataType.BYTE -> mem.setSByte(address+index, value.byteval!!)
|
||||||
DataType.UWORD -> mem.setUWord(address+index*2, value.wordval!!)
|
DataType.UWORD -> mem.setUWord(address+index*2, value.wordval!!)
|
||||||
DataType.WORD -> mem.setSWord(address+index*2, value.wordval!!)
|
DataType.WORD -> mem.setSWord(address+index*2, value.wordval!!)
|
||||||
DataType.FLOAT -> mem.setFloat(address+index*Mflpt5.MemorySize, value.floatval!!)
|
DataType.FLOAT -> mem.setFloat(address+index* MachineDefinition.Mflpt5.MemorySize, value.floatval!!)
|
||||||
else -> throw VmExecutionException("strange array elt type $elementType")
|
else -> throw VmExecutionException("strange array elt type $elementType")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -636,22 +658,22 @@ class AstVm(val program: Program) {
|
|||||||
private fun oneForCycle(stmt: ForLoop, loopvarDt: DataType, loopValue: Number, loopVar: IdentifierReference) {
|
private fun oneForCycle(stmt: ForLoop, loopvarDt: DataType, loopValue: Number, loopVar: IdentifierReference) {
|
||||||
// assign the new loop value to the loopvar, and run the code
|
// assign the new loop value to the loopvar, and run the code
|
||||||
performAssignment(AssignTarget(null, loopVar, null, null, loopVar.position),
|
performAssignment(AssignTarget(null, loopVar, null, null, loopVar.position),
|
||||||
RuntimeValue(loopvarDt, loopValue), stmt.body.statements.first(), evalCtx)
|
RuntimeValueNumeric(loopvarDt, loopValue), stmt.body.statements.first(), evalCtx)
|
||||||
executeAnonymousScope(stmt.body)
|
executeAnonymousScope(stmt.body)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun evaluate(args: List<IExpression>) = args.map { evaluate(it, evalCtx) }
|
private fun evaluate(args: List<Expression>) = args.map { evaluate(it, evalCtx) }
|
||||||
|
|
||||||
private fun performSyscall(sub: Subroutine, args: List<RuntimeValue>): RuntimeValue? {
|
private fun performSyscall(sub: Subroutine, args: List<RuntimeValueNumeric>): RuntimeValueNumeric? {
|
||||||
var result: RuntimeValue? = null
|
var result: RuntimeValueNumeric? = null
|
||||||
when (sub.scopedname) {
|
when (sub.scopedname) {
|
||||||
"c64scr.print" -> {
|
"c64scr.print" -> {
|
||||||
// if the argument is an UWORD, consider it to be the "address" of the string (=heapId)
|
// if the argument is an UWORD, consider it to be the "address" of the string (=heapId)
|
||||||
if (args[0].wordval != null) {
|
if (args[0].wordval != null) {
|
||||||
val str = program.heap.get(args[0].wordval!!).str!!
|
val encodedStr = getEncodedStringFromRuntimeVars(args[0].wordval!!)
|
||||||
dialog.canvas.printText(str, true)
|
dialog.canvas.printText(encodedStr)
|
||||||
} else
|
} else
|
||||||
dialog.canvas.printText(args[0].str!!, true)
|
throw VmExecutionException("print non-heap string")
|
||||||
}
|
}
|
||||||
"c64scr.print_ub" -> {
|
"c64scr.print_ub" -> {
|
||||||
dialog.canvas.printText(args[0].byteval!!.toString(), true)
|
dialog.canvas.printText(args[0].byteval!!.toString(), true)
|
||||||
@ -672,23 +694,23 @@ class AstVm(val program: Program) {
|
|||||||
dialog.canvas.printText(args[0].wordval!!.toString(), true)
|
dialog.canvas.printText(args[0].wordval!!.toString(), true)
|
||||||
}
|
}
|
||||||
"c64scr.print_ubhex" -> {
|
"c64scr.print_ubhex" -> {
|
||||||
val prefix = if (args[0].asBoolean) "$" else ""
|
val number = args[0].byteval!!
|
||||||
val number = args[1].byteval!!
|
val prefix = if (args[1].asBoolean) "$" else ""
|
||||||
dialog.canvas.printText("$prefix${number.toString(16).padStart(2, '0')}", true)
|
dialog.canvas.printText("$prefix${number.toString(16).padStart(2, '0')}", true)
|
||||||
}
|
}
|
||||||
"c64scr.print_uwhex" -> {
|
"c64scr.print_uwhex" -> {
|
||||||
val prefix = if (args[0].asBoolean) "$" else ""
|
val number = args[0].wordval!!
|
||||||
val number = args[1].wordval!!
|
val prefix = if (args[1].asBoolean) "$" else ""
|
||||||
dialog.canvas.printText("$prefix${number.toString(16).padStart(4, '0')}", true)
|
dialog.canvas.printText("$prefix${number.toString(16).padStart(4, '0')}", true)
|
||||||
}
|
}
|
||||||
"c64scr.print_uwbin" -> {
|
"c64scr.print_uwbin" -> {
|
||||||
val prefix = if (args[0].asBoolean) "%" else ""
|
val number = args[0].wordval!!
|
||||||
val number = args[1].wordval!!
|
val prefix = if (args[1].asBoolean) "%" else ""
|
||||||
dialog.canvas.printText("$prefix${number.toString(2).padStart(16, '0')}", true)
|
dialog.canvas.printText("$prefix${number.toString(2).padStart(16, '0')}", true)
|
||||||
}
|
}
|
||||||
"c64scr.print_ubbin" -> {
|
"c64scr.print_ubbin" -> {
|
||||||
val prefix = if (args[0].asBoolean) "%" else ""
|
val number = args[0].byteval!!
|
||||||
val number = args[1].byteval!!
|
val prefix = if (args[1].asBoolean) "%" else ""
|
||||||
dialog.canvas.printText("$prefix${number.toString(2).padStart(8, '0')}", true)
|
dialog.canvas.printText("$prefix${number.toString(2).padStart(8, '0')}", true)
|
||||||
}
|
}
|
||||||
"c64scr.clear_screenchars" -> {
|
"c64scr.clear_screenchars" -> {
|
||||||
@ -724,13 +746,14 @@ class AstVm(val program: Program) {
|
|||||||
}
|
}
|
||||||
val inputStr = input.joinToString("")
|
val inputStr = input.joinToString("")
|
||||||
val heapId = args[0].wordval!!
|
val heapId = args[0].wordval!!
|
||||||
val origStr = program.heap.get(heapId).str!!
|
val origStrLength = getEncodedStringFromRuntimeVars(heapId).size
|
||||||
val paddedStr=inputStr.padEnd(origStr.length+1, '\u0000').substring(0, origStr.length)
|
val encodedStr = Petscii.encodePetscii(inputStr, true).take(origStrLength).toMutableList()
|
||||||
program.heap.update(heapId, paddedStr)
|
while(encodedStr.size < origStrLength)
|
||||||
result = RuntimeValue(DataType.UBYTE, paddedStr.indexOf('\u0000'))
|
encodedStr.add(0)
|
||||||
|
result = RuntimeValueNumeric(DataType.UBYTE, encodedStr.indexOf(0))
|
||||||
}
|
}
|
||||||
"c64flt.print_f" -> {
|
"c64flt.print_f" -> {
|
||||||
dialog.canvas.printText(args[0].floatval.toString(), true)
|
dialog.canvas.printText(args[0].floatval.toString(), false)
|
||||||
}
|
}
|
||||||
"c64.CHROUT" -> {
|
"c64.CHROUT" -> {
|
||||||
dialog.canvas.printPetscii(args[0].byteval!!)
|
dialog.canvas.printPetscii(args[0].byteval!!)
|
||||||
@ -743,13 +766,13 @@ class AstVm(val program: Program) {
|
|||||||
Thread.sleep(10)
|
Thread.sleep(10)
|
||||||
}
|
}
|
||||||
val char=dialog.keyboardBuffer.pop()
|
val char=dialog.keyboardBuffer.pop()
|
||||||
result = RuntimeValue(DataType.UBYTE, char.toShort())
|
result = RuntimeValueNumeric(DataType.UBYTE, char.toShort())
|
||||||
}
|
}
|
||||||
"c64utils.str2uword" -> {
|
"c64utils.str2uword" -> {
|
||||||
val heapId = args[0].wordval!!
|
val heapId = args[0].wordval!!
|
||||||
val argString = program.heap.get(heapId).str!!
|
val argString = getEncodedStringFromRuntimeVars(heapId)
|
||||||
val numericpart = argString.takeWhile { it.isDigit() }
|
val numericpart = argString.takeWhile { it.toChar().isDigit() }.toString()
|
||||||
result = RuntimeValue(DataType.UWORD, numericpart.toInt() and 65535)
|
result = RuntimeValueNumeric(DataType.UWORD, numericpart.toInt() and 65535)
|
||||||
}
|
}
|
||||||
else -> TODO("syscall ${sub.scopedname} $sub")
|
else -> TODO("syscall ${sub.scopedname} $sub")
|
||||||
}
|
}
|
||||||
@ -757,124 +780,135 @@ class AstVm(val program: Program) {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun performBuiltinFunction(name: String, args: List<RuntimeValue>, statusflags: StatusFlags): RuntimeValue? {
|
private fun getEncodedStringFromRuntimeVars(heapId: Int): List<Short> {
|
||||||
|
val stringvar = runtimeVariables.getByHeapId(heapId) as RuntimeValueString
|
||||||
|
return when {
|
||||||
|
stringvar.type==DataType.STR -> Petscii.encodePetscii(stringvar.str, true)
|
||||||
|
stringvar.type==DataType.STR_S -> Petscii.encodeScreencode(stringvar.str, true)
|
||||||
|
else -> throw VmExecutionException("weird string type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getArrayFromRuntimeVars(heapId: Int): IntArray {
|
||||||
|
val arrayvar = runtimeVariables.getByHeapId(heapId) as RuntimeValueArray
|
||||||
|
return arrayvar.array.map { it.toInt() }.toIntArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun performBuiltinFunction(name: String, args: List<RuntimeValueBase>, statusflags: StatusFlags): RuntimeValueNumeric? {
|
||||||
return when (name) {
|
return when (name) {
|
||||||
"rnd" -> RuntimeValue(DataType.UBYTE, rnd.nextInt() and 255)
|
"rnd" -> RuntimeValueNumeric(DataType.UBYTE, rnd.nextInt() and 255)
|
||||||
"rndw" -> RuntimeValue(DataType.UWORD, rnd.nextInt() and 65535)
|
"rndw" -> RuntimeValueNumeric(DataType.UWORD, rnd.nextInt() and 65535)
|
||||||
"rndf" -> RuntimeValue(DataType.FLOAT, rnd.nextDouble())
|
"rndf" -> RuntimeValueNumeric(DataType.FLOAT, rnd.nextDouble())
|
||||||
"lsb" -> RuntimeValue(DataType.UBYTE, args[0].integerValue() and 255)
|
"lsb" -> RuntimeValueNumeric(DataType.UBYTE, args[0].integerValue() and 255)
|
||||||
"msb" -> RuntimeValue(DataType.UBYTE, (args[0].integerValue() ushr 8) and 255)
|
"msb" -> RuntimeValueNumeric(DataType.UBYTE, (args[0].integerValue() ushr 8) and 255)
|
||||||
"sin" -> RuntimeValue(DataType.FLOAT, sin(args[0].numericValue().toDouble()))
|
"sin" -> RuntimeValueNumeric(DataType.FLOAT, sin(args[0].numericValue().toDouble()))
|
||||||
"sin8" -> {
|
"sin8" -> {
|
||||||
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
|
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
|
||||||
RuntimeValue(DataType.BYTE, (127.0 * sin(rad)).toShort())
|
RuntimeValueNumeric(DataType.BYTE, (127.0 * sin(rad)).toShort())
|
||||||
}
|
}
|
||||||
"sin8u" -> {
|
"sin8u" -> {
|
||||||
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
|
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
|
||||||
RuntimeValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toShort())
|
RuntimeValueNumeric(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toShort())
|
||||||
}
|
}
|
||||||
"sin16" -> {
|
"sin16" -> {
|
||||||
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
|
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
|
||||||
RuntimeValue(DataType.BYTE, (32767.0 * sin(rad)).toShort())
|
RuntimeValueNumeric(DataType.BYTE, (32767.0 * sin(rad)).toShort())
|
||||||
}
|
}
|
||||||
"sin16u" -> {
|
"sin16u" -> {
|
||||||
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
|
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
|
||||||
RuntimeValue(DataType.UBYTE, (32768.0 + 32767.5 * sin(rad)).toShort())
|
RuntimeValueNumeric(DataType.UBYTE, (32768.0 + 32767.5 * sin(rad)).toShort())
|
||||||
}
|
}
|
||||||
"cos" -> RuntimeValue(DataType.FLOAT, cos(args[0].numericValue().toDouble()))
|
"cos" -> RuntimeValueNumeric(DataType.FLOAT, cos(args[0].numericValue().toDouble()))
|
||||||
"cos8" -> {
|
"cos8" -> {
|
||||||
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
|
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
|
||||||
RuntimeValue(DataType.BYTE, (127.0 * cos(rad)).toShort())
|
RuntimeValueNumeric(DataType.BYTE, (127.0 * cos(rad)).toShort())
|
||||||
}
|
}
|
||||||
"cos8u" -> {
|
"cos8u" -> {
|
||||||
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
|
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
|
||||||
RuntimeValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toShort())
|
RuntimeValueNumeric(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toShort())
|
||||||
}
|
}
|
||||||
"cos16" -> {
|
"cos16" -> {
|
||||||
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
|
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
|
||||||
RuntimeValue(DataType.BYTE, (32767.0 * cos(rad)).toShort())
|
RuntimeValueNumeric(DataType.BYTE, (32767.0 * cos(rad)).toShort())
|
||||||
}
|
}
|
||||||
"cos16u" -> {
|
"cos16u" -> {
|
||||||
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
|
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
|
||||||
RuntimeValue(DataType.UBYTE, (32768.0 + 32767.5 * cos(rad)).toShort())
|
RuntimeValueNumeric(DataType.UBYTE, (32768.0 + 32767.5 * cos(rad)).toShort())
|
||||||
}
|
}
|
||||||
"tan" -> RuntimeValue(DataType.FLOAT, tan(args[0].numericValue().toDouble()))
|
"tan" -> RuntimeValueNumeric(DataType.FLOAT, tan(args[0].numericValue().toDouble()))
|
||||||
"atan" -> RuntimeValue(DataType.FLOAT, atan(args[0].numericValue().toDouble()))
|
"atan" -> RuntimeValueNumeric(DataType.FLOAT, atan(args[0].numericValue().toDouble()))
|
||||||
"ln" -> RuntimeValue(DataType.FLOAT, ln(args[0].numericValue().toDouble()))
|
"ln" -> RuntimeValueNumeric(DataType.FLOAT, ln(args[0].numericValue().toDouble()))
|
||||||
"log2" -> RuntimeValue(DataType.FLOAT, log2(args[0].numericValue().toDouble()))
|
"log2" -> RuntimeValueNumeric(DataType.FLOAT, log2(args[0].numericValue().toDouble()))
|
||||||
"sqrt" -> RuntimeValue(DataType.FLOAT, sqrt(args[0].numericValue().toDouble()))
|
"sqrt" -> RuntimeValueNumeric(DataType.FLOAT, sqrt(args[0].numericValue().toDouble()))
|
||||||
"sqrt16" -> RuntimeValue(DataType.UBYTE, sqrt(args[0].wordval!!.toDouble()).toInt())
|
"sqrt16" -> RuntimeValueNumeric(DataType.UBYTE, sqrt((args[0] as RuntimeValueNumeric).wordval!!.toDouble()).toInt())
|
||||||
"rad" -> RuntimeValue(DataType.FLOAT, Math.toRadians(args[0].numericValue().toDouble()))
|
"rad" -> RuntimeValueNumeric(DataType.FLOAT, Math.toRadians(args[0].numericValue().toDouble()))
|
||||||
"deg" -> RuntimeValue(DataType.FLOAT, Math.toDegrees(args[0].numericValue().toDouble()))
|
"deg" -> RuntimeValueNumeric(DataType.FLOAT, Math.toDegrees(args[0].numericValue().toDouble()))
|
||||||
"round" -> RuntimeValue(DataType.FLOAT, round(args[0].numericValue().toDouble()))
|
"round" -> RuntimeValueNumeric(DataType.FLOAT, round(args[0].numericValue().toDouble()))
|
||||||
"floor" -> RuntimeValue(DataType.FLOAT, floor(args[0].numericValue().toDouble()))
|
"floor" -> RuntimeValueNumeric(DataType.FLOAT, floor(args[0].numericValue().toDouble()))
|
||||||
"ceil" -> RuntimeValue(DataType.FLOAT, ceil(args[0].numericValue().toDouble()))
|
"ceil" -> RuntimeValueNumeric(DataType.FLOAT, ceil(args[0].numericValue().toDouble()))
|
||||||
"rol" -> {
|
"rol" -> {
|
||||||
val (result, newCarry) = args[0].rol(statusflags.carry)
|
val (result, newCarry) = (args[0] as RuntimeValueNumeric).rol(statusflags.carry)
|
||||||
statusflags.carry = newCarry
|
statusflags.carry = newCarry
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
"rol2" -> args[0].rol2()
|
"rol2" -> (args[0] as RuntimeValueNumeric).rol2()
|
||||||
"ror" -> {
|
"ror" -> {
|
||||||
val (result, newCarry) = args[0].ror(statusflags.carry)
|
val (result, newCarry) = (args[0] as RuntimeValueNumeric).ror(statusflags.carry)
|
||||||
statusflags.carry = newCarry
|
statusflags.carry = newCarry
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
"ror2" -> args[0].ror2()
|
"ror2" -> (args[0] as RuntimeValueNumeric).ror2()
|
||||||
"lsl" -> args[0].shl()
|
"lsl" -> (args[0] as RuntimeValueNumeric).shl()
|
||||||
"lsr" -> args[0].shr()
|
"lsr" -> (args[0] as RuntimeValueNumeric).shr()
|
||||||
"abs" -> {
|
"abs" -> {
|
||||||
when (args[0].type) {
|
when (args[0].type) {
|
||||||
DataType.UBYTE -> args[0]
|
DataType.UBYTE -> (args[0] as RuntimeValueNumeric)
|
||||||
DataType.BYTE -> RuntimeValue(DataType.UBYTE, abs(args[0].numericValue().toDouble()))
|
DataType.BYTE -> RuntimeValueNumeric(DataType.UBYTE, abs(args[0].numericValue().toDouble()))
|
||||||
DataType.UWORD -> args[0]
|
DataType.UWORD -> (args[0] as RuntimeValueNumeric)
|
||||||
DataType.WORD -> RuntimeValue(DataType.UWORD, abs(args[0].numericValue().toDouble()))
|
DataType.WORD -> RuntimeValueNumeric(DataType.UWORD, abs(args[0].numericValue().toDouble()))
|
||||||
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, abs(args[0].numericValue().toDouble()))
|
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, abs(args[0].numericValue().toDouble()))
|
||||||
else -> throw VmExecutionException("strange abs type ${args[0]}")
|
else -> throw VmExecutionException("strange abs type ${args[0]}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"max" -> {
|
"max" -> {
|
||||||
val numbers = args.map { it.numericValue().toDouble() }
|
val numbers = (args.single() as RuntimeValueArray).array.map { it.toDouble() }
|
||||||
RuntimeValue(args[0].type, numbers.max())
|
RuntimeValueNumeric(ArrayElementTypes.getValue(args[0].type), numbers.max()!!)
|
||||||
}
|
}
|
||||||
"min" -> {
|
"min" -> {
|
||||||
val numbers = args.map { it.numericValue().toDouble() }
|
val numbers = (args.single() as RuntimeValueArray).array.map { it.toDouble() }
|
||||||
RuntimeValue(args[0].type, numbers.min())
|
RuntimeValueNumeric(ArrayElementTypes.getValue(args[0].type), numbers.min()!!)
|
||||||
}
|
|
||||||
"avg" -> {
|
|
||||||
val numbers = args.map { it.numericValue().toDouble() }
|
|
||||||
RuntimeValue(DataType.FLOAT, numbers.average())
|
|
||||||
}
|
}
|
||||||
"sum" -> {
|
"sum" -> {
|
||||||
val sum = args.map { it.numericValue().toDouble() }.sum()
|
val sum = (args.single() as RuntimeValueArray).array.map { it.toDouble() }.sum()
|
||||||
when (args[0].type) {
|
when (args[0].type) {
|
||||||
DataType.UBYTE -> RuntimeValue(DataType.UWORD, sum)
|
DataType.ARRAY_UB -> RuntimeValueNumeric(DataType.UWORD, sum)
|
||||||
DataType.BYTE -> RuntimeValue(DataType.WORD, sum)
|
DataType.ARRAY_B -> RuntimeValueNumeric(DataType.WORD, sum)
|
||||||
DataType.UWORD -> RuntimeValue(DataType.UWORD, sum)
|
DataType.ARRAY_UW -> RuntimeValueNumeric(DataType.UWORD, sum)
|
||||||
DataType.WORD -> RuntimeValue(DataType.WORD, sum)
|
DataType.ARRAY_W -> RuntimeValueNumeric(DataType.WORD, sum)
|
||||||
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, sum)
|
DataType.ARRAY_F -> RuntimeValueNumeric(DataType.FLOAT, sum)
|
||||||
else -> throw VmExecutionException("weird sum type ${args[0]}")
|
else -> throw VmExecutionException("weird sum type ${args[0]}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"any" -> {
|
"any" -> {
|
||||||
val numbers = args.map { it.numericValue().toDouble() }
|
val numbers = (args.single() as RuntimeValueArray).array.map { it.toDouble() }
|
||||||
RuntimeValue(DataType.UBYTE, if (numbers.any { it != 0.0 }) 1 else 0)
|
RuntimeValueNumeric(DataType.UBYTE, if (numbers.any { it != 0.0 }) 1 else 0)
|
||||||
}
|
}
|
||||||
"all" -> {
|
"all" -> {
|
||||||
val numbers = args.map { it.numericValue().toDouble() }
|
val numbers = (args.single() as RuntimeValueArray).array.map { it.toDouble() }
|
||||||
RuntimeValue(DataType.UBYTE, if (numbers.all { it != 0.0 }) 1 else 0)
|
RuntimeValueNumeric(DataType.UBYTE, if (numbers.all { it != 0.0 }) 1 else 0)
|
||||||
}
|
}
|
||||||
"swap" ->
|
"swap" ->
|
||||||
throw VmExecutionException("swap() cannot be implemented as a function")
|
throw VmExecutionException("swap() cannot be implemented as a function")
|
||||||
"strlen" -> {
|
"strlen" -> {
|
||||||
val zeroIndex = args[0].str!!.indexOf(0.toChar())
|
val zeroIndex = (args[0] as RuntimeValueString).str.indexOf(0.toChar())
|
||||||
if (zeroIndex >= 0)
|
if (zeroIndex >= 0)
|
||||||
RuntimeValue(DataType.UBYTE, zeroIndex)
|
RuntimeValueNumeric(DataType.UBYTE, zeroIndex)
|
||||||
else
|
else
|
||||||
RuntimeValue(DataType.UBYTE, args[0].str!!.length)
|
RuntimeValueNumeric(DataType.UBYTE, (args[0] as RuntimeValueString).str.length)
|
||||||
}
|
}
|
||||||
"memset" -> {
|
"memset" -> {
|
||||||
val target = args[0].array!!
|
val heapId = (args[0] as RuntimeValueNumeric).wordval!!
|
||||||
|
val target = getArrayFromRuntimeVars(heapId)
|
||||||
val amount = args[1].integerValue()
|
val amount = args[1].integerValue()
|
||||||
val value = args[2].integerValue()
|
val value = args[2].integerValue()
|
||||||
for (i in 0 until amount) {
|
for (i in 0 until amount) {
|
||||||
@ -883,7 +917,8 @@ class AstVm(val program: Program) {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
"memsetw" -> {
|
"memsetw" -> {
|
||||||
val target = args[0].array!!
|
val heapId = (args[0] as RuntimeValueNumeric).wordval!!
|
||||||
|
val target = getArrayFromRuntimeVars(heapId)
|
||||||
val amount = args[1].integerValue()
|
val amount = args[1].integerValue()
|
||||||
val value = args[2].integerValue()
|
val value = args[2].integerValue()
|
||||||
for (i in 0 until amount step 2) {
|
for (i in 0 until amount step 2) {
|
||||||
@ -893,8 +928,10 @@ class AstVm(val program: Program) {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
"memcopy" -> {
|
"memcopy" -> {
|
||||||
val source = args[0].array!!
|
val sourceHeapId = (args[0] as RuntimeValueNumeric).wordval!!
|
||||||
val dest = args[1].array!!
|
val destHeapId = (args[1] as RuntimeValueNumeric).wordval!!
|
||||||
|
val source = getArrayFromRuntimeVars(sourceHeapId)
|
||||||
|
val dest = getArrayFromRuntimeVars(destHeapId)
|
||||||
val amount = args[2].integerValue()
|
val amount = args[2].integerValue()
|
||||||
for(i in 0 until amount) {
|
for(i in 0 until amount) {
|
||||||
dest[i] = source[i]
|
dest[i] = source[i]
|
||||||
@ -903,7 +940,7 @@ class AstVm(val program: Program) {
|
|||||||
}
|
}
|
||||||
"mkword" -> {
|
"mkword" -> {
|
||||||
val result = (args[1].integerValue() shl 8) or args[0].integerValue()
|
val result = (args[1].integerValue() shl 8) or args[0].integerValue()
|
||||||
RuntimeValue(DataType.UWORD, result)
|
RuntimeValueNumeric(DataType.UWORD, result)
|
||||||
}
|
}
|
||||||
"set_carry" -> {
|
"set_carry" -> {
|
||||||
statusflags.carry=true
|
statusflags.carry=true
|
||||||
@ -926,13 +963,13 @@ class AstVm(val program: Program) {
|
|||||||
val zero = if(statusflags.zero) 2 else 0
|
val zero = if(statusflags.zero) 2 else 0
|
||||||
val irqd = if(statusflags.irqd) 4 else 0
|
val irqd = if(statusflags.irqd) 4 else 0
|
||||||
val negative = if(statusflags.negative) 128 else 0
|
val negative = if(statusflags.negative) 128 else 0
|
||||||
RuntimeValue(DataType.UBYTE, carry or zero or irqd or negative)
|
RuntimeValueNumeric(DataType.UBYTE, carry or zero or irqd or negative)
|
||||||
}
|
}
|
||||||
"rsave" -> {
|
"rsave" -> {
|
||||||
statusFlagsSave.push(statusflags)
|
statusFlagsSave.push(statusflags)
|
||||||
registerAsave.push(runtimeVariables.get(program.namespace, Register.A.name))
|
registerAsave.push(runtimeVariables.get(program.namespace, Register.A.name) as RuntimeValueNumeric)
|
||||||
registerXsave.push(runtimeVariables.get(program.namespace, Register.X.name))
|
registerXsave.push(runtimeVariables.get(program.namespace, Register.X.name) as RuntimeValueNumeric)
|
||||||
registerYsave.push(runtimeVariables.get(program.namespace, Register.Y.name))
|
registerYsave.push(runtimeVariables.get(program.namespace, Register.Y.name) as RuntimeValueNumeric)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
"rrestore" -> {
|
"rrestore" -> {
|
||||||
@ -946,9 +983,25 @@ class AstVm(val program: Program) {
|
|||||||
runtimeVariables.set(program.namespace, Register.Y.name, registerYsave.pop())
|
runtimeVariables.set(program.namespace, Register.Y.name, registerYsave.pop())
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
"sort" -> {
|
||||||
|
val array=args.single() as RuntimeValueArray
|
||||||
|
array.array.sort()
|
||||||
|
null
|
||||||
|
}
|
||||||
|
"reverse" -> {
|
||||||
|
val array=args.single() as RuntimeValueArray
|
||||||
|
array.array.reverse()
|
||||||
|
null
|
||||||
|
}
|
||||||
|
"sgn" -> {
|
||||||
|
val value = args.single().numericValue().toDouble()
|
||||||
|
when {
|
||||||
|
value<0.0 -> RuntimeValueNumeric(DataType.BYTE, -1)
|
||||||
|
value==0.0 -> RuntimeValueNumeric(DataType.BYTE, 0)
|
||||||
|
else -> RuntimeValueNumeric(DataType.BYTE, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> TODO("builtin function $name")
|
else -> TODO("builtin function $name")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
package prog8.vm.astvm
|
|
||||||
|
|
||||||
import prog8.ast.INameScope
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class CallStack {
|
|
||||||
|
|
||||||
private val stack = Stack<Pair<INameScope, Int>>()
|
|
||||||
|
|
||||||
fun pop(): Pair<INameScope, Int> {
|
|
||||||
return stack.pop()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun push(scope: INameScope, index: Int) {
|
|
||||||
stack.push(Pair(scope, index))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,51 +1,55 @@
|
|||||||
package prog8.vm.astvm
|
package prog8.vm.astvm
|
||||||
|
|
||||||
import prog8.ast.*
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.ArrayElementTypes
|
import prog8.ast.base.ArrayElementTypes
|
||||||
import prog8.ast.base.DataType
|
import prog8.ast.base.DataType
|
||||||
|
import prog8.ast.base.FatalAstException
|
||||||
import prog8.ast.base.VarDeclType
|
import prog8.ast.base.VarDeclType
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
|
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
|
||||||
import prog8.ast.statements.Label
|
import prog8.ast.statements.Label
|
||||||
import prog8.ast.statements.Subroutine
|
import prog8.ast.statements.Subroutine
|
||||||
import prog8.ast.statements.VarDecl
|
import prog8.ast.statements.VarDecl
|
||||||
import prog8.vm.RuntimeValue
|
import prog8.vm.*
|
||||||
import prog8.vm.RuntimeValueRange
|
|
||||||
import kotlin.math.abs
|
|
||||||
|
typealias BuiltinfunctionCaller = (name: String, args: List<RuntimeValueNumeric>, flags: StatusFlags) -> RuntimeValueNumeric?
|
||||||
|
typealias SubroutineCaller = (sub: Subroutine, args: List<RuntimeValueNumeric>, startAtLabel: Label?) -> RuntimeValueNumeric?
|
||||||
|
|
||||||
|
|
||||||
class EvalContext(val program: Program, val mem: Memory, val statusflags: StatusFlags,
|
class EvalContext(val program: Program, val mem: Memory, val statusflags: StatusFlags,
|
||||||
val runtimeVars: RuntimeVariables,
|
val runtimeVars: RuntimeVariables,
|
||||||
val performBuiltinFunction: (String, List<RuntimeValue>, StatusFlags) -> RuntimeValue?,
|
val performBuiltinFunction: BuiltinfunctionCaller,
|
||||||
val executeSubroutine: (sub: Subroutine, args: List<RuntimeValue>, startlabel: Label?) -> RuntimeValue?)
|
val executeSubroutine: SubroutineCaller)
|
||||||
|
|
||||||
fun evaluate(expr: IExpression, ctx: EvalContext): RuntimeValue {
|
fun evaluate(expr: Expression, ctx: EvalContext): RuntimeValueBase {
|
||||||
val constval = expr.constValue(ctx.program)
|
val constval = expr.constValue(ctx.program)
|
||||||
if(constval!=null)
|
if(constval!=null)
|
||||||
return RuntimeValue.from(constval, ctx.program.heap)
|
return RuntimeValueNumeric.fromLv(constval)
|
||||||
|
|
||||||
when(expr) {
|
when(expr) {
|
||||||
is LiteralValue -> {
|
is NumericLiteralValue -> return RuntimeValueNumeric.fromLv(expr)
|
||||||
return RuntimeValue.from(expr, ctx.program.heap)
|
is StringLiteralValue -> return RuntimeValueString.fromLv(expr)
|
||||||
}
|
is ArrayLiteralValue -> return RuntimeValueArray.fromLv(expr)
|
||||||
is PrefixExpression -> {
|
is PrefixExpression -> {
|
||||||
return when(expr.operator) {
|
return when(expr.operator) {
|
||||||
"-" -> evaluate(expr.expression, ctx).neg()
|
"-" -> (evaluate(expr.expression, ctx) as RuntimeValueNumeric).neg()
|
||||||
"~" -> evaluate(expr.expression, ctx).inv()
|
"~" -> (evaluate(expr.expression, ctx) as RuntimeValueNumeric).inv()
|
||||||
"not" -> evaluate(expr.expression, ctx).not()
|
"not" -> (evaluate(expr.expression, ctx) as RuntimeValueNumeric).not()
|
||||||
// unary '+' should have been optimized away
|
// unary '+' should have been optimized away
|
||||||
else -> throw VmExecutionException("unsupported prefix operator "+expr.operator)
|
else -> throw VmExecutionException("unsupported prefix operator "+expr.operator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is BinaryExpression -> {
|
is BinaryExpression -> {
|
||||||
val left = evaluate(expr.left, ctx)
|
val left = evaluate(expr.left, ctx) as RuntimeValueNumeric
|
||||||
val right = evaluate(expr.right, ctx)
|
val right = evaluate(expr.right, ctx) as RuntimeValueNumeric
|
||||||
return when(expr.operator) {
|
return when(expr.operator) {
|
||||||
"<" -> RuntimeValue(DataType.UBYTE, if (left < right) 1 else 0)
|
"<" -> RuntimeValueNumeric(DataType.UBYTE, if (left < right) 1 else 0)
|
||||||
"<=" -> RuntimeValue(DataType.UBYTE, if (left <= right) 1 else 0)
|
"<=" -> RuntimeValueNumeric(DataType.UBYTE, if (left <= right) 1 else 0)
|
||||||
">" -> RuntimeValue(DataType.UBYTE, if (left > right) 1 else 0)
|
">" -> RuntimeValueNumeric(DataType.UBYTE, if (left > right) 1 else 0)
|
||||||
">=" -> RuntimeValue(DataType.UBYTE, if (left >= right) 1 else 0)
|
">=" -> RuntimeValueNumeric(DataType.UBYTE, if (left >= right) 1 else 0)
|
||||||
"==" -> RuntimeValue(DataType.UBYTE, if (left == right) 1 else 0)
|
"==" -> RuntimeValueNumeric(DataType.UBYTE, if (left == right) 1 else 0)
|
||||||
"!=" -> RuntimeValue(DataType.UBYTE, if (left != right) 1 else 0)
|
"!=" -> RuntimeValueNumeric(DataType.UBYTE, if (left != right) 1 else 0)
|
||||||
"+" -> left.add(right)
|
"+" -> left.add(right)
|
||||||
"-" -> left.sub(right)
|
"-" -> left.sub(right)
|
||||||
"*" -> left.mul(right)
|
"*" -> left.mul(right)
|
||||||
@ -73,40 +77,57 @@ fun evaluate(expr: IExpression, ctx: EvalContext): RuntimeValue {
|
|||||||
}
|
}
|
||||||
is ArrayIndexedExpression -> {
|
is ArrayIndexedExpression -> {
|
||||||
val array = evaluate(expr.identifier, ctx)
|
val array = evaluate(expr.identifier, ctx)
|
||||||
val index = evaluate(expr.arrayspec.index, ctx)
|
val index = evaluate(expr.arrayspec.index, ctx) as RuntimeValueNumeric
|
||||||
val value = array.array!![index.integerValue()]
|
return when (array) {
|
||||||
return RuntimeValue(ArrayElementTypes.getValue(array.type), value)
|
is RuntimeValueString -> {
|
||||||
|
val value = array.str[index.integerValue()]
|
||||||
|
RuntimeValueNumeric(ArrayElementTypes.getValue(array.type), value.toShort())
|
||||||
|
}
|
||||||
|
is RuntimeValueArray -> {
|
||||||
|
val value = array.array[index.integerValue()]
|
||||||
|
RuntimeValueNumeric(ArrayElementTypes.getValue(array.type), value)
|
||||||
|
}
|
||||||
|
else -> throw VmExecutionException("weird type")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
is TypecastExpression -> {
|
is TypecastExpression -> {
|
||||||
return evaluate(expr.expression, ctx).cast(expr.type)
|
return (evaluate(expr.expression, ctx) as RuntimeValueNumeric).cast(expr.type)
|
||||||
}
|
}
|
||||||
is AddressOf -> {
|
is AddressOf -> {
|
||||||
// we support: address of heap var -> the heap id
|
// we support: address of heap var -> the heap id
|
||||||
val heapId = expr.identifier.heapId(ctx.program.namespace)
|
return try {
|
||||||
return RuntimeValue(DataType.UWORD, heapId)
|
val heapId = expr.identifier.heapId(ctx.program.namespace)
|
||||||
|
RuntimeValueNumeric(DataType.UWORD, heapId)
|
||||||
|
} catch( f: FatalAstException) {
|
||||||
|
// fallback: use the hash of the name, so we have at least *a* value...
|
||||||
|
val address = expr.identifier.hashCode() and 65535
|
||||||
|
RuntimeValueNumeric(DataType.UWORD, address)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
is DirectMemoryRead -> {
|
is DirectMemoryRead -> {
|
||||||
val address = evaluate(expr.addressExpression, ctx).wordval!!
|
val address = (evaluate(expr.addressExpression, ctx) as RuntimeValueNumeric).wordval!!
|
||||||
return RuntimeValue(DataType.UBYTE, ctx.mem.getUByte(address))
|
return RuntimeValueNumeric(DataType.UBYTE, ctx.mem.getUByte(address))
|
||||||
}
|
}
|
||||||
is RegisterExpr -> return ctx.runtimeVars.get(ctx.program.namespace, expr.register.name)
|
is RegisterExpr -> return ctx.runtimeVars.get(ctx.program.namespace, expr.register.name)
|
||||||
is IdentifierReference -> {
|
is IdentifierReference -> {
|
||||||
val scope = expr.definingScope()
|
val scope = expr.definingScope()
|
||||||
val variable = scope.lookup(expr.nameInSource, expr)
|
val variable = scope.lookup(expr.nameInSource, expr)
|
||||||
if(variable is VarDecl) {
|
if(variable is VarDecl) {
|
||||||
if(variable.type==VarDeclType.VAR)
|
when {
|
||||||
return ctx.runtimeVars.get(variable.definingScope(), variable.name)
|
variable.type==VarDeclType.VAR -> return ctx.runtimeVars.get(variable.definingScope(), variable.name)
|
||||||
else {
|
variable.datatype==DataType.STRUCT -> throw VmExecutionException("cannot process structs by-value. at ${expr.position}")
|
||||||
val address = ctx.runtimeVars.getMemoryAddress(variable.definingScope(), variable.name)
|
else -> {
|
||||||
return when(variable.datatype) {
|
val address = ctx.runtimeVars.getMemoryAddress(variable.definingScope(), variable.name)
|
||||||
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, ctx.mem.getUByte(address))
|
return when(variable.datatype) {
|
||||||
DataType.BYTE -> RuntimeValue(DataType.BYTE, ctx.mem.getSByte(address))
|
DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, ctx.mem.getUByte(address))
|
||||||
DataType.UWORD -> RuntimeValue(DataType.UWORD, ctx.mem.getUWord(address))
|
DataType.BYTE -> RuntimeValueNumeric(DataType.BYTE, ctx.mem.getSByte(address))
|
||||||
DataType.WORD -> RuntimeValue(DataType.WORD, ctx.mem.getSWord(address))
|
DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, ctx.mem.getUWord(address))
|
||||||
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, ctx.mem.getFloat(address))
|
DataType.WORD -> RuntimeValueNumeric(DataType.WORD, ctx.mem.getSWord(address))
|
||||||
DataType.STR -> RuntimeValue(DataType.STR, str = ctx.mem.getString(address))
|
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, ctx.mem.getFloat(address))
|
||||||
DataType.STR_S -> RuntimeValue(DataType.STR_S, str = ctx.mem.getScreencodeString(address))
|
DataType.STR -> RuntimeValueString(DataType.STR, ctx.mem.getString(address), null)
|
||||||
else -> throw VmExecutionException("unexpected datatype $variable")
|
DataType.STR_S -> RuntimeValueString(DataType.STR_S, ctx.mem.getScreencodeString(address)!!, null)
|
||||||
|
else -> throw VmExecutionException("unexpected datatype $variable")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
@ -114,7 +135,7 @@ fun evaluate(expr: IExpression, ctx: EvalContext): RuntimeValue {
|
|||||||
}
|
}
|
||||||
is FunctionCall -> {
|
is FunctionCall -> {
|
||||||
val sub = expr.target.targetStatement(ctx.program.namespace)
|
val sub = expr.target.targetStatement(ctx.program.namespace)
|
||||||
val args = expr.arglist.map { evaluate(it, ctx) }
|
val args = expr.arglist.map { evaluate(it, ctx) as RuntimeValueNumeric }
|
||||||
return when(sub) {
|
return when(sub) {
|
||||||
is Subroutine -> {
|
is Subroutine -> {
|
||||||
val result = ctx.executeSubroutine(sub, args, null)
|
val result = ctx.executeSubroutine(sub, args, null)
|
||||||
@ -133,24 +154,22 @@ fun evaluate(expr: IExpression, ctx: EvalContext): RuntimeValue {
|
|||||||
}
|
}
|
||||||
is RangeExpr -> {
|
is RangeExpr -> {
|
||||||
val cRange = expr.toConstantIntegerRange()
|
val cRange = expr.toConstantIntegerRange()
|
||||||
if(cRange!=null)
|
if(cRange!=null) {
|
||||||
return RuntimeValueRange(expr.inferType(ctx.program)!!, cRange)
|
val dt = expr.inferType(ctx.program)
|
||||||
val fromVal = evaluate(expr.from, ctx).integerValue()
|
if(dt.isKnown)
|
||||||
val toVal = evaluate(expr.to, ctx).integerValue()
|
return RuntimeValueRange(dt.typeOrElse(DataType.UBYTE), cRange)
|
||||||
val stepVal = evaluate(expr.step, ctx).integerValue()
|
else
|
||||||
val range = when {
|
throw VmExecutionException("couldn't determine datatype")
|
||||||
fromVal <= toVal -> when {
|
|
||||||
stepVal <= 0 -> IntRange.EMPTY
|
|
||||||
stepVal == 1 -> fromVal..toVal
|
|
||||||
else -> fromVal..toVal step stepVal
|
|
||||||
}
|
|
||||||
else -> when {
|
|
||||||
stepVal >= 0 -> IntRange.EMPTY
|
|
||||||
stepVal == -1 -> fromVal downTo toVal
|
|
||||||
else -> fromVal downTo toVal step abs(stepVal)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return RuntimeValueRange(expr.inferType(ctx.program)!!, range)
|
val fromVal = (evaluate(expr.from, ctx) as RuntimeValueNumeric).integerValue()
|
||||||
|
val toVal = (evaluate(expr.to, ctx) as RuntimeValueNumeric).integerValue()
|
||||||
|
val stepVal = (evaluate(expr.step, ctx) as RuntimeValueNumeric).integerValue()
|
||||||
|
val range = makeRange(fromVal, toVal, stepVal)
|
||||||
|
val dt = expr.inferType(ctx.program)
|
||||||
|
if(dt.isKnown)
|
||||||
|
return RuntimeValueRange(dt.typeOrElse(DataType.UBYTE), range)
|
||||||
|
else
|
||||||
|
throw VmExecutionException("couldn't determine datatype")
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
throw VmExecutionException("unimplemented expression node $expr")
|
throw VmExecutionException("unimplemented expression node $expr")
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package prog8.vm.astvm
|
package prog8.vm.astvm
|
||||||
|
|
||||||
import prog8.compiler.target.c64.Mflpt5
|
import prog8.compiler.target.c64.MachineDefinition
|
||||||
import prog8.compiler.target.c64.Petscii
|
import prog8.compiler.target.c64.Petscii
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
@ -21,7 +21,7 @@ class Memory(private val readObserver: (address: Int, value: Short) -> Short,
|
|||||||
else mem[address]
|
else mem[address]
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getUByte_DMA(address: Int): Short {
|
fun getUByteDirectly(address: Int): Short {
|
||||||
return mem[address]
|
return mem[address]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ class Memory(private val readObserver: (address: Int, value: Short) -> Short,
|
|||||||
else value
|
else value
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setUByte_DMA(address: Int, value: Short) {
|
fun setUByteDirectly(address: Int, value: Short) {
|
||||||
if(value !in 0..255)
|
if(value !in 0..255)
|
||||||
throw VmExecutionException("ubyte value out of range $value")
|
throw VmExecutionException("ubyte value out of range $value")
|
||||||
mem[address] = value
|
mem[address] = value
|
||||||
@ -80,7 +80,7 @@ class Memory(private val readObserver: (address: Int, value: Short) -> Short,
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setFloat(address: Int, value: Double) {
|
fun setFloat(address: Int, value: Double) {
|
||||||
val mflpt5 = Mflpt5.fromNumber(value)
|
val mflpt5 = MachineDefinition.Mflpt5.fromNumber(value)
|
||||||
setUByte(address, mflpt5.b0)
|
setUByte(address, mflpt5.b0)
|
||||||
setUByte(address+1, mflpt5.b1)
|
setUByte(address+1, mflpt5.b1)
|
||||||
setUByte(address+2, mflpt5.b2)
|
setUByte(address+2, mflpt5.b2)
|
||||||
@ -89,7 +89,7 @@ class Memory(private val readObserver: (address: Int, value: Short) -> Short,
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getFloat(address: Int): Double {
|
fun getFloat(address: Int): Double {
|
||||||
return Mflpt5(getUByte(address), getUByte(address + 1), getUByte(address + 2),
|
return MachineDefinition.Mflpt5(getUByte(address), getUByte(address + 1), getUByte(address + 2),
|
||||||
getUByte(address + 3), getUByte(address + 4)).toDouble()
|
getUByte(address + 3), getUByte(address + 4)).toDouble()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
package prog8.vm.astvm
|
package prog8.vm.astvm
|
||||||
|
|
||||||
import prog8.compiler.target.c64.Charset
|
import prog8.compiler.target.c64.MachineDefinition
|
||||||
import prog8.compiler.target.c64.Colors
|
|
||||||
import prog8.compiler.target.c64.Petscii
|
import prog8.compiler.target.c64.Petscii
|
||||||
import java.awt.*
|
import java.awt.*
|
||||||
import java.awt.event.KeyEvent
|
import java.awt.event.KeyEvent
|
||||||
import java.awt.event.KeyListener
|
import java.awt.event.KeyListener
|
||||||
import java.awt.image.BufferedImage
|
import java.awt.image.BufferedImage
|
||||||
import java.util.*
|
import java.util.ArrayDeque
|
||||||
import javax.swing.JFrame
|
import javax.swing.JFrame
|
||||||
import javax.swing.JPanel
|
import javax.swing.JPanel
|
||||||
import javax.swing.Timer
|
import javax.swing.Timer
|
||||||
@ -19,7 +18,7 @@ class BitmapScreenPanel : KeyListener, JPanel() {
|
|||||||
private val g2d = image.graphics as Graphics2D
|
private val g2d = image.graphics as Graphics2D
|
||||||
private var cursorX: Int=0
|
private var cursorX: Int=0
|
||||||
private var cursorY: Int=0
|
private var cursorY: Int=0
|
||||||
val keyboardBuffer: Deque<Char> = LinkedList<Char>()
|
val keyboardBuffer = ArrayDeque<Char>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val size = Dimension(image.width * SCALING, image.height * SCALING)
|
val size = Dimension(image.width * SCALING, image.height * SCALING)
|
||||||
@ -51,16 +50,16 @@ class BitmapScreenPanel : KeyListener, JPanel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun clearScreen(color: Short) {
|
fun clearScreen(color: Short) {
|
||||||
g2d.background = Colors.palette[color % Colors.palette.size]
|
g2d.background = MachineDefinition.colorPalette[color % MachineDefinition.colorPalette.size]
|
||||||
g2d.clearRect(0, 0, SCREENWIDTH, SCREENHEIGHT)
|
g2d.clearRect(0, 0, SCREENWIDTH, SCREENHEIGHT)
|
||||||
cursorX = 0
|
cursorX = 0
|
||||||
cursorY = 0
|
cursorY = 0
|
||||||
}
|
}
|
||||||
fun setPixel(x: Int, y: Int, color: Short) {
|
fun setPixel(x: Int, y: Int, color: Short) {
|
||||||
image.setRGB(x, y, Colors.palette[color % Colors.palette.size].rgb)
|
image.setRGB(x, y, MachineDefinition.colorPalette[color % MachineDefinition.colorPalette.size].rgb)
|
||||||
}
|
}
|
||||||
fun drawLine(x1: Int, y1: Int, x2: Int, y2: Int, color: Short) {
|
fun drawLine(x1: Int, y1: Int, x2: Int, y2: Int, color: Short) {
|
||||||
g2d.color = Colors.palette[color % Colors.palette.size]
|
g2d.color = MachineDefinition.colorPalette[color % MachineDefinition.colorPalette.size]
|
||||||
g2d.drawLine(x1, y1, x2, y2)
|
g2d.drawLine(x1, y1, x2, y2)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,6 +75,10 @@ class BitmapScreenPanel : KeyListener, JPanel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun printText(text: Iterable<Short>) {
|
||||||
|
text.forEach { printPetscii(it, false) }
|
||||||
|
}
|
||||||
|
|
||||||
fun printPetscii(char: Short, inverseVideo: Boolean=false) {
|
fun printPetscii(char: Short, inverseVideo: Boolean=false) {
|
||||||
if(char==13.toShort() || char==141.toShort()) {
|
if(char==13.toShort() || char==141.toShort()) {
|
||||||
cursorX=0
|
cursorX=0
|
||||||
@ -95,7 +98,7 @@ class BitmapScreenPanel : KeyListener, JPanel() {
|
|||||||
val graphics = image.graphics as Graphics2D
|
val graphics = image.graphics as Graphics2D
|
||||||
graphics.drawImage(screen, 0, -8, null)
|
graphics.drawImage(screen, 0, -8, null)
|
||||||
val color = graphics.color
|
val color = graphics.color
|
||||||
graphics.color = Colors.palette[6]
|
graphics.color = MachineDefinition.colorPalette[6]
|
||||||
graphics.fillRect(0, 24*8, SCREENWIDTH, 25*8)
|
graphics.fillRect(0, 24*8, SCREENWIDTH, 25*8)
|
||||||
graphics.color=color
|
graphics.color=color
|
||||||
cursorY--
|
cursorY--
|
||||||
@ -103,7 +106,7 @@ class BitmapScreenPanel : KeyListener, JPanel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun writeTextAt(x: Int, y: Int, text: String, color: Short, lowercase: Boolean, inverseVideo: Boolean=false) {
|
fun writeTextAt(x: Int, y: Int, text: String, color: Short, lowercase: Boolean, inverseVideo: Boolean=false) {
|
||||||
val colorIdx = (color % Colors.palette.size).toShort()
|
val colorIdx = (color % MachineDefinition.colorPalette.size).toShort()
|
||||||
var xx=x
|
var xx=x
|
||||||
for(clearx in xx until xx+text.length) {
|
for(clearx in xx until xx+text.length) {
|
||||||
g2d.clearRect(8*clearx, 8*y, 8, 8)
|
g2d.clearRect(8*clearx, 8*y, 8, 8)
|
||||||
@ -117,16 +120,16 @@ class BitmapScreenPanel : KeyListener, JPanel() {
|
|||||||
|
|
||||||
fun setPetscii(x: Int, y: Int, petscii: Short, color: Short, inverseVideo: Boolean) {
|
fun setPetscii(x: Int, y: Int, petscii: Short, color: Short, inverseVideo: Boolean) {
|
||||||
g2d.clearRect(8*x, 8*y, 8, 8)
|
g2d.clearRect(8*x, 8*y, 8, 8)
|
||||||
val colorIdx = (color % Colors.palette.size).toShort()
|
val colorIdx = (color % MachineDefinition.colorPalette.size).toShort()
|
||||||
val screencode = Petscii.petscii2scr(petscii, inverseVideo)
|
val screencode = Petscii.petscii2scr(petscii, inverseVideo)
|
||||||
val coloredImage = Charset.getColoredChar(screencode, colorIdx)
|
val coloredImage = MachineDefinition.Charset.getColoredChar(screencode, colorIdx)
|
||||||
g2d.drawImage(coloredImage, 8*x, 8*y , null)
|
g2d.drawImage(coloredImage, 8*x, 8*y , null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setChar(x: Int, y: Int, screencode: Short, color: Short) {
|
fun setChar(x: Int, y: Int, screencode: Short, color: Short) {
|
||||||
g2d.clearRect(8*x, 8*y, 8, 8)
|
g2d.clearRect(8*x, 8*y, 8, 8)
|
||||||
val colorIdx = (color % Colors.palette.size).toShort()
|
val colorIdx = (color % MachineDefinition.colorPalette.size).toShort()
|
||||||
val coloredImage = Charset.getColoredChar(screencode, colorIdx)
|
val coloredImage = MachineDefinition.Charset.getColoredChar(screencode, colorIdx)
|
||||||
g2d.drawImage(coloredImage, 8*x, 8*y , null)
|
g2d.drawImage(coloredImage, 8*x, 8*y , null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,19 +163,19 @@ class ScreenDialog(title: String) : JFrame(title) {
|
|||||||
// the borders (top, left, right, bottom)
|
// the borders (top, left, right, bottom)
|
||||||
val borderTop = JPanel().apply {
|
val borderTop = JPanel().apply {
|
||||||
preferredSize = Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH +2*borderWidth), BitmapScreenPanel.SCALING * borderWidth)
|
preferredSize = Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH +2*borderWidth), BitmapScreenPanel.SCALING * borderWidth)
|
||||||
background = Colors.palette[14]
|
background = MachineDefinition.colorPalette[14]
|
||||||
}
|
}
|
||||||
val borderBottom = JPanel().apply {
|
val borderBottom = JPanel().apply {
|
||||||
preferredSize =Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH +2*borderWidth), BitmapScreenPanel.SCALING * borderWidth)
|
preferredSize =Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH +2*borderWidth), BitmapScreenPanel.SCALING * borderWidth)
|
||||||
background = Colors.palette[14]
|
background = MachineDefinition.colorPalette[14]
|
||||||
}
|
}
|
||||||
val borderLeft = JPanel().apply {
|
val borderLeft = JPanel().apply {
|
||||||
preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT)
|
preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT)
|
||||||
background = Colors.palette[14]
|
background = MachineDefinition.colorPalette[14]
|
||||||
}
|
}
|
||||||
val borderRight = JPanel().apply {
|
val borderRight = JPanel().apply {
|
||||||
preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT)
|
preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT)
|
||||||
background = Colors.palette[14]
|
background = MachineDefinition.colorPalette[14]
|
||||||
}
|
}
|
||||||
var c = GridBagConstraints()
|
var c = GridBagConstraints()
|
||||||
c.gridx=0; c.gridy=1; c.gridwidth=3
|
c.gridx=0; c.gridy=1; c.gridwidth=3
|
||||||
|
@ -1,25 +1,37 @@
|
|||||||
package prog8.vm.astvm
|
package prog8.vm.astvm
|
||||||
|
|
||||||
import prog8.ast.*
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.DataType
|
||||||
import prog8.ast.expressions.LiteralValue
|
import prog8.ast.base.Position
|
||||||
|
import prog8.ast.base.Register
|
||||||
|
import prog8.ast.base.VarDeclType
|
||||||
|
import prog8.ast.expressions.ArrayLiteralValue
|
||||||
|
import prog8.ast.expressions.NumericLiteralValue
|
||||||
|
import prog8.ast.expressions.StringLiteralValue
|
||||||
import prog8.ast.processing.IAstModifyingVisitor
|
import prog8.ast.processing.IAstModifyingVisitor
|
||||||
|
import prog8.ast.statements.Statement
|
||||||
|
import prog8.ast.statements.StructDecl
|
||||||
import prog8.ast.statements.VarDecl
|
import prog8.ast.statements.VarDecl
|
||||||
import prog8.compiler.HeapValues
|
import prog8.ast.statements.ZeropageWish
|
||||||
import prog8.vm.RuntimeValue
|
import prog8.vm.RuntimeValueArray
|
||||||
|
import prog8.vm.RuntimeValueNumeric
|
||||||
|
import prog8.vm.RuntimeValueString
|
||||||
|
|
||||||
class VariablesCreator(private val runtimeVariables: RuntimeVariables, private val heap: HeapValues) : IAstModifyingVisitor {
|
class VariablesCreator(private val runtimeVariables: RuntimeVariables) : IAstModifyingVisitor {
|
||||||
|
|
||||||
override fun visit(program: Program) {
|
override fun visit(program: Program) {
|
||||||
// define the three registers as global variables
|
// define the three registers as global variables
|
||||||
runtimeVariables.define(program.namespace, Register.A.name, RuntimeValue(DataType.UBYTE, 0))
|
runtimeVariables.define(program.namespace, Register.A.name, RuntimeValueNumeric(DataType.UBYTE, 0))
|
||||||
runtimeVariables.define(program.namespace, Register.X.name, RuntimeValue(DataType.UBYTE, 255))
|
runtimeVariables.define(program.namespace, Register.X.name, RuntimeValueNumeric(DataType.UBYTE, 255))
|
||||||
runtimeVariables.define(program.namespace, Register.Y.name, RuntimeValue(DataType.UBYTE, 0))
|
runtimeVariables.define(program.namespace, Register.Y.name, RuntimeValueNumeric(DataType.UBYTE, 0))
|
||||||
|
|
||||||
val globalpos = Position("<<global>>", 0, 0, 0)
|
val globalpos = Position("<<global>>", 0, 0, 0)
|
||||||
val vdA = VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, Register.A.name, LiteralValue.optimalInteger(0, globalpos), isArray = false, autoGenerated = true, position = globalpos)
|
val vdA = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.DONTCARE, null, Register.A.name, null,
|
||||||
val vdX = VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, Register.X.name, LiteralValue.optimalInteger(255, globalpos), isArray = false, autoGenerated = true, position = globalpos)
|
NumericLiteralValue.optimalInteger(0, globalpos), isArray = false, autogeneratedDontRemove = true, position = globalpos)
|
||||||
val vdY = VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, Register.Y.name, LiteralValue.optimalInteger(0, globalpos), isArray = false, autoGenerated = true, position = globalpos)
|
val vdX = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.DONTCARE, null, Register.X.name, null,
|
||||||
|
NumericLiteralValue.optimalInteger(255, globalpos), isArray = false, autogeneratedDontRemove = true, position = globalpos)
|
||||||
|
val vdY = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.DONTCARE, null, Register.Y.name, null,
|
||||||
|
NumericLiteralValue.optimalInteger(0, globalpos), isArray = false, autogeneratedDontRemove = true, position = globalpos)
|
||||||
vdA.linkParents(program.namespace)
|
vdA.linkParents(program.namespace)
|
||||||
vdX.linkParents(program.namespace)
|
vdX.linkParents(program.namespace)
|
||||||
vdY.linkParents(program.namespace)
|
vdY.linkParents(program.namespace)
|
||||||
@ -30,28 +42,39 @@ class VariablesCreator(private val runtimeVariables: RuntimeVariables, private v
|
|||||||
super.visit(program)
|
super.visit(program)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(decl: VarDecl): IStatement {
|
override fun visit(decl: VarDecl): Statement {
|
||||||
when(decl.type) {
|
// if the decl is part of a struct, just skip it
|
||||||
// we can assume the value in the vardecl already has been converted into a constant LiteralValue here.
|
if(decl.parent !is StructDecl) {
|
||||||
VarDeclType.VAR -> {
|
when (decl.type) {
|
||||||
val value = RuntimeValue.from(decl.value as LiteralValue, heap)
|
VarDeclType.VAR -> {
|
||||||
runtimeVariables.define(decl.definingScope(), decl.name, value)
|
if(decl.datatype!=DataType.STRUCT) {
|
||||||
}
|
val numericLv = decl.value as? NumericLiteralValue
|
||||||
VarDeclType.MEMORY -> {
|
val value = if(numericLv!=null) {
|
||||||
runtimeVariables.defineMemory(decl.definingScope(), decl.name, (decl.value as LiteralValue).asIntegerValue!!)
|
RuntimeValueNumeric.fromLv(numericLv)
|
||||||
}
|
} else {
|
||||||
VarDeclType.CONST -> {
|
val strLv = decl.value as? StringLiteralValue
|
||||||
// consts should have been const-folded away
|
val arrayLv = decl.value as? ArrayLiteralValue
|
||||||
|
when {
|
||||||
|
strLv!=null -> {
|
||||||
|
RuntimeValueString.fromLv(strLv)
|
||||||
|
}
|
||||||
|
arrayLv!=null -> {
|
||||||
|
RuntimeValueArray.fromLv(arrayLv)
|
||||||
|
}
|
||||||
|
else -> throw VmExecutionException("weird var type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
runtimeVariables.define(decl.definingScope(), decl.name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VarDeclType.MEMORY -> {
|
||||||
|
runtimeVariables.defineMemory(decl.definingScope(), decl.name, (decl.value as NumericLiteralValue).number.toInt())
|
||||||
|
}
|
||||||
|
VarDeclType.CONST -> {
|
||||||
|
// consts should have been const-folded away
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return super.visit(decl)
|
return super.visit(decl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// override fun accept(assignment: Assignment): IStatement {
|
|
||||||
// if(assignment is VariableInitializationAssignment) {
|
|
||||||
// println("INIT VAR $assignment")
|
|
||||||
// }
|
|
||||||
// return super.accept(assignment)
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
package prog8.vm.stackvm
|
|
||||||
|
|
||||||
import prog8.printSoftwareHeader
|
|
||||||
import prog8.vm.astvm.ScreenDialog
|
|
||||||
import java.awt.EventQueue
|
|
||||||
import javax.swing.Timer
|
|
||||||
import kotlin.system.exitProcess
|
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
|
||||||
stackVmMain(args)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun stackVmMain(args: Array<String>) {
|
|
||||||
printSoftwareHeader("StackVM")
|
|
||||||
|
|
||||||
if(args.size != 1) {
|
|
||||||
System.err.println("requires one argument: name of stackvm sourcecode file")
|
|
||||||
exitProcess(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
val program = Program.load(args.first())
|
|
||||||
val vm = StackVm(traceOutputFile = null)
|
|
||||||
val dialog = ScreenDialog("StackVM")
|
|
||||||
vm.load(program, dialog.canvas)
|
|
||||||
EventQueue.invokeLater {
|
|
||||||
dialog.pack()
|
|
||||||
dialog.isVisible = true
|
|
||||||
dialog.start()
|
|
||||||
|
|
||||||
val programTimer = Timer(10) { a ->
|
|
||||||
try {
|
|
||||||
vm.step()
|
|
||||||
} catch(bp: VmBreakpointException) {
|
|
||||||
println("Breakpoint: execution halted. Press enter to resume.")
|
|
||||||
readLine()
|
|
||||||
} catch (tx: VmTerminationException) {
|
|
||||||
println("Execution halted: ${tx.message}")
|
|
||||||
(a.source as Timer).stop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val irqTimer = Timer(1000/60) { a -> vm.irq(a.`when`) }
|
|
||||||
|
|
||||||
programTimer.start()
|
|
||||||
irqTimer.start()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,299 +0,0 @@
|
|||||||
package prog8.vm.stackvm
|
|
||||||
|
|
||||||
import prog8.ast.antlr.unescape
|
|
||||||
import prog8.ast.base.*
|
|
||||||
import prog8.ast.expressions.AddressOf
|
|
||||||
import prog8.ast.expressions.IdentifierReference
|
|
||||||
import prog8.vm.RuntimeValue
|
|
||||||
import prog8.compiler.HeapValues
|
|
||||||
import prog8.compiler.IntegerOrAddressOf
|
|
||||||
import prog8.compiler.intermediate.*
|
|
||||||
import java.io.File
|
|
||||||
import java.util.*
|
|
||||||
import java.util.regex.Pattern
|
|
||||||
|
|
||||||
class Program (val name: String,
|
|
||||||
val program: MutableList<Instruction>,
|
|
||||||
val variables: Map<String, RuntimeValue>,
|
|
||||||
val memoryPointers: Map<String, Pair<Int, DataType>>,
|
|
||||||
val labels: Map<String, Int>,
|
|
||||||
val memory: Map<Int, List<RuntimeValue>>,
|
|
||||||
val heap: HeapValues)
|
|
||||||
{
|
|
||||||
init {
|
|
||||||
// add end of program marker and some sentinel instructions, to correctly connect all others
|
|
||||||
program.add(LabelInstr("____program_end", false))
|
|
||||||
program.add(Instruction(Opcode.TERMINATE))
|
|
||||||
program.add(Instruction(Opcode.NOP))
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun load(filename: String): Program {
|
|
||||||
val lines = File(filename).readLines().withIndex().iterator()
|
|
||||||
val memory = mutableMapOf<Int, List<RuntimeValue>>()
|
|
||||||
val heap = HeapValues()
|
|
||||||
val program = mutableListOf<Instruction>()
|
|
||||||
val variables = mutableMapOf<String, RuntimeValue>()
|
|
||||||
val memoryPointers = mutableMapOf<String, Pair<Int, DataType>>()
|
|
||||||
val labels = mutableMapOf<String, Int>()
|
|
||||||
|
|
||||||
while(lines.hasNext()) {
|
|
||||||
val (lineNr, line) = lines.next()
|
|
||||||
if(line.startsWith(';') || line.isEmpty())
|
|
||||||
continue
|
|
||||||
else if(line=="%memory")
|
|
||||||
loadMemory(lines, memory)
|
|
||||||
else if(line=="%heap")
|
|
||||||
loadHeap(lines, heap)
|
|
||||||
else if(line.startsWith("%block "))
|
|
||||||
loadBlock(lines, heap, program, variables, memoryPointers, labels)
|
|
||||||
else throw VmExecutionException("syntax error at line ${lineNr + 1}")
|
|
||||||
}
|
|
||||||
return Program(filename, program, variables, memoryPointers, labels, memory, heap)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadBlock(lines: Iterator<IndexedValue<String>>,
|
|
||||||
heap: HeapValues,
|
|
||||||
program: MutableList<Instruction>,
|
|
||||||
variables: MutableMap<String, RuntimeValue>,
|
|
||||||
memoryPointers: MutableMap<String, Pair<Int, DataType>>,
|
|
||||||
labels: MutableMap<String, Int>)
|
|
||||||
{
|
|
||||||
while(true) {
|
|
||||||
val (_, line) = lines.next()
|
|
||||||
if(line.isEmpty())
|
|
||||||
continue
|
|
||||||
else if(line=="%end_block")
|
|
||||||
return
|
|
||||||
else if(line=="%variables")
|
|
||||||
loadVars(lines, variables)
|
|
||||||
else if(line=="%memorypointers")
|
|
||||||
loadMemoryPointers(lines, memoryPointers, heap)
|
|
||||||
else if(line=="%instructions") {
|
|
||||||
val (blockInstructions, blockLabels) = loadInstructions(lines, heap)
|
|
||||||
val baseIndex = program.size
|
|
||||||
program.addAll(blockInstructions)
|
|
||||||
val labelsWithIndex = blockLabels.mapValues { baseIndex+blockInstructions.indexOf(it.value) }
|
|
||||||
labels.putAll(labelsWithIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadHeap(lines: Iterator<IndexedValue<String>>, heap: HeapValues) {
|
|
||||||
val splitpattern = Pattern.compile("\\s+")
|
|
||||||
val heapvalues = mutableListOf<Triple<Int, DataType, String>>()
|
|
||||||
while(true) {
|
|
||||||
val (_, line) = lines.next()
|
|
||||||
if (line == "%end_heap")
|
|
||||||
break
|
|
||||||
val parts = line.split(splitpattern, limit=3)
|
|
||||||
val value = Triple(parts[0].toInt(), DataType.valueOf(parts[1].toUpperCase()), parts[2])
|
|
||||||
heapvalues.add(value)
|
|
||||||
}
|
|
||||||
heapvalues.sortedBy { it.first }.forEach {
|
|
||||||
when(it.second) {
|
|
||||||
DataType.STR, DataType.STR_S -> heap.addString(it.second, unescape(it.third.substring(1, it.third.length - 1), Position("<stackvmsource>", 0, 0, 0)))
|
|
||||||
DataType.ARRAY_UB, DataType.ARRAY_B,
|
|
||||||
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
|
||||||
val numbers = it.third.substring(1, it.third.length-1).split(',')
|
|
||||||
val intarray = numbers.map{number->
|
|
||||||
val num=number.trim()
|
|
||||||
if(num.startsWith("&")) {
|
|
||||||
// it's AddressOf
|
|
||||||
val scopedname = num.substring(1)
|
|
||||||
val iref = IdentifierReference(scopedname.split('.'), Position("<intermediate>", 0, 0, 0))
|
|
||||||
val addrOf = AddressOf(iref, Position("<intermediate>", 0, 0, 0))
|
|
||||||
addrOf.scopedname=scopedname
|
|
||||||
IntegerOrAddressOf(null, addrOf)
|
|
||||||
} else {
|
|
||||||
IntegerOrAddressOf(num.toInt(), null)
|
|
||||||
}
|
|
||||||
}.toTypedArray()
|
|
||||||
heap.addIntegerArray(it.second, intarray)
|
|
||||||
}
|
|
||||||
DataType.ARRAY_F -> {
|
|
||||||
val numbers = it.third.substring(1, it.third.length-1).split(',')
|
|
||||||
val doublearray = numbers.map{number->number.trim().toDouble()}.toDoubleArray()
|
|
||||||
heap.addDoublesArray(doublearray)
|
|
||||||
}
|
|
||||||
in NumericDatatypes -> throw VmExecutionException("invalid heap value type ${it.second}")
|
|
||||||
else -> throw VmExecutionException("weird datatype")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadInstructions(lines: Iterator<IndexedValue<String>>, heap: HeapValues): Pair<MutableList<Instruction>, Map<String, Instruction>> {
|
|
||||||
val instructions = mutableListOf<Instruction>()
|
|
||||||
val labels = mutableMapOf<String, Instruction>()
|
|
||||||
val splitpattern = Pattern.compile("\\s+")
|
|
||||||
val nextInstructionLabels = Stack<String>() // more than one label can occur on the isSameAs line
|
|
||||||
|
|
||||||
while(true) {
|
|
||||||
val (lineNr, line) = lines.next()
|
|
||||||
if(line.isEmpty())
|
|
||||||
continue
|
|
||||||
if(line=="%end_instructions")
|
|
||||||
return Pair(instructions, labels)
|
|
||||||
if(!line.startsWith(' ') && line.endsWith(':')) {
|
|
||||||
nextInstructionLabels.push(line.substring(0, line.length-1))
|
|
||||||
} else if(line.startsWith(' ')) {
|
|
||||||
val parts = line.trimStart().split(splitpattern, limit = 2)
|
|
||||||
val opcodeStr = parts[0].toUpperCase()
|
|
||||||
val opcode= Opcode.valueOf(if(opcodeStr.startsWith('_')) opcodeStr.substring(1) else opcodeStr)
|
|
||||||
val args = if(parts.size==2) parts[1] else null
|
|
||||||
val instruction = when(opcode) {
|
|
||||||
Opcode.LINE -> Instruction(opcode, null, callLabel = args)
|
|
||||||
Opcode.JUMP, Opcode.CALL, Opcode.BNEG, Opcode.BPOS,
|
|
||||||
Opcode.BZ, Opcode.BNZ, Opcode.BCS, Opcode.BCC,
|
|
||||||
Opcode.JZ, Opcode.JNZ, Opcode.JZW, Opcode.JNZW -> {
|
|
||||||
if(args!!.startsWith('$')) {
|
|
||||||
Instruction(opcode, RuntimeValue(DataType.UWORD, args.substring(1).toInt(16)))
|
|
||||||
} else {
|
|
||||||
Instruction(opcode, callLabel = args)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
in opcodesWithVarArgument -> {
|
|
||||||
val withoutQuotes =
|
|
||||||
if(args!!.startsWith('"') && args.endsWith('"'))
|
|
||||||
args.substring(1, args.length-1) else args
|
|
||||||
|
|
||||||
Instruction(opcode, callLabel = withoutQuotes)
|
|
||||||
}
|
|
||||||
Opcode.SYSCALL -> {
|
|
||||||
if(args!! in syscallNames) {
|
|
||||||
val call = Syscall.valueOf(args)
|
|
||||||
Instruction(opcode, RuntimeValue(DataType.UBYTE, call.callNr))
|
|
||||||
} else {
|
|
||||||
val args2 = args.replace('.', '_')
|
|
||||||
if(args2 in syscallNames) {
|
|
||||||
val call = Syscall.valueOf(args2)
|
|
||||||
Instruction(opcode, RuntimeValue(DataType.UBYTE, call.callNr))
|
|
||||||
} else {
|
|
||||||
// the syscall is not yet implemented. emit a stub.
|
|
||||||
Instruction(Opcode.SYSCALL, RuntimeValue(DataType.UBYTE, Syscall.SYSCALLSTUB.callNr), callLabel = args2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Opcode.INCLUDE_FILE -> {
|
|
||||||
val argparts = args!!.split(' ')
|
|
||||||
val filename = argparts[0]
|
|
||||||
val offset = if(argparts.size>=2 && argparts[1]!="null") getArgValue(argparts[1], heap) else null
|
|
||||||
val length = if(argparts.size>=3 && argparts[2]!="null") getArgValue(argparts[2], heap) else null
|
|
||||||
Instruction(opcode, offset, length, filename)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
Instruction(opcode, getArgValue(args, heap))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
instructions.add(instruction)
|
|
||||||
while(nextInstructionLabels.isNotEmpty()) {
|
|
||||||
val label = nextInstructionLabels.pop()
|
|
||||||
labels[label] = instruction
|
|
||||||
}
|
|
||||||
} else throw VmExecutionException("syntax error at line ${lineNr + 1}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getArgValue(args: String?, heap: HeapValues): RuntimeValue? {
|
|
||||||
if(args==null)
|
|
||||||
return null
|
|
||||||
if(args[0]=='"' && args[args.length-1]=='"') {
|
|
||||||
throw VmExecutionException("encountered a string arg value, but all strings should already have been moved into the heap")
|
|
||||||
}
|
|
||||||
val (type, valueStr) = args.split(':')
|
|
||||||
return when(type) {
|
|
||||||
"b" -> RuntimeValue(DataType.BYTE, valueStr.toShort(16))
|
|
||||||
"ub" -> RuntimeValue(DataType.UBYTE, valueStr.toShort(16))
|
|
||||||
"w" -> RuntimeValue(DataType.WORD, valueStr.toInt(16))
|
|
||||||
"uw" -> RuntimeValue(DataType.UWORD, valueStr.toInt(16))
|
|
||||||
"f" -> RuntimeValue(DataType.FLOAT, valueStr.toDouble())
|
|
||||||
"heap" -> {
|
|
||||||
val heapId = valueStr.toInt()
|
|
||||||
RuntimeValue(heap.get(heapId).type, heapId = heapId)
|
|
||||||
}
|
|
||||||
else -> throw VmExecutionException("invalid datatype $type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadVars(lines: Iterator<IndexedValue<String>>,
|
|
||||||
vars: MutableMap<String, RuntimeValue>) {
|
|
||||||
val splitpattern = Pattern.compile("\\s+")
|
|
||||||
while(true) {
|
|
||||||
val (_, line) = lines.next()
|
|
||||||
if(line=="%end_variables")
|
|
||||||
return
|
|
||||||
val (name, typeStr, valueStr) = line.split(splitpattern, limit = 3)
|
|
||||||
if(valueStr[0] !='"' && ':' !in valueStr)
|
|
||||||
throw VmExecutionException("missing value type character")
|
|
||||||
val value = when(val type = DataType.valueOf(typeStr.toUpperCase())) {
|
|
||||||
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, valueStr.substring(3).toShort(16))
|
|
||||||
DataType.BYTE -> RuntimeValue(DataType.BYTE, valueStr.substring(2).toShort(16))
|
|
||||||
DataType.UWORD -> RuntimeValue(DataType.UWORD, valueStr.substring(3).toInt(16))
|
|
||||||
DataType.WORD -> RuntimeValue(DataType.WORD, valueStr.substring(2).toInt(16))
|
|
||||||
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, valueStr.substring(2).toDouble())
|
|
||||||
in StringDatatypes -> {
|
|
||||||
if(valueStr.startsWith('"') && valueStr.endsWith('"'))
|
|
||||||
throw VmExecutionException("encountered a var with a string value, but all string values should already have been moved into the heap")
|
|
||||||
else if(!valueStr.startsWith("heap:"))
|
|
||||||
throw VmExecutionException("invalid string value, should be a heap reference")
|
|
||||||
else {
|
|
||||||
val heapId = valueStr.substring(5).toInt()
|
|
||||||
RuntimeValue(type, heapId = heapId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
in ArrayDatatypes -> {
|
|
||||||
if(!valueStr.startsWith("heap:"))
|
|
||||||
throw VmExecutionException("invalid array value, should be a heap reference")
|
|
||||||
else {
|
|
||||||
val heapId = valueStr.substring(5).toInt()
|
|
||||||
RuntimeValue(type, heapId = heapId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> throw VmExecutionException("weird datatype")
|
|
||||||
}
|
|
||||||
vars[name] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadMemoryPointers(lines: Iterator<IndexedValue<String>>,
|
|
||||||
pointers: MutableMap<String, Pair<Int, DataType>>,
|
|
||||||
heap: HeapValues) {
|
|
||||||
val splitpattern = Pattern.compile("\\s+")
|
|
||||||
while(true) {
|
|
||||||
val (_, line) = lines.next()
|
|
||||||
if(line=="%end_memorypointers")
|
|
||||||
return
|
|
||||||
val (name, typeStr, valueStr) = line.split(splitpattern, limit = 3)
|
|
||||||
if(valueStr[0] !='"' && ':' !in valueStr)
|
|
||||||
throw VmExecutionException("missing value type character")
|
|
||||||
val type = DataType.valueOf(typeStr.toUpperCase())
|
|
||||||
val value = getArgValue(valueStr, heap)!!.integerValue()
|
|
||||||
pointers[name] = Pair(value, type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadMemory(lines: Iterator<IndexedValue<String>>, memory: MutableMap<Int, List<RuntimeValue>>): Map<Int, List<RuntimeValue>> {
|
|
||||||
while(true) {
|
|
||||||
val (lineNr, line) = lines.next()
|
|
||||||
if(line=="%end_memory")
|
|
||||||
return memory
|
|
||||||
val address = line.substringBefore(' ').toInt(16)
|
|
||||||
val rest = line.substringAfter(' ').trim()
|
|
||||||
if(rest.startsWith('"')) {
|
|
||||||
TODO("memory init with char/string")
|
|
||||||
} else {
|
|
||||||
val valueStrings = rest.split(' ')
|
|
||||||
val values = mutableListOf<RuntimeValue>()
|
|
||||||
valueStrings.forEach {
|
|
||||||
when(it.length) {
|
|
||||||
2 -> values.add(RuntimeValue(DataType.UBYTE, it.toShort(16)))
|
|
||||||
4 -> values.add(RuntimeValue(DataType.UWORD, it.toInt(16)))
|
|
||||||
else -> throw VmExecutionException("invalid value at line $lineNr+1")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
memory[address] = values
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -3,24 +3,29 @@ package prog8tests
|
|||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.jupiter.api.TestInstance
|
import org.junit.jupiter.api.TestInstance
|
||||||
import prog8.ast.base.DataType
|
import prog8.ast.base.DataType
|
||||||
import prog8.ast.expressions.LiteralValue
|
|
||||||
import prog8.ast.base.Position
|
import prog8.ast.base.Position
|
||||||
import kotlin.test.*
|
import prog8.ast.expressions.ArrayLiteralValue
|
||||||
|
import prog8.ast.expressions.NumericLiteralValue
|
||||||
|
import prog8.ast.expressions.StringLiteralValue
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertFalse
|
||||||
|
import kotlin.test.assertNotEquals
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
|
||||||
private fun sameValueAndType(lv1: LiteralValue, lv2: LiteralValue): Boolean {
|
private fun sameValueAndType(lv1: NumericLiteralValue, lv2: NumericLiteralValue): Boolean {
|
||||||
return lv1.type==lv2.type && lv1==lv2
|
return lv1.type==lv2.type && lv1==lv2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
class TestParserLiteralValue {
|
class TestParserNumericLiteralValue {
|
||||||
|
|
||||||
private val dummyPos = Position("test", 0, 0, 0)
|
private val dummyPos = Position("test", 0, 0, 0)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testIdentity() {
|
fun testIdentity() {
|
||||||
val v = LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos)
|
val v = NumericLiteralValue(DataType.UWORD, 12345, dummyPos)
|
||||||
assertEquals(v, v)
|
assertEquals(v, v)
|
||||||
assertFalse(v != v)
|
assertFalse(v != v)
|
||||||
assertTrue(v <= v)
|
assertTrue(v <= v)
|
||||||
@ -28,104 +33,109 @@ class TestParserLiteralValue {
|
|||||||
assertFalse(v < v)
|
assertFalse(v < v)
|
||||||
assertFalse(v > v)
|
assertFalse(v > v)
|
||||||
|
|
||||||
assertTrue(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos)))
|
assertTrue(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12345, dummyPos)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testEqualsAndNotEquals() {
|
fun testEqualsAndNotEquals() {
|
||||||
assertEquals(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UBYTE, 100, position = dummyPos))
|
assertEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
|
||||||
assertEquals(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 100, position = dummyPos))
|
assertEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 100, dummyPos))
|
||||||
assertEquals(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos))
|
assertEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos))
|
||||||
assertEquals(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos), LiteralValue(DataType.UBYTE, 254, position = dummyPos))
|
assertEquals(NumericLiteralValue(DataType.UWORD, 254, dummyPos), NumericLiteralValue(DataType.UBYTE, 254, dummyPos))
|
||||||
assertEquals(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos))
|
assertEquals(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12345, dummyPos))
|
||||||
assertEquals(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 12345.0, position = dummyPos))
|
assertEquals(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12345.0, dummyPos))
|
||||||
assertEquals(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos), LiteralValue(DataType.UBYTE, 100, position = dummyPos))
|
assertEquals(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
|
||||||
assertEquals(LiteralValue(DataType.FLOAT, floatvalue = 22239.0, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 22239, position = dummyPos))
|
assertEquals(NumericLiteralValue(DataType.FLOAT, 22239.0, dummyPos), NumericLiteralValue(DataType.UWORD, 22239, dummyPos))
|
||||||
assertEquals(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos))
|
assertEquals(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos))
|
||||||
|
|
||||||
assertTrue(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UBYTE, 100, position = dummyPos)))
|
assertTrue(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos)))
|
||||||
assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 100, position = dummyPos)))
|
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 100, dummyPos)))
|
||||||
assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos)))
|
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)))
|
||||||
assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos), LiteralValue(DataType.UBYTE, 254, position = dummyPos)))
|
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 254, dummyPos), NumericLiteralValue(DataType.UBYTE, 254, dummyPos)))
|
||||||
assertTrue(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos)))
|
assertTrue(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12345, dummyPos)))
|
||||||
assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 12345.0, position = dummyPos)))
|
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12345.0, dummyPos)))
|
||||||
assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos), LiteralValue(DataType.UBYTE, 100, position = dummyPos)))
|
assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos)))
|
||||||
assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 22239.0, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 22239, position = dummyPos)))
|
assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 22239.0, dummyPos), NumericLiteralValue(DataType.UWORD, 22239, dummyPos)))
|
||||||
assertTrue(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos)))
|
assertTrue(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos)))
|
||||||
|
|
||||||
assertNotEquals(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UBYTE, 101, position = dummyPos))
|
assertNotEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 101, dummyPos))
|
||||||
assertNotEquals(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 101, position = dummyPos))
|
assertNotEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 101, dummyPos))
|
||||||
assertNotEquals(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 101.0, position = dummyPos))
|
assertNotEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 101.0, dummyPos))
|
||||||
assertNotEquals(LiteralValue(DataType.UWORD, wordvalue = 245, position = dummyPos), LiteralValue(DataType.UBYTE, 246, position = dummyPos))
|
assertNotEquals(NumericLiteralValue(DataType.UWORD, 245, dummyPos), NumericLiteralValue(DataType.UBYTE, 246, dummyPos))
|
||||||
assertNotEquals(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 12346, position = dummyPos))
|
assertNotEquals(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12346, dummyPos))
|
||||||
assertNotEquals(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 12346.0, position = dummyPos))
|
assertNotEquals(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12346.0, dummyPos))
|
||||||
assertNotEquals(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.UBYTE, 9, position = dummyPos))
|
assertNotEquals(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UBYTE, 9, dummyPos))
|
||||||
assertNotEquals(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 9, position = dummyPos))
|
assertNotEquals(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UWORD, 9, dummyPos))
|
||||||
assertNotEquals(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 9.0, position = dummyPos))
|
assertNotEquals(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.FLOAT, 9.0, dummyPos))
|
||||||
|
|
||||||
assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UBYTE, 101, position = dummyPos)))
|
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 101, dummyPos)))
|
||||||
assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 101, position = dummyPos)))
|
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 101, dummyPos)))
|
||||||
assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 101.0, position = dummyPos)))
|
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 101.0, dummyPos)))
|
||||||
assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 245, position = dummyPos), LiteralValue(DataType.UBYTE, 246, position = dummyPos)))
|
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 245, dummyPos), NumericLiteralValue(DataType.UBYTE, 246, dummyPos)))
|
||||||
assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 12346, position = dummyPos)))
|
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12346, dummyPos)))
|
||||||
assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 12346.0, position = dummyPos)))
|
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12346.0, dummyPos)))
|
||||||
assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.UBYTE, 9, position = dummyPos)))
|
assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UBYTE, 9, dummyPos)))
|
||||||
assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 9, position = dummyPos)))
|
assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UWORD, 9, dummyPos)))
|
||||||
assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 9.0, position = dummyPos)))
|
assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.FLOAT, 9.0, dummyPos)))
|
||||||
|
|
||||||
assertTrue(sameValueAndType(LiteralValue(DataType.STR, strvalue = "hello", position = dummyPos), LiteralValue(DataType.STR, strvalue = "hello", position = dummyPos)))
|
|
||||||
assertFalse(sameValueAndType(LiteralValue(DataType.STR, strvalue = "hello", position = dummyPos), LiteralValue(DataType.STR, strvalue = "bye", position = dummyPos)))
|
|
||||||
|
|
||||||
val lvOne = LiteralValue(DataType.UBYTE, 1, position = dummyPos)
|
}
|
||||||
val lvTwo = LiteralValue(DataType.UBYTE, 2, position = dummyPos)
|
|
||||||
val lvThree = LiteralValue(DataType.UBYTE, 3, position = dummyPos)
|
@Test
|
||||||
val lvOneR = LiteralValue(DataType.UBYTE, 1, position = dummyPos)
|
fun testEqualsRef() {
|
||||||
val lvTwoR = LiteralValue(DataType.UBYTE, 2, position = dummyPos)
|
assertTrue(StringLiteralValue(DataType.STR, "hello", dummyPos) == StringLiteralValue(DataType.STR, "hello", dummyPos))
|
||||||
val lvThreeR = LiteralValue(DataType.UBYTE, 3, position = dummyPos)
|
assertFalse(StringLiteralValue(DataType.STR, "hello", dummyPos) == StringLiteralValue(DataType.STR, "bye", dummyPos))
|
||||||
val lvFour= LiteralValue(DataType.UBYTE, 4, position = dummyPos)
|
|
||||||
val lv1 = LiteralValue(DataType.ARRAY_UB, arrayvalue = arrayOf(lvOne, lvTwo, lvThree), position = dummyPos)
|
val lvOne = NumericLiteralValue(DataType.UBYTE, 1, dummyPos)
|
||||||
val lv2 = LiteralValue(DataType.ARRAY_UB, arrayvalue = arrayOf(lvOneR, lvTwoR, lvThreeR), position = dummyPos)
|
val lvTwo = NumericLiteralValue(DataType.UBYTE, 2, dummyPos)
|
||||||
val lv3 = LiteralValue(DataType.ARRAY_UB, arrayvalue = arrayOf(lvOneR, lvTwoR, lvFour), position = dummyPos)
|
val lvThree = NumericLiteralValue(DataType.UBYTE, 3, dummyPos)
|
||||||
|
val lvOneR = NumericLiteralValue(DataType.UBYTE, 1, dummyPos)
|
||||||
|
val lvTwoR = NumericLiteralValue(DataType.UBYTE, 2, dummyPos)
|
||||||
|
val lvThreeR = NumericLiteralValue(DataType.UBYTE, 3, dummyPos)
|
||||||
|
val lvFour= NumericLiteralValue(DataType.UBYTE, 4, dummyPos)
|
||||||
|
val lv1 = ArrayLiteralValue(DataType.ARRAY_UB, arrayOf(lvOne, lvTwo, lvThree), null, dummyPos)
|
||||||
|
val lv2 = ArrayLiteralValue(DataType.ARRAY_UB, arrayOf(lvOneR, lvTwoR, lvThreeR), null, dummyPos)
|
||||||
|
val lv3 = ArrayLiteralValue(DataType.ARRAY_UB, arrayOf(lvOneR, lvTwoR, lvFour), null, dummyPos)
|
||||||
assertEquals(lv1, lv2)
|
assertEquals(lv1, lv2)
|
||||||
assertNotEquals(lv1, lv3)
|
assertNotEquals(lv1, lv3)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testGreaterThan(){
|
fun testGreaterThan(){
|
||||||
assertTrue(LiteralValue(DataType.UBYTE, 100, position = dummyPos) > LiteralValue(DataType.UBYTE, 99, position = dummyPos))
|
assertTrue(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) > NumericLiteralValue(DataType.UBYTE, 99, dummyPos))
|
||||||
assertTrue(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) > LiteralValue(DataType.UWORD, wordvalue = 253, position = dummyPos))
|
assertTrue(NumericLiteralValue(DataType.UWORD, 254, dummyPos) > NumericLiteralValue(DataType.UWORD, 253, dummyPos))
|
||||||
assertTrue(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) > LiteralValue(DataType.FLOAT, floatvalue = 99.9, position = dummyPos))
|
assertTrue(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) > NumericLiteralValue(DataType.FLOAT, 99.9, dummyPos))
|
||||||
|
|
||||||
assertTrue(LiteralValue(DataType.UBYTE, 100, position = dummyPos) >= LiteralValue(DataType.UBYTE, 100, position = dummyPos))
|
assertTrue(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) >= NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
|
||||||
assertTrue(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) >= LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos))
|
assertTrue(NumericLiteralValue(DataType.UWORD, 254, dummyPos) >= NumericLiteralValue(DataType.UWORD, 254, dummyPos))
|
||||||
assertTrue(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) >= LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos))
|
assertTrue(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) >= NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos))
|
||||||
|
|
||||||
assertFalse(LiteralValue(DataType.UBYTE, 100, position = dummyPos) > LiteralValue(DataType.UBYTE, 100, position = dummyPos))
|
assertFalse(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) > NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
|
||||||
assertFalse(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) > LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos))
|
assertFalse(NumericLiteralValue(DataType.UWORD, 254, dummyPos) > NumericLiteralValue(DataType.UWORD, 254, dummyPos))
|
||||||
assertFalse(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) > LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos))
|
assertFalse(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) > NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos))
|
||||||
|
|
||||||
assertFalse(LiteralValue(DataType.UBYTE, 100, position = dummyPos) >= LiteralValue(DataType.UBYTE, 101, position = dummyPos))
|
assertFalse(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) >= NumericLiteralValue(DataType.UBYTE, 101, dummyPos))
|
||||||
assertFalse(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) >= LiteralValue(DataType.UWORD, wordvalue = 255, position = dummyPos))
|
assertFalse(NumericLiteralValue(DataType.UWORD, 254, dummyPos) >= NumericLiteralValue(DataType.UWORD, 255, dummyPos))
|
||||||
assertFalse(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) >= LiteralValue(DataType.FLOAT, floatvalue = 100.1, position = dummyPos))
|
assertFalse(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) >= NumericLiteralValue(DataType.FLOAT, 100.1, dummyPos))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testLessThan() {
|
fun testLessThan() {
|
||||||
assertTrue(LiteralValue(DataType.UBYTE, 100, position = dummyPos) < LiteralValue(DataType.UBYTE, 101, position = dummyPos))
|
assertTrue(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) < NumericLiteralValue(DataType.UBYTE, 101, dummyPos))
|
||||||
assertTrue(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) < LiteralValue(DataType.UWORD, wordvalue = 255, position = dummyPos))
|
assertTrue(NumericLiteralValue(DataType.UWORD, 254, dummyPos) < NumericLiteralValue(DataType.UWORD, 255, dummyPos))
|
||||||
assertTrue(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) < LiteralValue(DataType.FLOAT, floatvalue = 100.1, position = dummyPos))
|
assertTrue(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) < NumericLiteralValue(DataType.FLOAT, 100.1, dummyPos))
|
||||||
|
|
||||||
assertTrue(LiteralValue(DataType.UBYTE, 100, position = dummyPos) <= LiteralValue(DataType.UBYTE, 100, position = dummyPos))
|
assertTrue(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) <= NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
|
||||||
assertTrue(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) <= LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos))
|
assertTrue(NumericLiteralValue(DataType.UWORD, 254, dummyPos) <= NumericLiteralValue(DataType.UWORD, 254, dummyPos))
|
||||||
assertTrue(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) <= LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos))
|
assertTrue(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) <= NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos))
|
||||||
|
|
||||||
assertFalse(LiteralValue(DataType.UBYTE, 100, position = dummyPos) < LiteralValue(DataType.UBYTE, 100, position = dummyPos))
|
assertFalse(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) < NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
|
||||||
assertFalse(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) < LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos))
|
assertFalse(NumericLiteralValue(DataType.UWORD, 254, dummyPos) < NumericLiteralValue(DataType.UWORD, 254, dummyPos))
|
||||||
assertFalse(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) < LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos))
|
assertFalse(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) < NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos))
|
||||||
|
|
||||||
assertFalse(LiteralValue(DataType.UBYTE, 100, position = dummyPos) <= LiteralValue(DataType.UBYTE, 99, position = dummyPos))
|
assertFalse(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) <= NumericLiteralValue(DataType.UBYTE, 99, dummyPos))
|
||||||
assertFalse(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) <= LiteralValue(DataType.UWORD, wordvalue = 253, position = dummyPos))
|
assertFalse(NumericLiteralValue(DataType.UWORD, 254, dummyPos) <= NumericLiteralValue(DataType.UWORD, 253, dummyPos))
|
||||||
assertFalse(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) <= LiteralValue(DataType.FLOAT, floatvalue = 99.9, position = dummyPos))
|
assertFalse(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) <= NumericLiteralValue(DataType.FLOAT, 99.9, dummyPos))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,66 +3,66 @@ package prog8tests
|
|||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.jupiter.api.TestInstance
|
import org.junit.jupiter.api.TestInstance
|
||||||
import prog8.ast.base.DataType
|
import prog8.ast.base.DataType
|
||||||
import prog8.vm.RuntimeValue
|
import prog8.vm.RuntimeValueNumeric
|
||||||
import kotlin.test.*
|
import kotlin.test.*
|
||||||
|
|
||||||
|
|
||||||
private fun sameValueAndType(v1: RuntimeValue, v2: RuntimeValue): Boolean {
|
private fun sameValueAndType(v1: RuntimeValueNumeric, v2: RuntimeValueNumeric): Boolean {
|
||||||
return v1.type==v2.type && v1==v2
|
return v1.type==v2.type && v1==v2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
class TestRuntimeValue {
|
class TestRuntimeValueNumeric {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testValueRanges() {
|
fun testValueRanges() {
|
||||||
assertEquals(0, RuntimeValue(DataType.UBYTE, 0).integerValue())
|
assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 0).integerValue())
|
||||||
assertEquals(255, RuntimeValue(DataType.UBYTE, 255).integerValue())
|
assertEquals(255, RuntimeValueNumeric(DataType.UBYTE, 255).integerValue())
|
||||||
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.UBYTE, -1)}
|
assertFailsWith<IllegalArgumentException> { RuntimeValueNumeric(DataType.UBYTE, -1)}
|
||||||
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.UBYTE, 256)}
|
assertFailsWith<IllegalArgumentException> { RuntimeValueNumeric(DataType.UBYTE, 256)}
|
||||||
|
|
||||||
assertEquals(0, RuntimeValue(DataType.BYTE, 0).integerValue())
|
assertEquals(0, RuntimeValueNumeric(DataType.BYTE, 0).integerValue())
|
||||||
assertEquals(-128, RuntimeValue(DataType.BYTE, -128).integerValue())
|
assertEquals(-128, RuntimeValueNumeric(DataType.BYTE, -128).integerValue())
|
||||||
assertEquals(127, RuntimeValue(DataType.BYTE, 127).integerValue())
|
assertEquals(127, RuntimeValueNumeric(DataType.BYTE, 127).integerValue())
|
||||||
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.BYTE, -129)}
|
assertFailsWith<IllegalArgumentException> { RuntimeValueNumeric(DataType.BYTE, -129)}
|
||||||
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.BYTE, 128)}
|
assertFailsWith<IllegalArgumentException> { RuntimeValueNumeric(DataType.BYTE, 128)}
|
||||||
|
|
||||||
assertEquals(0, RuntimeValue(DataType.UWORD, 0).integerValue())
|
assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 0).integerValue())
|
||||||
assertEquals(65535, RuntimeValue(DataType.UWORD, 65535).integerValue())
|
assertEquals(65535, RuntimeValueNumeric(DataType.UWORD, 65535).integerValue())
|
||||||
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.UWORD, -1)}
|
assertFailsWith<IllegalArgumentException> { RuntimeValueNumeric(DataType.UWORD, -1)}
|
||||||
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.UWORD, 65536)}
|
assertFailsWith<IllegalArgumentException> { RuntimeValueNumeric(DataType.UWORD, 65536)}
|
||||||
|
|
||||||
assertEquals(0, RuntimeValue(DataType.WORD, 0).integerValue())
|
assertEquals(0, RuntimeValueNumeric(DataType.WORD, 0).integerValue())
|
||||||
assertEquals(-32768, RuntimeValue(DataType.WORD, -32768).integerValue())
|
assertEquals(-32768, RuntimeValueNumeric(DataType.WORD, -32768).integerValue())
|
||||||
assertEquals(32767, RuntimeValue(DataType.WORD, 32767).integerValue())
|
assertEquals(32767, RuntimeValueNumeric(DataType.WORD, 32767).integerValue())
|
||||||
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.WORD, -32769)}
|
assertFailsWith<IllegalArgumentException> { RuntimeValueNumeric(DataType.WORD, -32769)}
|
||||||
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.WORD, 32768)}
|
assertFailsWith<IllegalArgumentException> { RuntimeValueNumeric(DataType.WORD, 32768)}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testTruthiness()
|
fun testTruthiness()
|
||||||
{
|
{
|
||||||
assertFalse(RuntimeValue(DataType.BYTE, 0).asBoolean)
|
assertFalse(RuntimeValueNumeric(DataType.BYTE, 0).asBoolean)
|
||||||
assertFalse(RuntimeValue(DataType.UBYTE, 0).asBoolean)
|
assertFalse(RuntimeValueNumeric(DataType.UBYTE, 0).asBoolean)
|
||||||
assertFalse(RuntimeValue(DataType.WORD, 0).asBoolean)
|
assertFalse(RuntimeValueNumeric(DataType.WORD, 0).asBoolean)
|
||||||
assertFalse(RuntimeValue(DataType.UWORD, 0).asBoolean)
|
assertFalse(RuntimeValueNumeric(DataType.UWORD, 0).asBoolean)
|
||||||
assertFalse(RuntimeValue(DataType.FLOAT, 0.0).asBoolean)
|
assertFalse(RuntimeValueNumeric(DataType.FLOAT, 0.0).asBoolean)
|
||||||
|
|
||||||
assertTrue(RuntimeValue(DataType.BYTE, 42).asBoolean)
|
assertTrue(RuntimeValueNumeric(DataType.BYTE, 42).asBoolean)
|
||||||
assertTrue(RuntimeValue(DataType.UBYTE, 42).asBoolean)
|
assertTrue(RuntimeValueNumeric(DataType.UBYTE, 42).asBoolean)
|
||||||
assertTrue(RuntimeValue(DataType.WORD, 42).asBoolean)
|
assertTrue(RuntimeValueNumeric(DataType.WORD, 42).asBoolean)
|
||||||
assertTrue(RuntimeValue(DataType.UWORD, 42).asBoolean)
|
assertTrue(RuntimeValueNumeric(DataType.UWORD, 42).asBoolean)
|
||||||
assertTrue(RuntimeValue(DataType.FLOAT, 42.0).asBoolean)
|
assertTrue(RuntimeValueNumeric(DataType.FLOAT, 42.0).asBoolean)
|
||||||
assertTrue(RuntimeValue(DataType.BYTE, -42).asBoolean)
|
assertTrue(RuntimeValueNumeric(DataType.BYTE, -42).asBoolean)
|
||||||
assertTrue(RuntimeValue(DataType.WORD, -42).asBoolean)
|
assertTrue(RuntimeValueNumeric(DataType.WORD, -42).asBoolean)
|
||||||
assertTrue(RuntimeValue(DataType.FLOAT, -42.0).asBoolean)
|
assertTrue(RuntimeValueNumeric(DataType.FLOAT, -42.0).asBoolean)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testIdentity() {
|
fun testIdentity() {
|
||||||
val v = RuntimeValue(DataType.UWORD, 12345)
|
val v = RuntimeValueNumeric(DataType.UWORD, 12345)
|
||||||
assertEquals(v, v)
|
assertEquals(v, v)
|
||||||
assertFalse(v != v)
|
assertFalse(v != v)
|
||||||
assertTrue(v<=v)
|
assertTrue(v<=v)
|
||||||
@ -70,312 +70,283 @@ class TestRuntimeValue {
|
|||||||
assertFalse(v<v)
|
assertFalse(v<v)
|
||||||
assertFalse(v>v)
|
assertFalse(v>v)
|
||||||
|
|
||||||
assertTrue(sameValueAndType(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UBYTE, 100)))
|
assertTrue(sameValueAndType(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UBYTE, 100)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testEqualsAndNotEquals() {
|
fun testEqualsAndNotEquals() {
|
||||||
assertEquals(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UBYTE, 100))
|
assertEquals(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UBYTE, 100))
|
||||||
assertEquals(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UWORD, 100))
|
assertEquals(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UWORD, 100))
|
||||||
assertEquals(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.FLOAT, 100))
|
assertEquals(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.FLOAT, 100))
|
||||||
assertEquals(RuntimeValue(DataType.UWORD, 254), RuntimeValue(DataType.UBYTE, 254))
|
assertEquals(RuntimeValueNumeric(DataType.UWORD, 254), RuntimeValueNumeric(DataType.UBYTE, 254))
|
||||||
assertEquals(RuntimeValue(DataType.UWORD, 12345), RuntimeValue(DataType.UWORD, 12345))
|
assertEquals(RuntimeValueNumeric(DataType.UWORD, 12345), RuntimeValueNumeric(DataType.UWORD, 12345))
|
||||||
assertEquals(RuntimeValue(DataType.UWORD, 12345), RuntimeValue(DataType.FLOAT, 12345))
|
assertEquals(RuntimeValueNumeric(DataType.UWORD, 12345), RuntimeValueNumeric(DataType.FLOAT, 12345))
|
||||||
assertEquals(RuntimeValue(DataType.FLOAT, 100.0), RuntimeValue(DataType.UBYTE, 100))
|
assertEquals(RuntimeValueNumeric(DataType.FLOAT, 100.0), RuntimeValueNumeric(DataType.UBYTE, 100))
|
||||||
assertEquals(RuntimeValue(DataType.FLOAT, 22239.0), RuntimeValue(DataType.UWORD, 22239))
|
assertEquals(RuntimeValueNumeric(DataType.FLOAT, 22239.0), RuntimeValueNumeric(DataType.UWORD, 22239))
|
||||||
assertEquals(RuntimeValue(DataType.FLOAT, 9.99), RuntimeValue(DataType.FLOAT, 9.99))
|
assertEquals(RuntimeValueNumeric(DataType.FLOAT, 9.99), RuntimeValueNumeric(DataType.FLOAT, 9.99))
|
||||||
|
|
||||||
assertTrue(sameValueAndType(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UBYTE, 100)))
|
assertTrue(sameValueAndType(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UBYTE, 100)))
|
||||||
assertFalse(sameValueAndType(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UWORD, 100)))
|
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UWORD, 100)))
|
||||||
assertFalse(sameValueAndType(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.FLOAT, 100)))
|
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.FLOAT, 100)))
|
||||||
assertFalse(sameValueAndType(RuntimeValue(DataType.UWORD, 254), RuntimeValue(DataType.UBYTE, 254)))
|
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UWORD, 254), RuntimeValueNumeric(DataType.UBYTE, 254)))
|
||||||
assertTrue(sameValueAndType(RuntimeValue(DataType.UWORD, 12345), RuntimeValue(DataType.UWORD, 12345)))
|
assertTrue(sameValueAndType(RuntimeValueNumeric(DataType.UWORD, 12345), RuntimeValueNumeric(DataType.UWORD, 12345)))
|
||||||
assertFalse(sameValueAndType(RuntimeValue(DataType.UWORD, 12345), RuntimeValue(DataType.FLOAT, 12345)))
|
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UWORD, 12345), RuntimeValueNumeric(DataType.FLOAT, 12345)))
|
||||||
assertFalse(sameValueAndType(RuntimeValue(DataType.FLOAT, 100.0), RuntimeValue(DataType.UBYTE, 100)))
|
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.FLOAT, 100.0), RuntimeValueNumeric(DataType.UBYTE, 100)))
|
||||||
assertFalse(sameValueAndType(RuntimeValue(DataType.FLOAT, 22239.0), RuntimeValue(DataType.UWORD, 22239)))
|
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.FLOAT, 22239.0), RuntimeValueNumeric(DataType.UWORD, 22239)))
|
||||||
assertTrue(sameValueAndType(RuntimeValue(DataType.FLOAT, 9.99), RuntimeValue(DataType.FLOAT, 9.99)))
|
assertTrue(sameValueAndType(RuntimeValueNumeric(DataType.FLOAT, 9.99), RuntimeValueNumeric(DataType.FLOAT, 9.99)))
|
||||||
|
|
||||||
assertNotEquals(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UBYTE, 101))
|
assertNotEquals(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UBYTE, 101))
|
||||||
assertNotEquals(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UWORD, 101))
|
assertNotEquals(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UWORD, 101))
|
||||||
assertNotEquals(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.FLOAT, 101))
|
assertNotEquals(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.FLOAT, 101))
|
||||||
assertNotEquals(RuntimeValue(DataType.UWORD, 245), RuntimeValue(DataType.UBYTE, 246))
|
assertNotEquals(RuntimeValueNumeric(DataType.UWORD, 245), RuntimeValueNumeric(DataType.UBYTE, 246))
|
||||||
assertNotEquals(RuntimeValue(DataType.UWORD, 12345), RuntimeValue(DataType.UWORD, 12346))
|
assertNotEquals(RuntimeValueNumeric(DataType.UWORD, 12345), RuntimeValueNumeric(DataType.UWORD, 12346))
|
||||||
assertNotEquals(RuntimeValue(DataType.UWORD, 12345), RuntimeValue(DataType.FLOAT, 12346))
|
assertNotEquals(RuntimeValueNumeric(DataType.UWORD, 12345), RuntimeValueNumeric(DataType.FLOAT, 12346))
|
||||||
assertNotEquals(RuntimeValue(DataType.FLOAT, 9.99), RuntimeValue(DataType.UBYTE, 9))
|
assertNotEquals(RuntimeValueNumeric(DataType.FLOAT, 9.99), RuntimeValueNumeric(DataType.UBYTE, 9))
|
||||||
assertNotEquals(RuntimeValue(DataType.FLOAT, 9.99), RuntimeValue(DataType.UWORD, 9))
|
assertNotEquals(RuntimeValueNumeric(DataType.FLOAT, 9.99), RuntimeValueNumeric(DataType.UWORD, 9))
|
||||||
assertNotEquals(RuntimeValue(DataType.FLOAT, 9.99), RuntimeValue(DataType.FLOAT, 9.0))
|
assertNotEquals(RuntimeValueNumeric(DataType.FLOAT, 9.99), RuntimeValueNumeric(DataType.FLOAT, 9.0))
|
||||||
|
|
||||||
assertFalse(sameValueAndType(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UBYTE, 101)))
|
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UBYTE, 101)))
|
||||||
assertFalse(sameValueAndType(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UWORD, 101)))
|
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UWORD, 101)))
|
||||||
assertFalse(sameValueAndType(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.FLOAT, 101)))
|
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.FLOAT, 101)))
|
||||||
assertFalse(sameValueAndType(RuntimeValue(DataType.UWORD, 245), RuntimeValue(DataType.UBYTE, 246)))
|
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UWORD, 245), RuntimeValueNumeric(DataType.UBYTE, 246)))
|
||||||
assertFalse(sameValueAndType(RuntimeValue(DataType.UWORD, 12345), RuntimeValue(DataType.UWORD, 12346)))
|
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UWORD, 12345), RuntimeValueNumeric(DataType.UWORD, 12346)))
|
||||||
assertFalse(sameValueAndType(RuntimeValue(DataType.UWORD, 12345), RuntimeValue(DataType.FLOAT, 12346)))
|
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UWORD, 12345), RuntimeValueNumeric(DataType.FLOAT, 12346)))
|
||||||
assertFalse(sameValueAndType(RuntimeValue(DataType.FLOAT, 9.99), RuntimeValue(DataType.UBYTE, 9)))
|
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.FLOAT, 9.99), RuntimeValueNumeric(DataType.UBYTE, 9)))
|
||||||
assertFalse(sameValueAndType(RuntimeValue(DataType.FLOAT, 9.99), RuntimeValue(DataType.UWORD, 9)))
|
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.FLOAT, 9.99), RuntimeValueNumeric(DataType.UWORD, 9)))
|
||||||
assertFalse(sameValueAndType(RuntimeValue(DataType.FLOAT, 9.99), RuntimeValue(DataType.FLOAT, 9.0)))
|
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.FLOAT, 9.99), RuntimeValueNumeric(DataType.FLOAT, 9.0)))
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testRequireHeap()
|
|
||||||
{
|
|
||||||
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.STR, num = 999) }
|
|
||||||
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.STR_S, num = 999) }
|
|
||||||
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.ARRAY_F, num = 999) }
|
|
||||||
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.ARRAY_W, num = 999) }
|
|
||||||
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.ARRAY_UW, num = 999) }
|
|
||||||
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.ARRAY_B, num = 999) }
|
|
||||||
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.ARRAY_UB, num = 999) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testEqualityHeapTypes()
|
|
||||||
{
|
|
||||||
assertTrue(sameValueAndType(RuntimeValue(DataType.STR, heapId = 999), RuntimeValue(DataType.STR, heapId = 999)))
|
|
||||||
assertFalse(sameValueAndType(RuntimeValue(DataType.STR, heapId = 999), RuntimeValue(DataType.STR, heapId = 222)))
|
|
||||||
|
|
||||||
assertTrue(sameValueAndType(RuntimeValue(DataType.ARRAY_UB, heapId = 99), RuntimeValue(DataType.ARRAY_UB, heapId = 99)))
|
|
||||||
assertFalse(sameValueAndType(RuntimeValue(DataType.ARRAY_UB, heapId = 99), RuntimeValue(DataType.ARRAY_UB, heapId = 22)))
|
|
||||||
|
|
||||||
assertTrue(sameValueAndType(RuntimeValue(DataType.ARRAY_UW, heapId = 999), RuntimeValue(DataType.ARRAY_UW, heapId = 999)))
|
|
||||||
assertFalse(sameValueAndType(RuntimeValue(DataType.ARRAY_UW, heapId = 999), RuntimeValue(DataType.ARRAY_UW, heapId = 222)))
|
|
||||||
|
|
||||||
assertTrue(sameValueAndType(RuntimeValue(DataType.ARRAY_F, heapId = 999), RuntimeValue(DataType.ARRAY_F, heapId = 999)))
|
|
||||||
assertFalse(sameValueAndType(RuntimeValue(DataType.ARRAY_F, heapId = 999), RuntimeValue(DataType.ARRAY_UW, heapId = 999)))
|
|
||||||
assertFalse(sameValueAndType(RuntimeValue(DataType.ARRAY_F, heapId = 999), RuntimeValue(DataType.ARRAY_F, heapId = 222)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testGreaterThan(){
|
fun testGreaterThan(){
|
||||||
assertTrue(RuntimeValue(DataType.UBYTE, 100) > RuntimeValue(DataType.UBYTE, 99))
|
assertTrue(RuntimeValueNumeric(DataType.UBYTE, 100) > RuntimeValueNumeric(DataType.UBYTE, 99))
|
||||||
assertTrue(RuntimeValue(DataType.UWORD, 254) > RuntimeValue(DataType.UWORD, 253))
|
assertTrue(RuntimeValueNumeric(DataType.UWORD, 254) > RuntimeValueNumeric(DataType.UWORD, 253))
|
||||||
assertTrue(RuntimeValue(DataType.FLOAT, 100.0) > RuntimeValue(DataType.FLOAT, 99.9))
|
assertTrue(RuntimeValueNumeric(DataType.FLOAT, 100.0) > RuntimeValueNumeric(DataType.FLOAT, 99.9))
|
||||||
|
|
||||||
assertTrue(RuntimeValue(DataType.UBYTE, 100) >= RuntimeValue(DataType.UBYTE, 100))
|
assertTrue(RuntimeValueNumeric(DataType.UBYTE, 100) >= RuntimeValueNumeric(DataType.UBYTE, 100))
|
||||||
assertTrue(RuntimeValue(DataType.UWORD, 254) >= RuntimeValue(DataType.UWORD, 254))
|
assertTrue(RuntimeValueNumeric(DataType.UWORD, 254) >= RuntimeValueNumeric(DataType.UWORD, 254))
|
||||||
assertTrue(RuntimeValue(DataType.FLOAT, 100.0) >= RuntimeValue(DataType.FLOAT, 100.0))
|
assertTrue(RuntimeValueNumeric(DataType.FLOAT, 100.0) >= RuntimeValueNumeric(DataType.FLOAT, 100.0))
|
||||||
|
|
||||||
assertFalse(RuntimeValue(DataType.UBYTE, 100) > RuntimeValue(DataType.UBYTE, 100))
|
assertFalse(RuntimeValueNumeric(DataType.UBYTE, 100) > RuntimeValueNumeric(DataType.UBYTE, 100))
|
||||||
assertFalse(RuntimeValue(DataType.UWORD, 254) > RuntimeValue(DataType.UWORD, 254))
|
assertFalse(RuntimeValueNumeric(DataType.UWORD, 254) > RuntimeValueNumeric(DataType.UWORD, 254))
|
||||||
assertFalse(RuntimeValue(DataType.FLOAT, 100.0) > RuntimeValue(DataType.FLOAT, 100.0))
|
assertFalse(RuntimeValueNumeric(DataType.FLOAT, 100.0) > RuntimeValueNumeric(DataType.FLOAT, 100.0))
|
||||||
|
|
||||||
assertFalse(RuntimeValue(DataType.UBYTE, 100) >= RuntimeValue(DataType.UBYTE, 101))
|
assertFalse(RuntimeValueNumeric(DataType.UBYTE, 100) >= RuntimeValueNumeric(DataType.UBYTE, 101))
|
||||||
assertFalse(RuntimeValue(DataType.UWORD, 254) >= RuntimeValue(DataType.UWORD, 255))
|
assertFalse(RuntimeValueNumeric(DataType.UWORD, 254) >= RuntimeValueNumeric(DataType.UWORD, 255))
|
||||||
assertFalse(RuntimeValue(DataType.FLOAT, 100.0) >= RuntimeValue(DataType.FLOAT, 100.1))
|
assertFalse(RuntimeValueNumeric(DataType.FLOAT, 100.0) >= RuntimeValueNumeric(DataType.FLOAT, 100.1))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testLessThan() {
|
fun testLessThan() {
|
||||||
assertTrue(RuntimeValue(DataType.UBYTE, 100) < RuntimeValue(DataType.UBYTE, 101))
|
assertTrue(RuntimeValueNumeric(DataType.UBYTE, 100) < RuntimeValueNumeric(DataType.UBYTE, 101))
|
||||||
assertTrue(RuntimeValue(DataType.UWORD, 254) < RuntimeValue(DataType.UWORD, 255))
|
assertTrue(RuntimeValueNumeric(DataType.UWORD, 254) < RuntimeValueNumeric(DataType.UWORD, 255))
|
||||||
assertTrue(RuntimeValue(DataType.FLOAT, 100.0) < RuntimeValue(DataType.FLOAT, 100.1))
|
assertTrue(RuntimeValueNumeric(DataType.FLOAT, 100.0) < RuntimeValueNumeric(DataType.FLOAT, 100.1))
|
||||||
|
|
||||||
assertTrue(RuntimeValue(DataType.UBYTE, 100) <= RuntimeValue(DataType.UBYTE, 100))
|
assertTrue(RuntimeValueNumeric(DataType.UBYTE, 100) <= RuntimeValueNumeric(DataType.UBYTE, 100))
|
||||||
assertTrue(RuntimeValue(DataType.UWORD, 254) <= RuntimeValue(DataType.UWORD, 254))
|
assertTrue(RuntimeValueNumeric(DataType.UWORD, 254) <= RuntimeValueNumeric(DataType.UWORD, 254))
|
||||||
assertTrue(RuntimeValue(DataType.FLOAT, 100.0) <= RuntimeValue(DataType.FLOAT, 100.0))
|
assertTrue(RuntimeValueNumeric(DataType.FLOAT, 100.0) <= RuntimeValueNumeric(DataType.FLOAT, 100.0))
|
||||||
|
|
||||||
assertFalse(RuntimeValue(DataType.UBYTE, 100) < RuntimeValue(DataType.UBYTE, 100))
|
assertFalse(RuntimeValueNumeric(DataType.UBYTE, 100) < RuntimeValueNumeric(DataType.UBYTE, 100))
|
||||||
assertFalse(RuntimeValue(DataType.UWORD, 254) < RuntimeValue(DataType.UWORD, 254))
|
assertFalse(RuntimeValueNumeric(DataType.UWORD, 254) < RuntimeValueNumeric(DataType.UWORD, 254))
|
||||||
assertFalse(RuntimeValue(DataType.FLOAT, 100.0) < RuntimeValue(DataType.FLOAT, 100.0))
|
assertFalse(RuntimeValueNumeric(DataType.FLOAT, 100.0) < RuntimeValueNumeric(DataType.FLOAT, 100.0))
|
||||||
|
|
||||||
assertFalse(RuntimeValue(DataType.UBYTE, 100) <= RuntimeValue(DataType.UBYTE, 99))
|
assertFalse(RuntimeValueNumeric(DataType.UBYTE, 100) <= RuntimeValueNumeric(DataType.UBYTE, 99))
|
||||||
assertFalse(RuntimeValue(DataType.UWORD, 254) <= RuntimeValue(DataType.UWORD, 253))
|
assertFalse(RuntimeValueNumeric(DataType.UWORD, 254) <= RuntimeValueNumeric(DataType.UWORD, 253))
|
||||||
assertFalse(RuntimeValue(DataType.FLOAT, 100.0) <= RuntimeValue(DataType.FLOAT, 99.9))
|
assertFalse(RuntimeValueNumeric(DataType.FLOAT, 100.0) <= RuntimeValueNumeric(DataType.FLOAT, 99.9))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testNoDtConversion() {
|
fun testNoDtConversion() {
|
||||||
assertFailsWith<ArithmeticException> {
|
assertFailsWith<ArithmeticException> {
|
||||||
RuntimeValue(DataType.UWORD, 100).add(RuntimeValue(DataType.UBYTE, 120))
|
RuntimeValueNumeric(DataType.UWORD, 100).add(RuntimeValueNumeric(DataType.UBYTE, 120))
|
||||||
}
|
}
|
||||||
assertFailsWith<ArithmeticException> {
|
assertFailsWith<ArithmeticException> {
|
||||||
RuntimeValue(DataType.UBYTE, 100).add(RuntimeValue(DataType.UWORD, 120))
|
RuntimeValueNumeric(DataType.UBYTE, 100).add(RuntimeValueNumeric(DataType.UWORD, 120))
|
||||||
}
|
}
|
||||||
assertFailsWith<ArithmeticException> {
|
assertFailsWith<ArithmeticException> {
|
||||||
RuntimeValue(DataType.FLOAT, 100.22).add(RuntimeValue(DataType.UWORD, 120))
|
RuntimeValueNumeric(DataType.FLOAT, 100.22).add(RuntimeValueNumeric(DataType.UWORD, 120))
|
||||||
}
|
}
|
||||||
assertFailsWith<ArithmeticException> {
|
assertFailsWith<ArithmeticException> {
|
||||||
RuntimeValue(DataType.UWORD, 1002).add(RuntimeValue(DataType.FLOAT, 120.22))
|
RuntimeValueNumeric(DataType.UWORD, 1002).add(RuntimeValueNumeric(DataType.FLOAT, 120.22))
|
||||||
}
|
}
|
||||||
assertFailsWith<ArithmeticException> {
|
assertFailsWith<ArithmeticException> {
|
||||||
RuntimeValue(DataType.FLOAT, 100.22).add(RuntimeValue(DataType.UBYTE, 120))
|
RuntimeValueNumeric(DataType.FLOAT, 100.22).add(RuntimeValueNumeric(DataType.UBYTE, 120))
|
||||||
}
|
}
|
||||||
assertFailsWith<ArithmeticException> {
|
assertFailsWith<ArithmeticException> {
|
||||||
RuntimeValue(DataType.UBYTE, 12).add(RuntimeValue(DataType.FLOAT, 120.22))
|
RuntimeValueNumeric(DataType.UBYTE, 12).add(RuntimeValueNumeric(DataType.FLOAT, 120.22))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testNoAutoFloatConversion() {
|
fun testNoAutoFloatConversion() {
|
||||||
assertFailsWith<ArithmeticException> {
|
assertFailsWith<ArithmeticException> {
|
||||||
RuntimeValue(DataType.UBYTE, 233).add(RuntimeValue(DataType.FLOAT, 1.234))
|
RuntimeValueNumeric(DataType.UBYTE, 233).add(RuntimeValueNumeric(DataType.FLOAT, 1.234))
|
||||||
}
|
}
|
||||||
assertFailsWith<ArithmeticException> {
|
assertFailsWith<ArithmeticException> {
|
||||||
RuntimeValue(DataType.UWORD, 233).add(RuntimeValue(DataType.FLOAT, 1.234))
|
RuntimeValueNumeric(DataType.UWORD, 233).add(RuntimeValueNumeric(DataType.FLOAT, 1.234))
|
||||||
}
|
}
|
||||||
assertFailsWith<ArithmeticException> {
|
assertFailsWith<ArithmeticException> {
|
||||||
RuntimeValue(DataType.UBYTE, 233).mul(RuntimeValue(DataType.FLOAT, 1.234))
|
RuntimeValueNumeric(DataType.UBYTE, 233).mul(RuntimeValueNumeric(DataType.FLOAT, 1.234))
|
||||||
}
|
}
|
||||||
assertFailsWith<ArithmeticException> {
|
assertFailsWith<ArithmeticException> {
|
||||||
RuntimeValue(DataType.UWORD, 233).mul(RuntimeValue(DataType.FLOAT, 1.234))
|
RuntimeValueNumeric(DataType.UWORD, 233).mul(RuntimeValueNumeric(DataType.FLOAT, 1.234))
|
||||||
}
|
}
|
||||||
assertFailsWith<ArithmeticException> {
|
assertFailsWith<ArithmeticException> {
|
||||||
RuntimeValue(DataType.UBYTE, 233).div(RuntimeValue(DataType.FLOAT, 1.234))
|
RuntimeValueNumeric(DataType.UBYTE, 233).div(RuntimeValueNumeric(DataType.FLOAT, 1.234))
|
||||||
}
|
}
|
||||||
assertFailsWith<ArithmeticException> {
|
assertFailsWith<ArithmeticException> {
|
||||||
RuntimeValue(DataType.UWORD, 233).div(RuntimeValue(DataType.FLOAT, 1.234))
|
RuntimeValueNumeric(DataType.UWORD, 233).div(RuntimeValueNumeric(DataType.FLOAT, 1.234))
|
||||||
}
|
}
|
||||||
val result = RuntimeValue(DataType.FLOAT, 233.333).add(RuntimeValue(DataType.FLOAT, 1.234))
|
val result = RuntimeValueNumeric(DataType.FLOAT, 233.333).add(RuntimeValueNumeric(DataType.FLOAT, 1.234))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun arithmetictestUbyte() {
|
fun arithmetictestUbyte() {
|
||||||
assertEquals(255, RuntimeValue(DataType.UBYTE, 200).add(RuntimeValue(DataType.UBYTE, 55)).integerValue())
|
assertEquals(255, RuntimeValueNumeric(DataType.UBYTE, 200).add(RuntimeValueNumeric(DataType.UBYTE, 55)).integerValue())
|
||||||
assertEquals(0, RuntimeValue(DataType.UBYTE, 200).add(RuntimeValue(DataType.UBYTE, 56)).integerValue())
|
assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 200).add(RuntimeValueNumeric(DataType.UBYTE, 56)).integerValue())
|
||||||
assertEquals(1, RuntimeValue(DataType.UBYTE, 200).add(RuntimeValue(DataType.UBYTE, 57)).integerValue())
|
assertEquals(1, RuntimeValueNumeric(DataType.UBYTE, 200).add(RuntimeValueNumeric(DataType.UBYTE, 57)).integerValue())
|
||||||
|
|
||||||
assertEquals(1, RuntimeValue(DataType.UBYTE, 2).sub(RuntimeValue(DataType.UBYTE, 1)).integerValue())
|
assertEquals(1, RuntimeValueNumeric(DataType.UBYTE, 2).sub(RuntimeValueNumeric(DataType.UBYTE, 1)).integerValue())
|
||||||
assertEquals(0, RuntimeValue(DataType.UBYTE, 2).sub(RuntimeValue(DataType.UBYTE, 2)).integerValue())
|
assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 2).sub(RuntimeValueNumeric(DataType.UBYTE, 2)).integerValue())
|
||||||
assertEquals(255, RuntimeValue(DataType.UBYTE, 2).sub(RuntimeValue(DataType.UBYTE, 3)).integerValue())
|
assertEquals(255, RuntimeValueNumeric(DataType.UBYTE, 2).sub(RuntimeValueNumeric(DataType.UBYTE, 3)).integerValue())
|
||||||
|
|
||||||
assertEquals(255, RuntimeValue(DataType.UBYTE, 254).inc().integerValue())
|
assertEquals(255, RuntimeValueNumeric(DataType.UBYTE, 254).inc().integerValue())
|
||||||
assertEquals(0, RuntimeValue(DataType.UBYTE, 255).inc().integerValue())
|
assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 255).inc().integerValue())
|
||||||
assertEquals(0, RuntimeValue(DataType.UBYTE, 1).dec().integerValue())
|
assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 1).dec().integerValue())
|
||||||
assertEquals(255, RuntimeValue(DataType.UBYTE, 0).dec().integerValue())
|
assertEquals(255, RuntimeValueNumeric(DataType.UBYTE, 0).dec().integerValue())
|
||||||
|
|
||||||
assertEquals(255, RuntimeValue(DataType.UBYTE, 0).inv().integerValue())
|
assertEquals(255, RuntimeValueNumeric(DataType.UBYTE, 0).inv().integerValue())
|
||||||
assertEquals(0b00110011, RuntimeValue(DataType.UBYTE, 0b11001100).inv().integerValue())
|
assertEquals(0b00110011, RuntimeValueNumeric(DataType.UBYTE, 0b11001100).inv().integerValue())
|
||||||
// assertEquals(0, RuntimeValue(DataType.UBYTE, 0).neg().integerValue())
|
// assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 0).neg().integerValue())
|
||||||
// assertEquals(0, RuntimeValue(DataType.UBYTE, 0).neg().integerValue())
|
// assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 0).neg().integerValue())
|
||||||
assertEquals(1, RuntimeValue(DataType.UBYTE, 0).not().integerValue())
|
assertEquals(1, RuntimeValueNumeric(DataType.UBYTE, 0).not().integerValue())
|
||||||
assertEquals(0, RuntimeValue(DataType.UBYTE, 1).not().integerValue())
|
assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 1).not().integerValue())
|
||||||
assertEquals(0, RuntimeValue(DataType.UBYTE, 111).not().integerValue())
|
assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 111).not().integerValue())
|
||||||
assertEquals(0, RuntimeValue(DataType.UBYTE, 255).not().integerValue())
|
assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 255).not().integerValue())
|
||||||
|
|
||||||
assertEquals(200, RuntimeValue(DataType.UBYTE, 20).mul(RuntimeValue(DataType.UBYTE, 10)).integerValue())
|
assertEquals(200, RuntimeValueNumeric(DataType.UBYTE, 20).mul(RuntimeValueNumeric(DataType.UBYTE, 10)).integerValue())
|
||||||
assertEquals(144, RuntimeValue(DataType.UBYTE, 20).mul(RuntimeValue(DataType.UBYTE, 20)).integerValue())
|
assertEquals(144, RuntimeValueNumeric(DataType.UBYTE, 20).mul(RuntimeValueNumeric(DataType.UBYTE, 20)).integerValue())
|
||||||
|
|
||||||
assertEquals(25, RuntimeValue(DataType.UBYTE, 5).pow(RuntimeValue(DataType.UBYTE, 2)).integerValue())
|
assertEquals(25, RuntimeValueNumeric(DataType.UBYTE, 5).pow(RuntimeValueNumeric(DataType.UBYTE, 2)).integerValue())
|
||||||
assertEquals(125, RuntimeValue(DataType.UBYTE, 5).pow(RuntimeValue(DataType.UBYTE, 3)).integerValue())
|
assertEquals(125, RuntimeValueNumeric(DataType.UBYTE, 5).pow(RuntimeValueNumeric(DataType.UBYTE, 3)).integerValue())
|
||||||
assertEquals(113, RuntimeValue(DataType.UBYTE, 5).pow(RuntimeValue(DataType.UBYTE, 4)).integerValue())
|
assertEquals(113, RuntimeValueNumeric(DataType.UBYTE, 5).pow(RuntimeValueNumeric(DataType.UBYTE, 4)).integerValue())
|
||||||
|
|
||||||
assertEquals(100, RuntimeValue(DataType.UBYTE, 50).shl().integerValue())
|
assertEquals(100, RuntimeValueNumeric(DataType.UBYTE, 50).shl().integerValue())
|
||||||
assertEquals(200, RuntimeValue(DataType.UBYTE, 100).shl().integerValue())
|
assertEquals(200, RuntimeValueNumeric(DataType.UBYTE, 100).shl().integerValue())
|
||||||
assertEquals(144, RuntimeValue(DataType.UBYTE, 200).shl().integerValue())
|
assertEquals(144, RuntimeValueNumeric(DataType.UBYTE, 200).shl().integerValue())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun arithmetictestUWord() {
|
fun arithmetictestUWord() {
|
||||||
assertEquals(65535, RuntimeValue(DataType.UWORD, 60000).add(RuntimeValue(DataType.UWORD, 5535)).integerValue())
|
assertEquals(65535, RuntimeValueNumeric(DataType.UWORD, 60000).add(RuntimeValueNumeric(DataType.UWORD, 5535)).integerValue())
|
||||||
assertEquals(0, RuntimeValue(DataType.UWORD, 60000).add(RuntimeValue(DataType.UWORD, 5536)).integerValue())
|
assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 60000).add(RuntimeValueNumeric(DataType.UWORD, 5536)).integerValue())
|
||||||
assertEquals(1, RuntimeValue(DataType.UWORD, 60000).add(RuntimeValue(DataType.UWORD, 5537)).integerValue())
|
assertEquals(1, RuntimeValueNumeric(DataType.UWORD, 60000).add(RuntimeValueNumeric(DataType.UWORD, 5537)).integerValue())
|
||||||
|
|
||||||
assertEquals(1, RuntimeValue(DataType.UWORD, 2).sub(RuntimeValue(DataType.UWORD, 1)).integerValue())
|
assertEquals(1, RuntimeValueNumeric(DataType.UWORD, 2).sub(RuntimeValueNumeric(DataType.UWORD, 1)).integerValue())
|
||||||
assertEquals(0, RuntimeValue(DataType.UWORD, 2).sub(RuntimeValue(DataType.UWORD, 2)).integerValue())
|
assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 2).sub(RuntimeValueNumeric(DataType.UWORD, 2)).integerValue())
|
||||||
assertEquals(65535, RuntimeValue(DataType.UWORD, 2).sub(RuntimeValue(DataType.UWORD, 3)).integerValue())
|
assertEquals(65535, RuntimeValueNumeric(DataType.UWORD, 2).sub(RuntimeValueNumeric(DataType.UWORD, 3)).integerValue())
|
||||||
|
|
||||||
assertEquals(65535, RuntimeValue(DataType.UWORD, 65534).inc().integerValue())
|
assertEquals(65535, RuntimeValueNumeric(DataType.UWORD, 65534).inc().integerValue())
|
||||||
assertEquals(0, RuntimeValue(DataType.UWORD, 65535).inc().integerValue())
|
assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 65535).inc().integerValue())
|
||||||
assertEquals(0, RuntimeValue(DataType.UWORD, 1).dec().integerValue())
|
assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 1).dec().integerValue())
|
||||||
assertEquals(65535, RuntimeValue(DataType.UWORD, 0).dec().integerValue())
|
assertEquals(65535, RuntimeValueNumeric(DataType.UWORD, 0).dec().integerValue())
|
||||||
|
|
||||||
assertEquals(65535, RuntimeValue(DataType.UWORD, 0).inv().integerValue())
|
assertEquals(65535, RuntimeValueNumeric(DataType.UWORD, 0).inv().integerValue())
|
||||||
assertEquals(0b0011001101010101, RuntimeValue(DataType.UWORD, 0b1100110010101010).inv().integerValue())
|
assertEquals(0b0011001101010101, RuntimeValueNumeric(DataType.UWORD, 0b1100110010101010).inv().integerValue())
|
||||||
// assertEquals(0, RuntimeValue(DataType.UWORD, 0).neg().integerValue())
|
// assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 0).neg().integerValue())
|
||||||
// assertEquals(0, RuntimeValue(DataType.UWORD, 0).neg().integerValue())
|
// assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 0).neg().integerValue())
|
||||||
assertEquals(1, RuntimeValue(DataType.UWORD, 0).not().integerValue())
|
assertEquals(1, RuntimeValueNumeric(DataType.UWORD, 0).not().integerValue())
|
||||||
assertEquals(0, RuntimeValue(DataType.UWORD, 1).not().integerValue())
|
assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 1).not().integerValue())
|
||||||
assertEquals(0, RuntimeValue(DataType.UWORD, 11111).not().integerValue())
|
assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 11111).not().integerValue())
|
||||||
assertEquals(0, RuntimeValue(DataType.UWORD, 65535).not().integerValue())
|
assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 65535).not().integerValue())
|
||||||
|
|
||||||
assertEquals(2000, RuntimeValue(DataType.UWORD, 200).mul(RuntimeValue(DataType.UWORD, 10)).integerValue())
|
assertEquals(2000, RuntimeValueNumeric(DataType.UWORD, 200).mul(RuntimeValueNumeric(DataType.UWORD, 10)).integerValue())
|
||||||
assertEquals(40000, RuntimeValue(DataType.UWORD, 200).mul(RuntimeValue(DataType.UWORD, 200)).integerValue())
|
assertEquals(40000, RuntimeValueNumeric(DataType.UWORD, 200).mul(RuntimeValueNumeric(DataType.UWORD, 200)).integerValue())
|
||||||
assertEquals(14464, RuntimeValue(DataType.UWORD, 200).mul(RuntimeValue(DataType.UWORD, 400)).integerValue())
|
assertEquals(14464, RuntimeValueNumeric(DataType.UWORD, 200).mul(RuntimeValueNumeric(DataType.UWORD, 400)).integerValue())
|
||||||
|
|
||||||
assertEquals(15625, RuntimeValue(DataType.UWORD, 5).pow(RuntimeValue(DataType.UWORD, 6)).integerValue())
|
assertEquals(15625, RuntimeValueNumeric(DataType.UWORD, 5).pow(RuntimeValueNumeric(DataType.UWORD, 6)).integerValue())
|
||||||
assertEquals(12589, RuntimeValue(DataType.UWORD, 5).pow(RuntimeValue(DataType.UWORD, 7)).integerValue())
|
assertEquals(12589, RuntimeValueNumeric(DataType.UWORD, 5).pow(RuntimeValueNumeric(DataType.UWORD, 7)).integerValue())
|
||||||
|
|
||||||
assertEquals(10000, RuntimeValue(DataType.UWORD, 5000).shl().integerValue())
|
assertEquals(10000, RuntimeValueNumeric(DataType.UWORD, 5000).shl().integerValue())
|
||||||
assertEquals(60000, RuntimeValue(DataType.UWORD, 30000).shl().integerValue())
|
assertEquals(60000, RuntimeValueNumeric(DataType.UWORD, 30000).shl().integerValue())
|
||||||
assertEquals(14464, RuntimeValue(DataType.UWORD, 40000).shl().integerValue())
|
assertEquals(14464, RuntimeValueNumeric(DataType.UWORD, 40000).shl().integerValue())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun arithmetictestByte() {
|
fun arithmetictestByte() {
|
||||||
assertEquals(127, RuntimeValue(DataType.BYTE, 100).add(RuntimeValue(DataType.BYTE, 27)).integerValue())
|
assertEquals(127, RuntimeValueNumeric(DataType.BYTE, 100).add(RuntimeValueNumeric(DataType.BYTE, 27)).integerValue())
|
||||||
assertEquals(-128, RuntimeValue(DataType.BYTE, 100).add(RuntimeValue(DataType.BYTE, 28)).integerValue())
|
assertEquals(-128, RuntimeValueNumeric(DataType.BYTE, 100).add(RuntimeValueNumeric(DataType.BYTE, 28)).integerValue())
|
||||||
assertEquals(-127, RuntimeValue(DataType.BYTE, 100).add(RuntimeValue(DataType.BYTE, 29)).integerValue())
|
assertEquals(-127, RuntimeValueNumeric(DataType.BYTE, 100).add(RuntimeValueNumeric(DataType.BYTE, 29)).integerValue())
|
||||||
|
|
||||||
assertEquals(1, RuntimeValue(DataType.BYTE, 2).sub(RuntimeValue(DataType.BYTE, 1)).integerValue())
|
assertEquals(1, RuntimeValueNumeric(DataType.BYTE, 2).sub(RuntimeValueNumeric(DataType.BYTE, 1)).integerValue())
|
||||||
assertEquals(0, RuntimeValue(DataType.BYTE, 2).sub(RuntimeValue(DataType.BYTE, 2)).integerValue())
|
assertEquals(0, RuntimeValueNumeric(DataType.BYTE, 2).sub(RuntimeValueNumeric(DataType.BYTE, 2)).integerValue())
|
||||||
assertEquals(-1, RuntimeValue(DataType.BYTE, 2).sub(RuntimeValue(DataType.BYTE, 3)).integerValue())
|
assertEquals(-1, RuntimeValueNumeric(DataType.BYTE, 2).sub(RuntimeValueNumeric(DataType.BYTE, 3)).integerValue())
|
||||||
assertEquals(-128, RuntimeValue(DataType.BYTE, -100).sub(RuntimeValue(DataType.BYTE, 28)).integerValue())
|
assertEquals(-128, RuntimeValueNumeric(DataType.BYTE, -100).sub(RuntimeValueNumeric(DataType.BYTE, 28)).integerValue())
|
||||||
assertEquals(127, RuntimeValue(DataType.BYTE, -100).sub(RuntimeValue(DataType.BYTE, 29)).integerValue())
|
assertEquals(127, RuntimeValueNumeric(DataType.BYTE, -100).sub(RuntimeValueNumeric(DataType.BYTE, 29)).integerValue())
|
||||||
|
|
||||||
assertEquals(127, RuntimeValue(DataType.BYTE, 126).inc().integerValue())
|
assertEquals(127, RuntimeValueNumeric(DataType.BYTE, 126).inc().integerValue())
|
||||||
assertEquals(-128, RuntimeValue(DataType.BYTE, 127).inc().integerValue())
|
assertEquals(-128, RuntimeValueNumeric(DataType.BYTE, 127).inc().integerValue())
|
||||||
assertEquals(0, RuntimeValue(DataType.BYTE, 1).dec().integerValue())
|
assertEquals(0, RuntimeValueNumeric(DataType.BYTE, 1).dec().integerValue())
|
||||||
assertEquals(-1, RuntimeValue(DataType.BYTE, 0).dec().integerValue())
|
assertEquals(-1, RuntimeValueNumeric(DataType.BYTE, 0).dec().integerValue())
|
||||||
assertEquals(-128, RuntimeValue(DataType.BYTE, -127).dec().integerValue())
|
assertEquals(-128, RuntimeValueNumeric(DataType.BYTE, -127).dec().integerValue())
|
||||||
assertEquals(127, RuntimeValue(DataType.BYTE, -128).dec().integerValue())
|
assertEquals(127, RuntimeValueNumeric(DataType.BYTE, -128).dec().integerValue())
|
||||||
|
|
||||||
assertEquals(-1, RuntimeValue(DataType.BYTE, 0).inv().integerValue())
|
assertEquals(-1, RuntimeValueNumeric(DataType.BYTE, 0).inv().integerValue())
|
||||||
assertEquals(-103, RuntimeValue(DataType.BYTE, 0b01100110).inv().integerValue())
|
assertEquals(-103, RuntimeValueNumeric(DataType.BYTE, 0b01100110).inv().integerValue())
|
||||||
assertEquals(0, RuntimeValue(DataType.BYTE, 0).neg().integerValue())
|
assertEquals(0, RuntimeValueNumeric(DataType.BYTE, 0).neg().integerValue())
|
||||||
assertEquals(-2, RuntimeValue(DataType.BYTE, 2).neg().integerValue())
|
assertEquals(-2, RuntimeValueNumeric(DataType.BYTE, 2).neg().integerValue())
|
||||||
assertEquals(1, RuntimeValue(DataType.BYTE, 0).not().integerValue())
|
assertEquals(1, RuntimeValueNumeric(DataType.BYTE, 0).not().integerValue())
|
||||||
assertEquals(0, RuntimeValue(DataType.BYTE, 1).not().integerValue())
|
assertEquals(0, RuntimeValueNumeric(DataType.BYTE, 1).not().integerValue())
|
||||||
assertEquals(0, RuntimeValue(DataType.BYTE, 111).not().integerValue())
|
assertEquals(0, RuntimeValueNumeric(DataType.BYTE, 111).not().integerValue())
|
||||||
assertEquals(0, RuntimeValue(DataType.BYTE, -33).not().integerValue())
|
assertEquals(0, RuntimeValueNumeric(DataType.BYTE, -33).not().integerValue())
|
||||||
|
|
||||||
assertEquals(100, RuntimeValue(DataType.BYTE, 10).mul(RuntimeValue(DataType.BYTE, 10)).integerValue())
|
assertEquals(100, RuntimeValueNumeric(DataType.BYTE, 10).mul(RuntimeValueNumeric(DataType.BYTE, 10)).integerValue())
|
||||||
assertEquals(-56, RuntimeValue(DataType.BYTE, 20).mul(RuntimeValue(DataType.BYTE, 10)).integerValue())
|
assertEquals(-56, RuntimeValueNumeric(DataType.BYTE, 20).mul(RuntimeValueNumeric(DataType.BYTE, 10)).integerValue())
|
||||||
|
|
||||||
assertEquals(25, RuntimeValue(DataType.BYTE, 5).pow(RuntimeValue(DataType.BYTE, 2)).integerValue())
|
assertEquals(25, RuntimeValueNumeric(DataType.BYTE, 5).pow(RuntimeValueNumeric(DataType.BYTE, 2)).integerValue())
|
||||||
assertEquals(125, RuntimeValue(DataType.BYTE, 5).pow(RuntimeValue(DataType.BYTE, 3)).integerValue())
|
assertEquals(125, RuntimeValueNumeric(DataType.BYTE, 5).pow(RuntimeValueNumeric(DataType.BYTE, 3)).integerValue())
|
||||||
assertEquals(113, RuntimeValue(DataType.BYTE, 5).pow(RuntimeValue(DataType.BYTE, 4)).integerValue())
|
assertEquals(113, RuntimeValueNumeric(DataType.BYTE, 5).pow(RuntimeValueNumeric(DataType.BYTE, 4)).integerValue())
|
||||||
|
|
||||||
assertEquals(100, RuntimeValue(DataType.BYTE, 50).shl().integerValue())
|
assertEquals(100, RuntimeValueNumeric(DataType.BYTE, 50).shl().integerValue())
|
||||||
assertEquals(-56, RuntimeValue(DataType.BYTE, 100).shl().integerValue())
|
assertEquals(-56, RuntimeValueNumeric(DataType.BYTE, 100).shl().integerValue())
|
||||||
assertEquals(-2, RuntimeValue(DataType.BYTE, -1).shl().integerValue())
|
assertEquals(-2, RuntimeValueNumeric(DataType.BYTE, -1).shl().integerValue())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun arithmetictestWorrd() {
|
fun arithmetictestWorrd() {
|
||||||
assertEquals(32767, RuntimeValue(DataType.WORD, 32700).add(RuntimeValue(DataType.WORD, 67)).integerValue())
|
assertEquals(32767, RuntimeValueNumeric(DataType.WORD, 32700).add(RuntimeValueNumeric(DataType.WORD, 67)).integerValue())
|
||||||
assertEquals(-32768, RuntimeValue(DataType.WORD, 32700).add(RuntimeValue(DataType.WORD, 68)).integerValue())
|
assertEquals(-32768, RuntimeValueNumeric(DataType.WORD, 32700).add(RuntimeValueNumeric(DataType.WORD, 68)).integerValue())
|
||||||
assertEquals(-32767, RuntimeValue(DataType.WORD, 32700).add(RuntimeValue(DataType.WORD, 69)).integerValue())
|
assertEquals(-32767, RuntimeValueNumeric(DataType.WORD, 32700).add(RuntimeValueNumeric(DataType.WORD, 69)).integerValue())
|
||||||
|
|
||||||
assertEquals(1, RuntimeValue(DataType.WORD, 2).sub(RuntimeValue(DataType.WORD, 1)).integerValue())
|
assertEquals(1, RuntimeValueNumeric(DataType.WORD, 2).sub(RuntimeValueNumeric(DataType.WORD, 1)).integerValue())
|
||||||
assertEquals(0, RuntimeValue(DataType.WORD, 2).sub(RuntimeValue(DataType.WORD, 2)).integerValue())
|
assertEquals(0, RuntimeValueNumeric(DataType.WORD, 2).sub(RuntimeValueNumeric(DataType.WORD, 2)).integerValue())
|
||||||
assertEquals(-1, RuntimeValue(DataType.WORD, 2).sub(RuntimeValue(DataType.WORD, 3)).integerValue())
|
assertEquals(-1, RuntimeValueNumeric(DataType.WORD, 2).sub(RuntimeValueNumeric(DataType.WORD, 3)).integerValue())
|
||||||
assertEquals(-32768, RuntimeValue(DataType.WORD, -32700).sub(RuntimeValue(DataType.WORD, 68)).integerValue())
|
assertEquals(-32768, RuntimeValueNumeric(DataType.WORD, -32700).sub(RuntimeValueNumeric(DataType.WORD, 68)).integerValue())
|
||||||
assertEquals(32767, RuntimeValue(DataType.WORD, -32700).sub(RuntimeValue(DataType.WORD, 69)).integerValue())
|
assertEquals(32767, RuntimeValueNumeric(DataType.WORD, -32700).sub(RuntimeValueNumeric(DataType.WORD, 69)).integerValue())
|
||||||
|
|
||||||
assertEquals(32767, RuntimeValue(DataType.WORD, 32766).inc().integerValue())
|
assertEquals(32767, RuntimeValueNumeric(DataType.WORD, 32766).inc().integerValue())
|
||||||
assertEquals(-32768, RuntimeValue(DataType.WORD, 32767).inc().integerValue())
|
assertEquals(-32768, RuntimeValueNumeric(DataType.WORD, 32767).inc().integerValue())
|
||||||
assertEquals(0, RuntimeValue(DataType.WORD, 1).dec().integerValue())
|
assertEquals(0, RuntimeValueNumeric(DataType.WORD, 1).dec().integerValue())
|
||||||
assertEquals(-1, RuntimeValue(DataType.WORD, 0).dec().integerValue())
|
assertEquals(-1, RuntimeValueNumeric(DataType.WORD, 0).dec().integerValue())
|
||||||
assertEquals(-32768, RuntimeValue(DataType.WORD, -32767).dec().integerValue())
|
assertEquals(-32768, RuntimeValueNumeric(DataType.WORD, -32767).dec().integerValue())
|
||||||
assertEquals(32767, RuntimeValue(DataType.WORD, -32768).dec().integerValue())
|
assertEquals(32767, RuntimeValueNumeric(DataType.WORD, -32768).dec().integerValue())
|
||||||
|
|
||||||
assertEquals(-1, RuntimeValue(DataType.WORD, 0).inv().integerValue())
|
assertEquals(-1, RuntimeValueNumeric(DataType.WORD, 0).inv().integerValue())
|
||||||
assertEquals(-103, RuntimeValue(DataType.WORD, 0b01100110).inv().integerValue())
|
assertEquals(-103, RuntimeValueNumeric(DataType.WORD, 0b01100110).inv().integerValue())
|
||||||
assertEquals(0, RuntimeValue(DataType.WORD, 0).neg().integerValue())
|
assertEquals(0, RuntimeValueNumeric(DataType.WORD, 0).neg().integerValue())
|
||||||
assertEquals(-2, RuntimeValue(DataType.WORD, 2).neg().integerValue())
|
assertEquals(-2, RuntimeValueNumeric(DataType.WORD, 2).neg().integerValue())
|
||||||
assertEquals(1, RuntimeValue(DataType.WORD, 0).not().integerValue())
|
assertEquals(1, RuntimeValueNumeric(DataType.WORD, 0).not().integerValue())
|
||||||
assertEquals(0, RuntimeValue(DataType.WORD, 1).not().integerValue())
|
assertEquals(0, RuntimeValueNumeric(DataType.WORD, 1).not().integerValue())
|
||||||
assertEquals(0, RuntimeValue(DataType.WORD, 111).not().integerValue())
|
assertEquals(0, RuntimeValueNumeric(DataType.WORD, 111).not().integerValue())
|
||||||
assertEquals(0, RuntimeValue(DataType.WORD, -33).not().integerValue())
|
assertEquals(0, RuntimeValueNumeric(DataType.WORD, -33).not().integerValue())
|
||||||
|
|
||||||
assertEquals(10000, RuntimeValue(DataType.WORD, 100).mul(RuntimeValue(DataType.WORD, 100)).integerValue())
|
assertEquals(10000, RuntimeValueNumeric(DataType.WORD, 100).mul(RuntimeValueNumeric(DataType.WORD, 100)).integerValue())
|
||||||
assertEquals(-25536, RuntimeValue(DataType.WORD, 200).mul(RuntimeValue(DataType.WORD, 200)).integerValue())
|
assertEquals(-25536, RuntimeValueNumeric(DataType.WORD, 200).mul(RuntimeValueNumeric(DataType.WORD, 200)).integerValue())
|
||||||
|
|
||||||
assertEquals(15625, RuntimeValue(DataType.WORD, 5).pow(RuntimeValue(DataType.WORD, 6)).integerValue())
|
assertEquals(15625, RuntimeValueNumeric(DataType.WORD, 5).pow(RuntimeValueNumeric(DataType.WORD, 6)).integerValue())
|
||||||
assertEquals(-6487, RuntimeValue(DataType.WORD, 9).pow(RuntimeValue(DataType.WORD, 5)).integerValue())
|
assertEquals(-6487, RuntimeValueNumeric(DataType.WORD, 9).pow(RuntimeValueNumeric(DataType.WORD, 5)).integerValue())
|
||||||
|
|
||||||
assertEquals(18000, RuntimeValue(DataType.WORD, 9000).shl().integerValue())
|
assertEquals(18000, RuntimeValueNumeric(DataType.WORD, 9000).shl().integerValue())
|
||||||
assertEquals(-25536, RuntimeValue(DataType.WORD, 20000).shl().integerValue())
|
assertEquals(-25536, RuntimeValueNumeric(DataType.WORD, 20000).shl().integerValue())
|
||||||
assertEquals(-2, RuntimeValue(DataType.WORD, -1).shl().integerValue())
|
assertEquals(-2, RuntimeValueNumeric(DataType.WORD, -1).shl().integerValue())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -7,14 +7,18 @@ import org.junit.jupiter.api.Test
|
|||||||
import org.junit.jupiter.api.TestInstance
|
import org.junit.jupiter.api.TestInstance
|
||||||
import prog8.ast.base.DataType
|
import prog8.ast.base.DataType
|
||||||
import prog8.ast.base.Position
|
import prog8.ast.base.Position
|
||||||
import prog8.ast.expressions.LiteralValue
|
import prog8.ast.expressions.NumericLiteralValue
|
||||||
import prog8.vm.RuntimeValue
|
import prog8.ast.expressions.StringLiteralValue
|
||||||
import prog8.compiler.*
|
import prog8.compiler.*
|
||||||
import prog8.compiler.target.c64.*
|
import prog8.compiler.target.c64.MachineDefinition.C64Zeropage
|
||||||
|
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_NEGATIVE
|
||||||
|
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_POSITIVE
|
||||||
|
import prog8.compiler.target.c64.MachineDefinition.Mflpt5
|
||||||
|
import prog8.compiler.target.c64.Petscii
|
||||||
|
import prog8.vm.RuntimeValueNumeric
|
||||||
import java.io.CharConversionException
|
import java.io.CharConversionException
|
||||||
import kotlin.test.*
|
import kotlin.test.*
|
||||||
|
|
||||||
|
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
class TestCompiler {
|
class TestCompiler {
|
||||||
@Test
|
@Test
|
||||||
@ -49,7 +53,6 @@ class TestCompiler {
|
|||||||
assertFailsWith<CompilerException> { 65536L.toHex() }
|
assertFailsWith<CompilerException> { 65536L.toHex() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testFloatToMflpt5() {
|
fun testFloatToMflpt5() {
|
||||||
assertThat(Mflpt5.fromNumber(0), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)))
|
assertThat(Mflpt5.fromNumber(0), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)))
|
||||||
@ -140,8 +143,12 @@ class TestZeropage {
|
|||||||
assertFailsWith<CompilerException> {
|
assertFailsWith<CompilerException> {
|
||||||
zp.allocate("", DataType.FLOAT, null)
|
zp.allocate("", DataType.FLOAT, null)
|
||||||
}
|
}
|
||||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true))
|
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true))
|
||||||
zp2.allocate("", DataType.FLOAT, null)
|
assertFailsWith<CompilerException> {
|
||||||
|
zp2.allocate("", DataType.FLOAT, null)
|
||||||
|
}
|
||||||
|
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true))
|
||||||
|
zp3.allocate("", DataType.FLOAT, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -160,14 +167,24 @@ class TestZeropage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testZpDontuse() {
|
||||||
|
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false))
|
||||||
|
println(zp.free)
|
||||||
|
assertEquals(0, zp.available())
|
||||||
|
assertFailsWith<CompilerException> {
|
||||||
|
zp.allocate("", DataType.BYTE, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testFreeSpaces() {
|
fun testFreeSpaces() {
|
||||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
|
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
|
||||||
assertEquals(20, zp1.available())
|
assertEquals(16, zp1.available())
|
||||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false))
|
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false))
|
||||||
assertEquals(95, zp2.available())
|
assertEquals(91, zp2.available())
|
||||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false))
|
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false))
|
||||||
assertEquals(129, zp3.available())
|
assertEquals(125, zp3.available())
|
||||||
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
|
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
|
||||||
assertEquals(238, zp4.available())
|
assertEquals(238, zp4.available())
|
||||||
}
|
}
|
||||||
@ -197,11 +214,10 @@ class TestZeropage {
|
|||||||
@Test
|
@Test
|
||||||
fun testBasicsafeAllocation() {
|
fun testBasicsafeAllocation() {
|
||||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
|
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
|
||||||
assertEquals(20, zp.available())
|
assertEquals(16, zp.available())
|
||||||
|
|
||||||
zp.allocate("", DataType.FLOAT, null)
|
|
||||||
assertFailsWith<ZeropageDepletedError> {
|
assertFailsWith<ZeropageDepletedError> {
|
||||||
// in regular zp there aren't 5 sequential bytes free after we take the first sequence
|
// in regular zp there aren't 5 sequential bytes free
|
||||||
zp.allocate("", DataType.FLOAT, null)
|
zp.allocate("", DataType.FLOAT, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,16 +267,16 @@ class TestZeropage {
|
|||||||
@Test
|
@Test
|
||||||
fun testEfficientAllocation() {
|
fun testEfficientAllocation() {
|
||||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
|
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
|
||||||
assertEquals(20, zp.available())
|
assertEquals(16, zp.available())
|
||||||
assertEquals(0x04, zp.allocate("", DataType.FLOAT, null))
|
assertEquals(0x04, zp.allocate("", DataType.WORD, null))
|
||||||
assertEquals(0x09, zp.allocate("", DataType.UBYTE, null))
|
assertEquals(0x06, zp.allocate("", DataType.UBYTE, null))
|
||||||
assertEquals(0x0d, zp.allocate("", DataType.UWORD, null))
|
assertEquals(0x0a, zp.allocate("", DataType.UBYTE, null))
|
||||||
assertEquals(0x94, zp.allocate("", DataType.UWORD, null))
|
assertEquals(0x94, zp.allocate("", DataType.UWORD, null))
|
||||||
assertEquals(0xa7, zp.allocate("", DataType.UWORD, null))
|
assertEquals(0xa7, zp.allocate("", DataType.UWORD, null))
|
||||||
assertEquals(0xa9, zp.allocate("", DataType.UWORD, null))
|
assertEquals(0xa9, zp.allocate("", DataType.UWORD, null))
|
||||||
assertEquals(0xb5, zp.allocate("", DataType.UWORD, null))
|
assertEquals(0xb5, zp.allocate("", DataType.UWORD, null))
|
||||||
assertEquals(0xf7, zp.allocate("", DataType.UWORD, null))
|
assertEquals(0xf7, zp.allocate("", DataType.UWORD, null))
|
||||||
assertEquals(0x0a, zp.allocate("", DataType.UBYTE, null))
|
assertEquals(0x0e, zp.allocate("", DataType.UBYTE, null))
|
||||||
assertEquals(0xf9, zp.allocate("", DataType.UBYTE, null))
|
assertEquals(0xf9, zp.allocate("", DataType.UBYTE, null))
|
||||||
assertEquals(0, zp.available())
|
assertEquals(0, zp.available())
|
||||||
}
|
}
|
||||||
@ -336,8 +352,8 @@ class TestPetscii {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testLiteralValueComparisons() {
|
fun testLiteralValueComparisons() {
|
||||||
val ten = LiteralValue(DataType.UWORD, wordvalue = 10, position = Position("", 0, 0, 0))
|
val ten = NumericLiteralValue(DataType.UWORD, 10, Position("", 0, 0, 0))
|
||||||
val nine = LiteralValue(DataType.UBYTE, bytevalue = 9, position = Position("", 0, 0, 0))
|
val nine = NumericLiteralValue(DataType.UBYTE, 9, Position("", 0, 0, 0))
|
||||||
assertEquals(ten, ten)
|
assertEquals(ten, ten)
|
||||||
assertNotEquals(ten, nine)
|
assertNotEquals(ten, nine)
|
||||||
assertFalse(ten != ten)
|
assertFalse(ten != ten)
|
||||||
@ -353,23 +369,17 @@ class TestPetscii {
|
|||||||
assertTrue(ten <= ten)
|
assertTrue(ten <= ten)
|
||||||
assertFalse(ten < ten)
|
assertFalse(ten < ten)
|
||||||
|
|
||||||
val abc = LiteralValue(DataType.STR, strvalue = "abc", position = Position("", 0, 0, 0))
|
val abc = StringLiteralValue(DataType.STR, "abc", Position("", 0, 0, 0))
|
||||||
val abd = LiteralValue(DataType.STR, strvalue = "abd", position = Position("", 0, 0, 0))
|
val abd = StringLiteralValue(DataType.STR, "abd", Position("", 0, 0, 0))
|
||||||
assertEquals(abc, abc)
|
assertEquals(abc, abc)
|
||||||
assertTrue(abc!=abd)
|
assertTrue(abc!=abd)
|
||||||
assertFalse(abc!=abc)
|
assertFalse(abc!=abc)
|
||||||
assertTrue(abc < abd)
|
|
||||||
assertTrue(abc <= abd)
|
|
||||||
assertFalse(abd <= abc)
|
|
||||||
assertTrue(abd >= abc)
|
|
||||||
assertTrue(abd > abc)
|
|
||||||
assertFalse(abc > abd)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testStackvmValueComparisons() {
|
fun testStackvmValueComparisons() {
|
||||||
val ten = RuntimeValue(DataType.FLOAT, 10)
|
val ten = RuntimeValueNumeric(DataType.FLOAT, 10)
|
||||||
val nine = RuntimeValue(DataType.UWORD, 9)
|
val nine = RuntimeValueNumeric(DataType.UWORD, 9)
|
||||||
assertEquals(ten, ten)
|
assertEquals(ten, ten)
|
||||||
assertNotEquals(ten, nine)
|
assertNotEquals(ten, nine)
|
||||||
assertFalse(ten != ten)
|
assertFalse(ten != ten)
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
Writing and building a program
|
Writing and building a program
|
||||||
==============================
|
==============================
|
||||||
|
|
||||||
|
.. _building_compiler:
|
||||||
|
|
||||||
First, getting a working compiler
|
First, getting a working compiler
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
||||||
@ -11,20 +13,38 @@ Then you can choose a few ways to get a compiler:
|
|||||||
|
|
||||||
**Download a precompiled version from github:**
|
**Download a precompiled version from github:**
|
||||||
|
|
||||||
#. download a recent "prog8compiler.jar" from `the releases on Github <https://github.com/irmen/prog8/releases>`_
|
#. download a recent "fat-jar" (called something like "prog8compiler-all.jar") from `the releases on Github <https://github.com/irmen/prog8/releases>`_
|
||||||
#. run the compiler with "java -jar prog8compiler.jar" to see how you can use it.
|
#. run the compiler with "java -jar prog8compiler-all.jar" to see how you can use it.
|
||||||
|
|
||||||
**Using the shell scripts:**
|
**using the Gradle build system to make it yourself:**
|
||||||
|
|
||||||
#. run the "create_compiler_jar.sh" shell script and have a little patience while everything is built
|
The Gradle build system is used to build the compiler.
|
||||||
#. it will output "prog8compiler.jar" file which contains everything.
|
The most interesting gradle commands to run are probably:
|
||||||
#. run the compiler with "java -jar prog8compiler.jar" to see how you can use it.
|
|
||||||
|
|
||||||
**using the Gradle build system directly:**
|
``./gradlew check``
|
||||||
|
Builds the compiler code and runs all available checks and unit-tests.
|
||||||
|
``./gradlew installDist``
|
||||||
|
Builds the compiler and installs it with scripts to run it, in the directory
|
||||||
|
``./compiler/build/install/p8compile``
|
||||||
|
``./gradlew installShadowDist``
|
||||||
|
Creates a 'fat-jar' that contains the compiler and all dependencies, in a single
|
||||||
|
executable .jar file, and includes few start scripts to run it.
|
||||||
|
The output can be found in ``.compiler/build/install/compiler-shadow/``
|
||||||
|
``./gradlew shadowDistZip``
|
||||||
|
Creates a zipfile with the above in it, for easy distribution.
|
||||||
|
This file can be found in ``./compiler/build/distributions/``
|
||||||
|
|
||||||
#. run the command "./gradlew installDist" and have a little patience while everything is built
|
For normal use, the ``installDist`` target should suffice and ater succesful completion
|
||||||
#. it will create the commands and required libraries in the "./compiler/build/install/p8compile/" directory
|
of that build task, you can start the compiler with:
|
||||||
#. run the compiler with the "./compiler/build/install/p8compile/bin/p8compile" command to see how you can use it.
|
|
||||||
|
``./compiler/build/install/p8compile/bin/p8compile <options> <sourcefile>``
|
||||||
|
|
||||||
|
(You should probably make an alias...)
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Development and testing is done on Linux, but the compiler should run on most
|
||||||
|
operating systems. If you do have trouble building or running
|
||||||
|
the compiler on another operating system, please let me know!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -54,14 +74,10 @@ The compiler will link everything together into one output program at the end.
|
|||||||
If you start the compiler without arguments, it will print a short usage text.
|
If you start the compiler without arguments, it will print a short usage text.
|
||||||
For normal use the compiler is invoked with the command:
|
For normal use the compiler is invoked with the command:
|
||||||
|
|
||||||
``$ java -jar prog8compiler.jar sourcefile.p8`` if you're using the packaged release version, or
|
``$ java -jar prog8compiler.jar sourcefile.p8``
|
||||||
|
|
||||||
``$ ./p8compile.sh sourcefile.p8`` if you're running an unpackaged development version.
|
Other options are also available, see the introduction page about how
|
||||||
|
to build and run the compiler.
|
||||||
If you tell it to save the intermediate code for the prog8 virtual machine, you can
|
|
||||||
actually run this code with the command:
|
|
||||||
|
|
||||||
``$ ./p8vm.sh sourcefile.vm.txt``
|
|
||||||
|
|
||||||
|
|
||||||
By default, assembly code is generated and written to ``sourcefile.asm``.
|
By default, assembly code is generated and written to ``sourcefile.asm``.
|
||||||
@ -71,6 +87,20 @@ If you use the option to let the compiler auto-start a C-64 emulator, it will do
|
|||||||
a successful compilation. This will load your program and the symbol and breakpoint lists
|
a successful compilation. This will load your program and the symbol and breakpoint lists
|
||||||
(for the machine code monitor) into the emulator.
|
(for the machine code monitor) into the emulator.
|
||||||
|
|
||||||
|
Continuous compilation mode
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
Almost instant compilation times (less than a second) can be achieved when using the continuous compilation mode.
|
||||||
|
Start the compiler with the ``-watch`` argument to enable this.
|
||||||
|
It will compile your program and then instead of exiting, it waits for any changes in the module source files.
|
||||||
|
As soon as a change happens, the program gets compiled again.
|
||||||
|
|
||||||
|
Other options
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
There's an option to specify the output directory if you're not happy with the default (the current working directory).
|
||||||
|
Also it is possible to specify more than one main module to compile:
|
||||||
|
this can be useful to quickly recompile multiple separate programs quickly.
|
||||||
|
(compiling in a batch like this is a lot faster than invoking the compiler again once per main file)
|
||||||
|
|
||||||
|
|
||||||
Module source code files
|
Module source code files
|
||||||
------------------------
|
------------------------
|
||||||
@ -85,6 +115,13 @@ They are embedded into the packaged release version of the compiler so you don't
|
|||||||
where they are, but their names are still reserved.
|
where they are, but their names are still reserved.
|
||||||
|
|
||||||
|
|
||||||
|
User defined library files
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
You can create library files yourself too that can be shared among programs.
|
||||||
|
You can tell the compiler where it should look for these files, by setting the java command line property ``prog8.libdir``
|
||||||
|
or by setting the ``PROG8_LIBDIR`` environment variable to the correct directory.
|
||||||
|
|
||||||
|
|
||||||
.. _debugging:
|
.. _debugging:
|
||||||
|
|
||||||
Debugging (with Vice)
|
Debugging (with Vice)
|
||||||
@ -131,3 +168,18 @@ or::
|
|||||||
|
|
||||||
$ ./p8compile.sh -emu examples/rasterbars.p8
|
$ ./p8compile.sh -emu examples/rasterbars.p8
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Virtual Machine / Simulator
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
You may have noticed the ``-sim`` command line option for the compiler:
|
||||||
|
|
||||||
|
-sim
|
||||||
|
Launches the "AST virtual machine Simulator" that directly executes the parsed program.
|
||||||
|
No compilation steps will be performed.
|
||||||
|
Allows for very fast testing and debugging before actually compiling programs
|
||||||
|
to machine code.
|
||||||
|
It simulates a bare minimum of features from the target platform, so most stuff
|
||||||
|
that calls ROM routines or writes into hardware registers won't work. But basic
|
||||||
|
system routines are emulated.
|
||||||
|
@ -37,15 +37,15 @@ This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/license
|
|||||||
:alt: Fully playable tetris clone
|
:alt: Fully playable tetris clone
|
||||||
|
|
||||||
|
|
||||||
Code example
|
Code examples
|
||||||
------------
|
-------------
|
||||||
|
|
||||||
This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
||||||
|
|
||||||
%import c64utils
|
%import c64utils
|
||||||
%zeropage basicsafe
|
%zeropage basicsafe
|
||||||
|
|
||||||
~ main {
|
main {
|
||||||
|
|
||||||
ubyte[256] sieve
|
ubyte[256] sieve
|
||||||
ubyte candidate_prime = 2
|
ubyte candidate_prime = 2
|
||||||
@ -89,13 +89,47 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
when compiled an ran on a C-64 you'll get:
|
when compiled an ran on a C-64 you get this:
|
||||||
|
|
||||||
.. image:: _static/primes_example.png
|
.. image:: _static/primes_example.png
|
||||||
:align: center
|
:align: center
|
||||||
:alt: result when run on C-64
|
:alt: result when run on C-64
|
||||||
|
|
||||||
|
|
||||||
|
The following programs shows a use of the high level ``struct`` type::
|
||||||
|
|
||||||
|
%import c64utils
|
||||||
|
%zeropage basicsafe
|
||||||
|
|
||||||
|
main {
|
||||||
|
|
||||||
|
struct Color {
|
||||||
|
ubyte red
|
||||||
|
ubyte green
|
||||||
|
ubyte blue
|
||||||
|
}
|
||||||
|
|
||||||
|
sub start() {
|
||||||
|
|
||||||
|
Color purple = {255, 0, 255}
|
||||||
|
Color other
|
||||||
|
other = purple
|
||||||
|
other.red /= 2
|
||||||
|
other.green = 10 + other.green / 2
|
||||||
|
other.blue = 99
|
||||||
|
|
||||||
|
c64scr.print_ub(other.red)
|
||||||
|
c64.CHROUT(',')
|
||||||
|
c64scr.print_ub(other.green)
|
||||||
|
c64.CHROUT(',')
|
||||||
|
c64scr.print_ub(other.blue)
|
||||||
|
c64.CHROUT('\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
when compiled and ran, it prints ``127,10,99`` on the screen.
|
||||||
|
|
||||||
|
|
||||||
Design principles and features
|
Design principles and features
|
||||||
------------------------------
|
------------------------------
|
||||||
|
|
||||||
@ -106,7 +140,8 @@ Design principles and features
|
|||||||
- 'One statement per line' code style, resulting in clear readable programs.
|
- 'One statement per line' code style, resulting in clear readable programs.
|
||||||
- Modular programming and scoping via modules, code blocks, and subroutines.
|
- Modular programming and scoping via modules, code blocks, and subroutines.
|
||||||
- Provide high level programming constructs but stay close to the metal;
|
- Provide high level programming constructs but stay close to the metal;
|
||||||
still able to directly use memory addresses, CPU registers and ROM subroutines
|
still able to directly use memory addresses, CPU registers and ROM subroutines,
|
||||||
|
and inline assembly to have full control when every cycle or byte matters
|
||||||
- Arbitrary number of subroutine parameters (constrained only by available memory)
|
- Arbitrary number of subroutine parameters (constrained only by available memory)
|
||||||
- Complex nested expressions are possible
|
- Complex nested expressions are possible
|
||||||
- Values are typed. Types supported include signed and unsigned bytes and words, arrays, strings and floats.
|
- Values are typed. Types supported include signed and unsigned bytes and words, arrays, strings and floats.
|
||||||
@ -116,13 +151,14 @@ Design principles and features
|
|||||||
the compiled program in an emulator and provide debugging information to the emulator.
|
the compiled program in an emulator and provide debugging information to the emulator.
|
||||||
- The compiler outputs a regular 6502 assembly source code file, but doesn't assemble this itself.
|
- The compiler outputs a regular 6502 assembly source code file, but doesn't assemble this itself.
|
||||||
The (separate) '64tass' cross-assembler tool is used for that.
|
The (separate) '64tass' cross-assembler tool is used for that.
|
||||||
- Goto is usually considered harmful, but not here: arbitrary control flow jumps and branches are possible,
|
- Arbitrary control flow jumps and branches are possible,
|
||||||
and will usually translate directly into the appropriate single 6502 jump/branch instruction.
|
and will usually translate directly into the appropriate single 6502 jump/branch instruction.
|
||||||
- There are no complicated built-in error handling or overflow checks, you'll have to take care
|
- There are no complicated built-in error handling or overflow checks, you'll have to take care
|
||||||
of this yourself if required. This keeps the language and code simple and efficient.
|
of this yourself if required. This keeps the language and code simple and efficient.
|
||||||
- The compiler tries to optimize the program and generated code, but hand-tuning of the
|
- The compiler tries to optimize the program and generated code, but hand-tuning of the
|
||||||
performance or space-critical parts will likely still be required. This is supported by
|
performance or space-critical parts will likely still be required. This is supported by
|
||||||
the ability to easily write embedded assembly code directly in the program source code.
|
the ability to easily write embedded assembly code directly in the program source code.
|
||||||
|
- There are many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``, ``sort`` and ``reverse``
|
||||||
|
|
||||||
|
|
||||||
.. _requirements:
|
.. _requirements:
|
||||||
@ -150,37 +186,7 @@ of the `Vice emulator <http://vice-emu.sourceforge.net/>`_.
|
|||||||
IDE from Jetbrains, with the Kotlin plugin (free community edition of this IDE is available).
|
IDE from Jetbrains, with the Kotlin plugin (free community edition of this IDE is available).
|
||||||
But a bare Kotlin SDK installation should work just as well.
|
But a bare Kotlin SDK installation should work just as well.
|
||||||
|
|
||||||
|
Instructions on how to obtain a working compiler are in :ref:`building_compiler`.
|
||||||
If you have the 'fat-jar' you can run it with ``java -jar prog8compiler.jar`` or just use
|
|
||||||
one of the scripts that are created by Gradle
|
|
||||||
|
|
||||||
The Gradle build system is used to build the compiler.
|
|
||||||
The most interesting gradle commands to run are probably:
|
|
||||||
|
|
||||||
``./gradlew check``
|
|
||||||
Builds the compiler code and runs all available checks and unit-tests.
|
|
||||||
``./gradlew installDist``
|
|
||||||
Builds the compiler and installs it with scripts to run it, in the directory
|
|
||||||
``./compiler/build/install/p8compile``
|
|
||||||
``./gradlew installShadowDist``
|
|
||||||
Creates a 'fat-jar' that contains the compiler and all dependencies, in a single
|
|
||||||
executable .jar file, and includes few start scripts to run it.
|
|
||||||
The output can be found in ``.compiler/build/install/compiler-shadow/``
|
|
||||||
``./gradlew shadowDistZip``
|
|
||||||
Creates a zipfile with the above in it, for easy distribution.
|
|
||||||
This file can be found in ``./compiler/build/distributions/``
|
|
||||||
|
|
||||||
For normal use, the ``installDist`` target should suffice and ater succesful completion
|
|
||||||
of that build task, you can start the compiler with:
|
|
||||||
|
|
||||||
``./compiler/build/install/p8compile/bin/p8compile <options> <sourcefile>``
|
|
||||||
|
|
||||||
(You should probably make an alias...)
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
Development and testing is done on Linux, but the compiler should run on most
|
|
||||||
operating systems. If you do have trouble building or running
|
|
||||||
the compiler on another operating system, please let me know!
|
|
||||||
|
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
|
@ -93,7 +93,7 @@ Blocks, Scopes, and accessing Symbols
|
|||||||
**Blocks** are the top level separate pieces of code and data of your program. They are combined
|
**Blocks** are the top level separate pieces of code and data of your program. They are combined
|
||||||
into a single output program. No code or data can occur outside a block. Here's an example::
|
into a single output program. No code or data can occur outside a block. Here's an example::
|
||||||
|
|
||||||
~ main $c000 {
|
main $c000 {
|
||||||
; this is code inside the block...
|
; this is code inside the block...
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,12 +127,20 @@ The address must be >= ``$0200`` (because ``$00``--``$ff`` is the ZP and ``$100`
|
|||||||
to them by their 'short' name directly. If the symbol is not found in the same scope,
|
to them by their 'short' name directly. If the symbol is not found in the same scope,
|
||||||
the enclosing scope is searched for it, and so on, until the symbol is found.
|
the enclosing scope is searched for it, and so on, until the symbol is found.
|
||||||
|
|
||||||
Scopes are created using several statements:
|
Scopes are created using either of these two statements:
|
||||||
|
|
||||||
- blocks (top-level named scope)
|
- blocks (top-level named scope)
|
||||||
- subroutines (nested named scopes)
|
- subroutines (nested named scope)
|
||||||
- for, while, repeat loops (anonymous scope)
|
|
||||||
- if statements and branching conditionals (anonymous scope)
|
.. note::
|
||||||
|
In contrast to many other programming languages, a new scope is *not* created inside
|
||||||
|
for, while and repeat statements, nor for the if statement and branching conditionals.
|
||||||
|
This is a bit restrictive because you have to think harder about what variables you
|
||||||
|
want to use inside a subroutine. But it is done precisely for this reason; memory in the
|
||||||
|
target system is very limited and it would be a waste to allocate a lot of variables.
|
||||||
|
|
||||||
|
Right now the prog8 compiler is not advanced enough to be able to 'share' or 'overlap'
|
||||||
|
variables intelligently by itself. So for now, it's something the programmer has to think about.
|
||||||
|
|
||||||
|
|
||||||
Program Start and Entry Point
|
Program Start and Entry Point
|
||||||
@ -151,7 +159,7 @@ taking no parameters and having no return value.
|
|||||||
|
|
||||||
As any subroutine, it has to end with a ``return`` statement (or a ``goto`` call)::
|
As any subroutine, it has to end with a ``return`` statement (or a ``goto`` call)::
|
||||||
|
|
||||||
~ main {
|
main {
|
||||||
sub start () {
|
sub start () {
|
||||||
; program entrypoint code here
|
; program entrypoint code here
|
||||||
return
|
return
|
||||||
@ -237,6 +245,7 @@ Arrays
|
|||||||
^^^^^^
|
^^^^^^
|
||||||
Array types are also supported. They can be made of bytes, words or floats::
|
Array types are also supported. They can be made of bytes, words or floats::
|
||||||
|
|
||||||
|
byte[10] array ; array of 10 bytes, initially set to 0
|
||||||
byte[] array = [1, 2, 3, 4] ; initialize the array, size taken from value
|
byte[] array = [1, 2, 3, 4] ; initialize the array, size taken from value
|
||||||
byte[99] array = 255 ; initialize array with 99 times 255 [255, 255, 255, 255, ...]
|
byte[99] array = 255 ; initialize array with 99 times 255 [255, 255, 255, 255, ...]
|
||||||
byte[] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
|
byte[] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
|
||||||
@ -266,6 +275,13 @@ Strings in your source code files will be encoded (translated from ASCII/UTF-8)
|
|||||||
PETSCII is the default choice. If you need screencodes (also called 'poke' codes) instead,
|
PETSCII is the default choice. If you need screencodes (also called 'poke' codes) instead,
|
||||||
you have to use the ``str_s`` variants of the string type identifier.
|
you have to use the ``str_s`` variants of the string type identifier.
|
||||||
|
|
||||||
|
You can concatenate two string literals using '+' (not very useful though) or repeat
|
||||||
|
a string literal a given number of times using '*'::
|
||||||
|
|
||||||
|
str string1 = "first part" + "second part"
|
||||||
|
str string2 = "hello!" * 10
|
||||||
|
|
||||||
|
|
||||||
.. caution::
|
.. caution::
|
||||||
It's probably best that you don't change strings after they're created.
|
It's probably best that you don't change strings after they're created.
|
||||||
This is because if your program exits and is restarted (without loading it again),
|
This is because if your program exits and is restarted (without loading it again),
|
||||||
@ -273,6 +289,39 @@ you have to use the ``str_s`` variants of the string type identifier.
|
|||||||
The same is true for arrays by the way.
|
The same is true for arrays by the way.
|
||||||
|
|
||||||
|
|
||||||
|
Structs
|
||||||
|
^^^^^^^
|
||||||
|
|
||||||
|
A struct is a group of one or more other variables.
|
||||||
|
This allows you to reuse the definition and manipulate it as a whole.
|
||||||
|
Individual variables in the struct are accessed as you would expect, just
|
||||||
|
use a scoped name to refer to them: ``structvariable.membername``.
|
||||||
|
|
||||||
|
Structs are a bit limited in Prog8: you can only use numerical variables
|
||||||
|
as member of a struct, so strings and arrays and other structs can not be part of a struct.
|
||||||
|
Also, it is not possible to use a struct itself inside an array.
|
||||||
|
Structs are mainly syntactic sugar for repeated groups of vardecls
|
||||||
|
and assignments that belong together. However,
|
||||||
|
*they are layed out in sequence in memory as the members are defined*
|
||||||
|
which may be usefulif you want to pass pointers around.
|
||||||
|
|
||||||
|
To create a variable of a struct type you need to define the struct itself,
|
||||||
|
and then create a variable with it::
|
||||||
|
|
||||||
|
struct Color {
|
||||||
|
ubyte red
|
||||||
|
ubyte green
|
||||||
|
ubyte blue
|
||||||
|
}
|
||||||
|
|
||||||
|
Color rgb = {255,122,0} ; note the curly braces here instead of brackets
|
||||||
|
Color another ; the init value is optional, like arrays
|
||||||
|
|
||||||
|
another = rgb ; assign all of the values of rgb to another
|
||||||
|
another.blue = 255 ; set a single member
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Special types: const and memory-mapped
|
Special types: const and memory-mapped
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
@ -321,10 +370,17 @@ Initial values across multiple runs of the program
|
|||||||
When declaring values with an initial value, this value will be set into the variable each time
|
When declaring values with an initial value, this value will be set into the variable each time
|
||||||
the program reaches the declaration again. This can be in loops, multiple subroutine calls,
|
the program reaches the declaration again. This can be in loops, multiple subroutine calls,
|
||||||
or even multiple invocations of the entire program. If you omit an initial value, it will
|
or even multiple invocations of the entire program. If you omit an initial value, it will
|
||||||
be set to zero only for the first run of the program. A second run will utilize the last value
|
be set to zero *but only for the first run of the program*. A second run will utilize the last value
|
||||||
where it left off (but your code will be a bit smaller because no initialization instructions
|
where it left off (but your code will be a bit smaller because no initialization instructions
|
||||||
are generated)
|
are generated)
|
||||||
|
|
||||||
|
This only works for simple types, *and not for string variables and arrays*.
|
||||||
|
It is assumed these are left unchanged by the program; they are not re-initialized on
|
||||||
|
a second run.
|
||||||
|
If you do modify them in-place, you should take care yourself that they work as
|
||||||
|
expected when the program is restarted.
|
||||||
|
(This is an optimization choice to avoid having to store two copies of every string and array)
|
||||||
|
|
||||||
.. caution::
|
.. caution::
|
||||||
variables that get allocated in zero-page will *not* have a zero starting value when you omit
|
variables that get allocated in zero-page will *not* have a zero starting value when you omit
|
||||||
the variable's initialization. They'll be whatever the last value in that zero page
|
the variable's initialization. They'll be whatever the last value in that zero page
|
||||||
@ -334,19 +390,12 @@ are generated)
|
|||||||
this behavior may change in a future version so that subsequent runs always
|
this behavior may change in a future version so that subsequent runs always
|
||||||
use the same initial values
|
use the same initial values
|
||||||
|
|
||||||
This only works for simple types, *and not for string variables and arrays*.
|
|
||||||
It is assumed these are left unchanged by the program.
|
|
||||||
If you do modify them in-place, you should take care yourself that they work as
|
|
||||||
expected when the program is restarted.
|
|
||||||
(This is an optimization choice to avoid having to store two copies of every string and array)
|
|
||||||
|
|
||||||
|
|
||||||
Loops
|
Loops
|
||||||
-----
|
-----
|
||||||
|
|
||||||
The *for*-loop is used to let a variable (or register) iterate over a range of values. Iteration is done in steps of 1, but you can change this.
|
The *for*-loop is used to let a variable (or register) iterate over a range of values. Iteration is done in steps of 1, but you can change this.
|
||||||
The loop variable can be declared as byte or word earlier so you can reuse it for multiple occasions,
|
The loop variable must be declared as byte or word earlier so you can reuse it for multiple occasions.
|
||||||
or you can declare one directly in the for statement which will only be visible in the for loop body.
|
|
||||||
Iterating with a floating point variable is not supported. If you want to loop over a floating-point array, use a loop with an integer index variable instead.
|
Iterating with a floating point variable is not supported. If you want to loop over a floating-point array, use a loop with an integer index variable instead.
|
||||||
|
|
||||||
The *while*-loop is used to repeat a piece of code while a certain condition is still true.
|
The *while*-loop is used to repeat a piece of code while a certain condition is still true.
|
||||||
@ -358,7 +407,6 @@ You can also create loops by using the ``goto`` statement, but this should usual
|
|||||||
The value of the loop variable or register after executing the loop *is undefined*. Don't use it immediately
|
The value of the loop variable or register after executing the loop *is undefined*. Don't use it immediately
|
||||||
after the loop without first assigning a new value to it!
|
after the loop without first assigning a new value to it!
|
||||||
(this is an optimization issue to avoid having to deal with mostly useless post-loop logic to adjust the loop variable's value)
|
(this is an optimization issue to avoid having to deal with mostly useless post-loop logic to adjust the loop variable's value)
|
||||||
Loop variables that are declared inline are scoped in the loop body so they're not accessible at all after the loop finishes.
|
|
||||||
|
|
||||||
|
|
||||||
Conditional Execution
|
Conditional Execution
|
||||||
@ -611,55 +659,58 @@ cos16u(x)
|
|||||||
Fast 16-bit uword cosine of angle 0..255, result is in range 0..65535
|
Fast 16-bit uword cosine of angle 0..255, result is in range 0..65535
|
||||||
|
|
||||||
cos16(x)
|
cos16(x)
|
||||||
Fast 16-bit word cosine of angle 0..255, result is in range -32767..32767
|
Fast 16-bit word cosine of angle 0..255, result is in range -32767..32767
|
||||||
|
|
||||||
abs(x)
|
abs(x)
|
||||||
Absolute value.
|
Absolute value.
|
||||||
|
|
||||||
tan(x)
|
tan(x)
|
||||||
Tangent.
|
Tangent.
|
||||||
|
|
||||||
atan(x)
|
atan(x)
|
||||||
Arctangent.
|
Arctangent.
|
||||||
|
|
||||||
ln(x)
|
ln(x)
|
||||||
Natural logarithm (base e).
|
Natural logarithm (base e).
|
||||||
|
|
||||||
log2(x)
|
log2(x)
|
||||||
Base 2 logarithm.
|
Base 2 logarithm.
|
||||||
|
|
||||||
sqrt16(w)
|
sqrt16(w)
|
||||||
16 bit unsigned integer Square root. Result is unsigned byte.
|
16 bit unsigned integer Square root. Result is unsigned byte.
|
||||||
|
|
||||||
sqrt(x)
|
sqrt(x)
|
||||||
Floating point Square root.
|
Floating point Square root.
|
||||||
|
|
||||||
round(x)
|
round(x)
|
||||||
Rounds the floating point to the closest integer.
|
Rounds the floating point to the closest integer.
|
||||||
|
|
||||||
floor (x)
|
floor (x)
|
||||||
Rounds the floating point down to an integer towards minus infinity.
|
Rounds the floating point down to an integer towards minus infinity.
|
||||||
|
|
||||||
ceil(x)
|
ceil(x)
|
||||||
Rounds the floating point up to an integer towards positive infinity.
|
Rounds the floating point up to an integer towards positive infinity.
|
||||||
|
|
||||||
rad(x)
|
rad(x)
|
||||||
Degrees to radians.
|
Degrees to radians.
|
||||||
|
|
||||||
deg(x)
|
deg(x)
|
||||||
Radians to degrees.
|
Radians to degrees.
|
||||||
|
|
||||||
max(x)
|
max(x)
|
||||||
Maximum of the values in the array value x
|
Maximum of the values in the array value x
|
||||||
|
|
||||||
min(x)
|
min(x)
|
||||||
Minimum of the values in the array value x
|
Minimum of the values in the array value x
|
||||||
|
|
||||||
avg(x)
|
|
||||||
Average of the values in the array value x
|
|
||||||
|
|
||||||
sum(x)
|
sum(x)
|
||||||
Sum of the values in the array value x
|
Sum of the values in the array value x
|
||||||
|
|
||||||
|
sort(array)
|
||||||
|
Sort the array in ascending order (in-place)
|
||||||
|
|
||||||
|
reverse(array)
|
||||||
|
Reverse the values in the array (in-place). Can be used after sort() to sort an array in descending order.
|
||||||
|
|
||||||
len(x)
|
len(x)
|
||||||
Number of values in the array value x, or the number of characters in a string (excluding the size or 0-byte).
|
Number of values in the array value x, or the number of characters in a string (excluding the size or 0-byte).
|
||||||
@ -678,6 +729,9 @@ lsb(x)
|
|||||||
msb(x)
|
msb(x)
|
||||||
Get the most significant byte of the word x.
|
Get the most significant byte of the word x.
|
||||||
|
|
||||||
|
sgn(x)
|
||||||
|
Get the sign of the value. Result is -1, 0 or 1 (negative, zero, positive).
|
||||||
|
|
||||||
mkword(lsb, msb)
|
mkword(lsb, msb)
|
||||||
Efficiently create a word value from two bytes (the lsb and the msb). Avoids multiplication and shifting.
|
Efficiently create a word value from two bytes (the lsb and the msb). Avoids multiplication and shifting.
|
||||||
|
|
||||||
@ -714,7 +768,7 @@ rol(x)
|
|||||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||||
|
|
||||||
rol2(x)
|
rol2(x)
|
||||||
Like _rol but now as 8-bit or 16-bit rotation.
|
Like ``rol`` but now as 8-bit or 16-bit rotation.
|
||||||
It uses some extra logic to not consider the carry flag as extra rotation bit.
|
It uses some extra logic to not consider the carry flag as extra rotation bit.
|
||||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||||
|
|
||||||
@ -726,7 +780,7 @@ ror(x)
|
|||||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||||
|
|
||||||
ror2(x)
|
ror2(x)
|
||||||
Like _ror but now as 8-bit or 16-bit rotation.
|
Like ``ror`` but now as 8-bit or 16-bit rotation.
|
||||||
It uses some extra logic to not consider the carry flag as extra rotation bit.
|
It uses some extra logic to not consider the carry flag as extra rotation bit.
|
||||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||||
|
|
||||||
|
@ -74,6 +74,7 @@ Directives
|
|||||||
As with ``kernalsafe``, it is not possible to cleanly exit the program, other than to reset the machine.
|
As with ``kernalsafe``, it is not possible to cleanly exit the program, other than to reset the machine.
|
||||||
This option makes programs smaller and faster because even more variables can
|
This option makes programs smaller and faster because even more variables can
|
||||||
be stored in the ZP (which allows for more efficient assembly code).
|
be stored in the ZP (which allows for more efficient assembly code).
|
||||||
|
- style ``dontuse`` -- don't use *any* location in the zeropage.
|
||||||
|
|
||||||
Also read :ref:`zeropage`.
|
Also read :ref:`zeropage`.
|
||||||
|
|
||||||
@ -173,7 +174,7 @@ Code blocks
|
|||||||
A named block of actual program code. Itefines a *scope* (also known as 'namespace') and
|
A named block of actual program code. Itefines a *scope* (also known as 'namespace') and
|
||||||
can contain Prog8 *code*, *directives*, *variable declarations* and *subroutines*::
|
can contain Prog8 *code*, *directives*, *variable declarations* and *subroutines*::
|
||||||
|
|
||||||
~ <blockname> [<address>] {
|
<blockname> [<address>] {
|
||||||
<directives>
|
<directives>
|
||||||
<variables>
|
<variables>
|
||||||
<statements>
|
<statements>
|
||||||
@ -185,7 +186,7 @@ The <address> is optional. If specified it must be a valid memory address such a
|
|||||||
It's used to tell the compiler to put the block at a certain position in memory.
|
It's used to tell the compiler to put the block at a certain position in memory.
|
||||||
Also read :ref:`blocks`. Here is an example of a code block, to be loaded at ``$c000``::
|
Also read :ref:`blocks`. Here is an example of a code block, to be loaded at ``$c000``::
|
||||||
|
|
||||||
~ main $c000 {
|
main $c000 {
|
||||||
; this is code inside the block...
|
; this is code inside the block...
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,7 +216,7 @@ Variable declarations
|
|||||||
^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Variables should be declared with their exact type and size so the compiler can allocate storage
|
Variables should be declared with their exact type and size so the compiler can allocate storage
|
||||||
for them. You must give them an initial value as well. That value can be a simple literal value,
|
for them. You can give them an initial value as well. That value can be a simple literal value,
|
||||||
or an expression. You can add a ``@zp`` zeropage-tag, to tell the compiler to prioritize it
|
or an expression. You can add a ``@zp`` zeropage-tag, to tell the compiler to prioritize it
|
||||||
when selecting variables to be put into zeropage.
|
when selecting variables to be put into zeropage.
|
||||||
The syntax is::
|
The syntax is::
|
||||||
@ -231,10 +232,11 @@ Various examples::
|
|||||||
str name = "my name is Irmen"
|
str name = "my name is Irmen"
|
||||||
uword address = &counter
|
uword address = &counter
|
||||||
byte[] values = [11, 22, 33, 44, 55]
|
byte[] values = [11, 22, 33, 44, 55]
|
||||||
|
byte[5] values ; array of 5 bytes, initially set to zero
|
||||||
byte[5] values = 255 ; initialize with five 255 bytes
|
byte[5] values = 255 ; initialize with five 255 bytes
|
||||||
|
|
||||||
word @zp zpword = 9999 ; prioritize this when selecting vars for zeropage storage
|
word @zp zpword = 9999 ; prioritize this when selecting vars for zeropage storage
|
||||||
|
Color rgb = {1,255,0} ; a struct variable with initial values
|
||||||
|
|
||||||
|
|
||||||
Data types
|
Data types
|
||||||
@ -358,6 +360,26 @@ Syntax is familiar with brackets: ``arrayvar[x]`` ::
|
|||||||
string[4] ; the fifth character (=byte) in the string
|
string[4] ; the fifth character (=byte) in the string
|
||||||
|
|
||||||
|
|
||||||
|
Struct
|
||||||
|
^^^^^^
|
||||||
|
|
||||||
|
A *struct* has to be defined to specify what its member variables are.
|
||||||
|
There are one or more members::
|
||||||
|
|
||||||
|
struct <structname> {
|
||||||
|
<vardecl>
|
||||||
|
[ <vardecl> ...]
|
||||||
|
}
|
||||||
|
|
||||||
|
You can only use numerical variables as member of a struct, so strings and arrays
|
||||||
|
and other structs can not be part of a struct. Vice versa, a struct can not occur in an array.
|
||||||
|
|
||||||
|
After defining a struct you can use the name of the struct as a data type to declare variables with.
|
||||||
|
|
||||||
|
Struct variables can be assigned a struct literal value (also in their declaration as initial value)::
|
||||||
|
|
||||||
|
Color rgb = {255, 100, 0} ; curly braces instead of brackets
|
||||||
|
|
||||||
|
|
||||||
Operators
|
Operators
|
||||||
---------
|
---------
|
||||||
@ -366,7 +388,7 @@ arithmetic: ``+`` ``-`` ``*`` ``/`` ``**`` ``%``
|
|||||||
``+``, ``-``, ``*``, ``/`` are the familiar arithmetic operations.
|
``+``, ``-``, ``*``, ``/`` are the familiar arithmetic operations.
|
||||||
``/`` is division (will result in integer division when using on integer operands, and a floating point division when at least one of the operands is a float)
|
``/`` is division (will result in integer division when using on integer operands, and a floating point division when at least one of the operands is a float)
|
||||||
``**`` is the power operator: ``3 ** 5`` is equal to 3*3*3*3*3 and is 243. (it only works on floating point variables)
|
``**`` is the power operator: ``3 ** 5`` is equal to 3*3*3*3*3 and is 243. (it only works on floating point variables)
|
||||||
``%`` is the remainder operator: ``25 % 7`` is 4. Be careful: without a space, %10 will be parsed as the binary number 2
|
``%`` is the remainder operator: ``25 % 7`` is 4. Be careful: without a space, %10 will be parsed as the binary number 2.
|
||||||
Remainder is only supported on integer operands (not floats).
|
Remainder is only supported on integer operands (not floats).
|
||||||
|
|
||||||
bitwise arithmetic: ``&`` ``|`` ``^`` ``~`` ``<<`` ``>>``
|
bitwise arithmetic: ``&`` ``|`` ``^`` ``~`` ``<<`` ``>>``
|
||||||
@ -495,42 +517,39 @@ Loops
|
|||||||
for loop
|
for loop
|
||||||
^^^^^^^^
|
^^^^^^^^
|
||||||
|
|
||||||
The loop variable must be a register or a byte/word variable. It must be defined in the local scope (to reuse
|
The loop variable must be a register or a byte/word variable,
|
||||||
an existing variable), or you can declare it in the for loop directly to make a new one that is only visible
|
and must be defined first in the local scope of the for loop.
|
||||||
in the body of the for loop.
|
|
||||||
The expression that you loop over can be anything that supports iteration (such as ranges like ``0 to 100``,
|
The expression that you loop over can be anything that supports iteration (such as ranges like ``0 to 100``,
|
||||||
array variables and strings) *except* floating-point arrays (because a floating-point loop variable is not supported).
|
array variables and strings) *except* floating-point arrays (because a floating-point loop variable is not supported).
|
||||||
|
|
||||||
You can use a single statement, or a statement block like in the example below::
|
You can use a single statement, or a statement block like in the example below::
|
||||||
|
|
||||||
for [byte | word] <loopvar> in <expression> [ step <amount> ] {
|
for <loopvar> in <expression> [ step <amount> ] {
|
||||||
; do something...
|
; do something...
|
||||||
break ; break out of the loop
|
break ; break out of the loop
|
||||||
continue ; immediately enter next iteration
|
continue ; immediately enter next iteration
|
||||||
}
|
}
|
||||||
|
|
||||||
For example, this is a for loop using the existing byte variable ``i`` to loop over a certain range of numbers::
|
For example, this is a for loop using a byte variable ``i``, defined before, to loop over a certain range of numbers::
|
||||||
|
|
||||||
|
ubyte i
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
for i in 20 to 155 {
|
for i in 20 to 155 {
|
||||||
; do something
|
; do something
|
||||||
}
|
}
|
||||||
|
|
||||||
And this is a loop over the values of the array ``fibonacci_numbers`` where the loop variable is declared in the loop itself::
|
And this is a loop over the values of the array ``fibonacci_numbers``::
|
||||||
|
|
||||||
word[] fibonacci_numbers = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181]
|
uword[] fibonacci_numbers = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181]
|
||||||
|
|
||||||
for word fibnr in fibonacci_numbers {
|
uword number
|
||||||
; do something
|
for number in fibonacci_numbers {
|
||||||
|
; do something with number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
You can inline the loop variable declaration in the for statement, including optional zp-tag. In this case
|
|
||||||
the variable is not visible outside of the for loop::
|
|
||||||
|
|
||||||
for ubyte @zp fastindex in 10 to 20 {
|
|
||||||
; do something
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
while loop
|
while loop
|
||||||
^^^^^^^^^^
|
^^^^^^^^^^
|
||||||
|
@ -168,7 +168,7 @@ These routines are::
|
|||||||
If you activate an IRQ handler with one of these, it expects the handler to be defined
|
If you activate an IRQ handler with one of these, it expects the handler to be defined
|
||||||
as a subroutine ``irq`` in the module ``irq`` so like this::
|
as a subroutine ``irq`` in the module ``irq`` so like this::
|
||||||
|
|
||||||
~ irq {
|
irq {
|
||||||
sub irq() {
|
sub irq() {
|
||||||
; ... irq handling here ...
|
; ... irq handling here ...
|
||||||
}
|
}
|
||||||
|
@ -5,20 +5,17 @@ TODO
|
|||||||
Memory Block Operations integrated in language?
|
Memory Block Operations integrated in language?
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
list,string memory block operations?
|
array/string memory block operations?
|
||||||
|
|
||||||
- list operations (whole list, individual element)
|
- array operations
|
||||||
operations: set, get, copy (from another list with the same length), shift-N(left,right), rotate-N(left,right)
|
copy (from another array with the same length), shift-N(left,right), rotate-N(left,right)
|
||||||
clear (set whole list to the given value, default 0)
|
clear (set whole array to the given value, default 0)
|
||||||
|
|
||||||
- list operations ofcourse work identical on vars and on memory mapped vars of these types.
|
- array operations ofcourse work identical on vars and on memory mapped vars of these types.
|
||||||
|
|
||||||
- strings: identical operations as on lists.
|
- strings: identical operations as on array.
|
||||||
|
|
||||||
these should call optimized pieces of assembly code, so they run as fast as possible
|
|
||||||
|
|
||||||
For now, we have the ``memcopy``, ``memset`` and ``strlen`` builtin functions.
|
|
||||||
|
|
||||||
|
For now, we have the ``memcopy`` and ``memset`` builtin functions.
|
||||||
|
|
||||||
|
|
||||||
More optimizations
|
More optimizations
|
||||||
@ -27,14 +24,9 @@ More optimizations
|
|||||||
Add more compiler optimizations to the existing ones.
|
Add more compiler optimizations to the existing ones.
|
||||||
|
|
||||||
- on the language AST level
|
- on the language AST level
|
||||||
- on the StackVM intermediate code level
|
|
||||||
- on the final assembly source level
|
- on the final assembly source level
|
||||||
- can the parameter passing to subroutines be optimized to avoid copying?
|
- can the parameter passing to subroutines be optimized to avoid copying?
|
||||||
|
- working subroutine inlining (taking care of vars and identifier refs to them)
|
||||||
- subroutines with 1 or 2 byte args (or 1 word arg) should be converted to asm calling convention with the args in A/Y register
|
|
||||||
this requires rethinking the way parameters are represented, simply injecting vardecls to
|
|
||||||
declare local variables for them is not always correct anymore
|
|
||||||
|
|
||||||
|
|
||||||
Also some library routines and code patterns could perhaps be optimized further
|
Also some library routines and code patterns could perhaps be optimized further
|
||||||
|
|
||||||
@ -48,30 +40,18 @@ It could then even be moved into the zeropage to greatly reduce code size and sl
|
|||||||
|
|
||||||
Or just move the LSB portion into a slab of the zeropage.
|
Or just move the LSB portion into a slab of the zeropage.
|
||||||
|
|
||||||
Allocate a fixed word in ZP that is the TOS so we can operate on TOS directly
|
Allocate a fixed word in ZP that is the TOS so we can always operate on TOS directly
|
||||||
without having to to index into the stack?
|
without having to to index into the stack?
|
||||||
|
|
||||||
|
|
||||||
structs?
|
Bugs
|
||||||
^^^^^^^^
|
^^^^
|
||||||
|
Ofcourse there are still bugs to fix ;)
|
||||||
A user defined struct type would be nice to group a bunch
|
|
||||||
of values together (and use it multiple times). Something like::
|
|
||||||
|
|
||||||
struct Point {
|
|
||||||
ubyte color
|
|
||||||
word[] vec = [0,0,0]
|
|
||||||
}
|
|
||||||
|
|
||||||
Point p1
|
|
||||||
Point p2
|
|
||||||
Point p3
|
|
||||||
|
|
||||||
p1.color = 3
|
|
||||||
p1.vec[2] = 2
|
|
||||||
|
|
||||||
|
|
||||||
Misc
|
Misc
|
||||||
^^^^
|
^^^^
|
||||||
|
|
||||||
- are there any other missing instructions in the code generator?
|
Several ideas were discussed on my reddit post
|
||||||
|
https://www.reddit.com/r/programming/comments/alhj59/creating_a_programming_language_and_cross/
|
||||||
|
|
||||||
|
110
examples/arithmetic/aggregates.p8
Normal file
110
examples/arithmetic/aggregates.p8
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
%import c64lib
|
||||||
|
%import c64utils
|
||||||
|
%import c64flt
|
||||||
|
%zeropage dontuse
|
||||||
|
|
||||||
|
main {
|
||||||
|
|
||||||
|
sub start() {
|
||||||
|
ubyte[] ubarr = [100, 0, 99, 199, 22]
|
||||||
|
byte[] barr = [-100, 0, 99, -122, 22]
|
||||||
|
uword[] uwarr = [1000, 0, 222, 4444, 999]
|
||||||
|
word[] warr = [-1000, 0, 999, -4444, 222]
|
||||||
|
float[] farr = [-1000.1, 0, 999.9, -4444.4, 222.2]
|
||||||
|
str name = "irmen"
|
||||||
|
ubyte ub
|
||||||
|
byte bb
|
||||||
|
word ww
|
||||||
|
uword uw
|
||||||
|
float ff
|
||||||
|
|
||||||
|
; LEN/STRLEN
|
||||||
|
ubyte length = len(name)
|
||||||
|
if length!=5 c64scr.print("error len1\n")
|
||||||
|
length = len(uwarr)
|
||||||
|
if length!=5 c64scr.print("error len2\n")
|
||||||
|
length=strlen(name)
|
||||||
|
if length!=5 c64scr.print("error strlen1\n")
|
||||||
|
name[3] = 0
|
||||||
|
length=strlen(name)
|
||||||
|
if length!=3 c64scr.print("error strlen2\n")
|
||||||
|
|
||||||
|
; MAX
|
||||||
|
ub = max(ubarr)
|
||||||
|
if ub!=199 c64scr.print("error max1\n")
|
||||||
|
bb = max(barr)
|
||||||
|
if bb!=99 c64scr.print("error max2\n")
|
||||||
|
uw = max(uwarr)
|
||||||
|
if uw!=4444 c64scr.print("error max3\n")
|
||||||
|
ww = max(warr)
|
||||||
|
if ww!=999 c64scr.print("error max4\n")
|
||||||
|
ff = max(farr)
|
||||||
|
if ff!=999.9 c64scr.print("error max5\n")
|
||||||
|
|
||||||
|
; MIN
|
||||||
|
ub = min(ubarr)
|
||||||
|
if ub!=0 c64scr.print("error min1\n")
|
||||||
|
bb = min(barr)
|
||||||
|
if bb!=-122 c64scr.print("error min2\n")
|
||||||
|
uw = min(uwarr)
|
||||||
|
if uw!=0 c64scr.print("error min3\n")
|
||||||
|
ww = min(warr)
|
||||||
|
if ww!=-4444 c64scr.print("error min4\n")
|
||||||
|
ff = min(farr)
|
||||||
|
if ff!=-4444.4 c64scr.print("error min5\n")
|
||||||
|
|
||||||
|
; SUM
|
||||||
|
uw = sum(ubarr)
|
||||||
|
if uw!=420 c64scr.print("error sum1\n")
|
||||||
|
ww = sum(barr)
|
||||||
|
if ww!=-101 c64scr.print("error sum2\n")
|
||||||
|
uw = sum(uwarr)
|
||||||
|
if uw!=6665 c64scr.print("error sum3\n")
|
||||||
|
ww = sum(warr)
|
||||||
|
if ww!=-4223 c64scr.print("error sum4\n")
|
||||||
|
ff = sum(farr)
|
||||||
|
if ff!=-4222.4 c64scr.print("error sum5\n")
|
||||||
|
|
||||||
|
; ANY
|
||||||
|
ub = any(ubarr)
|
||||||
|
if ub==0 c64scr.print("error any1\n")
|
||||||
|
ub = any(barr)
|
||||||
|
if ub==0 c64scr.print("error any2\n")
|
||||||
|
ub = any(uwarr)
|
||||||
|
if ub==0 c64scr.print("error any3\n")
|
||||||
|
ub = any(warr)
|
||||||
|
if ub==0 c64scr.print("error any4\n")
|
||||||
|
ub = any(farr)
|
||||||
|
if ub==0 c64scr.print("error any5\n")
|
||||||
|
|
||||||
|
; ALL
|
||||||
|
ub = all(ubarr)
|
||||||
|
if ub==1 c64scr.print("error all1\n")
|
||||||
|
ub = all(barr)
|
||||||
|
if ub==1 c64scr.print("error all2\n")
|
||||||
|
ub = all(uwarr)
|
||||||
|
if ub==1 c64scr.print("error all3\n")
|
||||||
|
ub = all(warr)
|
||||||
|
if ub==1 c64scr.print("error all4\n")
|
||||||
|
ub = all(farr)
|
||||||
|
if ub==1 c64scr.print("error all5\n")
|
||||||
|
ubarr[1]=$40
|
||||||
|
barr[1]=$40
|
||||||
|
uwarr[1]=$4000
|
||||||
|
warr[1]=$4000
|
||||||
|
farr[1]=1.1
|
||||||
|
ub = all(ubarr)
|
||||||
|
if ub==0 c64scr.print("error all6\n")
|
||||||
|
ub = all(barr)
|
||||||
|
if ub==0 c64scr.print("error all7\n")
|
||||||
|
ub = all(uwarr)
|
||||||
|
if ub==0 c64scr.print("error all8\n")
|
||||||
|
ub = all(warr)
|
||||||
|
if ub==0 c64scr.print("error all9\n")
|
||||||
|
ub = all(farr)
|
||||||
|
if ub==0 c64scr.print("error all10\n")
|
||||||
|
|
||||||
|
|
||||||
|
c64scr.print("\nyou should see no errors above.")
|
||||||
|
}
|
||||||
|
}
|
111
examples/arithmetic/bitshift.p8
Normal file
111
examples/arithmetic/bitshift.p8
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
%import c64utils
|
||||||
|
%zeropage basicsafe
|
||||||
|
|
||||||
|
main {
|
||||||
|
|
||||||
|
byte bb
|
||||||
|
ubyte ub
|
||||||
|
word ww
|
||||||
|
uword uw
|
||||||
|
&ubyte membyte=9999
|
||||||
|
&uword memword=9999
|
||||||
|
ubyte[] ubarray = [8,8,8]
|
||||||
|
uword[] uwarray = [8200, 8200, 8200]
|
||||||
|
byte[] bbarray = [8,8,8]
|
||||||
|
word[] wwarray = [8200, 8200, 8200]
|
||||||
|
|
||||||
|
sub unimplemented() {
|
||||||
|
; TODO implement these asm routines
|
||||||
|
lsr(ubarray[1])
|
||||||
|
lsl(ubarray[1])
|
||||||
|
ror(ubarray[1])
|
||||||
|
rol(ubarray[1])
|
||||||
|
ror2(ubarray[1])
|
||||||
|
rol2(ubarray[1])
|
||||||
|
lsr(bbarray[1])
|
||||||
|
lsl(bbarray[1])
|
||||||
|
|
||||||
|
lsr(uwarray[1])
|
||||||
|
lsl(uwarray[1])
|
||||||
|
ror(uwarray[1])
|
||||||
|
rol(uwarray[1])
|
||||||
|
ror2(uwarray[1])
|
||||||
|
rol2(uwarray[1])
|
||||||
|
lsr(wwarray[1])
|
||||||
|
lsl(wwarray[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
sub start() {
|
||||||
|
unimplemented()
|
||||||
|
lsr(A)
|
||||||
|
lsl(A)
|
||||||
|
ror(A)
|
||||||
|
rol(A)
|
||||||
|
ror2(A)
|
||||||
|
rol2(A)
|
||||||
|
lsr(Y)
|
||||||
|
lsl(Y)
|
||||||
|
ror(Y)
|
||||||
|
rol(Y)
|
||||||
|
ror2(Y)
|
||||||
|
rol2(Y)
|
||||||
|
|
||||||
|
lsr(bb)
|
||||||
|
lsl(bb)
|
||||||
|
|
||||||
|
lsr(membyte)
|
||||||
|
lsl(membyte)
|
||||||
|
ror(membyte)
|
||||||
|
rol(membyte)
|
||||||
|
ror2(membyte)
|
||||||
|
rol2(membyte)
|
||||||
|
lsr(memword)
|
||||||
|
lsl(memword)
|
||||||
|
ror(memword)
|
||||||
|
rol(memword)
|
||||||
|
ror2(memword)
|
||||||
|
rol2(memword)
|
||||||
|
|
||||||
|
lsl(@(9999))
|
||||||
|
lsr(@(9999))
|
||||||
|
ror(@(9999))
|
||||||
|
rol(@(9999))
|
||||||
|
ror2(@(9999))
|
||||||
|
rol2(@(9999))
|
||||||
|
|
||||||
|
lsl(@(9999+A))
|
||||||
|
lsr(@(9999+A))
|
||||||
|
ror(@(9999+A))
|
||||||
|
rol(@(9999+A))
|
||||||
|
ror2(@(9999+A))
|
||||||
|
rol2(@(9999+A))
|
||||||
|
|
||||||
|
bb /= 2
|
||||||
|
bb >>= 1
|
||||||
|
bb *= 4
|
||||||
|
bb <<= 2
|
||||||
|
|
||||||
|
ub /= 2
|
||||||
|
ub >>= 1
|
||||||
|
ub *= 4
|
||||||
|
ub <<= 2
|
||||||
|
rol(ub)
|
||||||
|
ror(ub)
|
||||||
|
rol2(ub)
|
||||||
|
ror2(ub)
|
||||||
|
|
||||||
|
ww /= 2
|
||||||
|
ww >>= 1
|
||||||
|
ww *= 4
|
||||||
|
ww <<= 2
|
||||||
|
|
||||||
|
uw /= 2
|
||||||
|
uw >>= 1
|
||||||
|
uw *= 4
|
||||||
|
uw <<= 2
|
||||||
|
rol(uw)
|
||||||
|
ror(uw)
|
||||||
|
rol2(uw)
|
||||||
|
ror2(uw)
|
||||||
|
}
|
||||||
|
}
|
105
examples/arithmetic/div.p8
Normal file
105
examples/arithmetic/div.p8
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
%import c64lib
|
||||||
|
%import c64utils
|
||||||
|
%import c64flt
|
||||||
|
%zeropage basicsafe
|
||||||
|
|
||||||
|
main {
|
||||||
|
|
||||||
|
sub start() {
|
||||||
|
div_ubyte(0, 1, 0)
|
||||||
|
div_ubyte(100, 6, 16)
|
||||||
|
div_ubyte(255, 2, 127)
|
||||||
|
|
||||||
|
div_byte(0, 1, 0)
|
||||||
|
div_byte(100, -6, -16)
|
||||||
|
div_byte(127, -2, -63)
|
||||||
|
|
||||||
|
div_uword(0,1,0)
|
||||||
|
div_uword(40000,500,80)
|
||||||
|
div_uword(43211,2,21605)
|
||||||
|
|
||||||
|
div_word(0,1,0)
|
||||||
|
div_word(-20000,500,-40)
|
||||||
|
div_word(-2222,2,-1111)
|
||||||
|
|
||||||
|
div_float(0,1,0)
|
||||||
|
div_float(999.9,111.0,9.008108108108107)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
sub div_ubyte(ubyte a1, ubyte a2, ubyte c) {
|
||||||
|
ubyte r = a1/a2
|
||||||
|
if r==c
|
||||||
|
c64scr.print(" ok ")
|
||||||
|
else
|
||||||
|
c64scr.print("err! ")
|
||||||
|
c64scr.print("ubyte ")
|
||||||
|
c64scr.print_ub(a1)
|
||||||
|
c64scr.print(" / ")
|
||||||
|
c64scr.print_ub(a2)
|
||||||
|
c64scr.print(" = ")
|
||||||
|
c64scr.print_ub(r)
|
||||||
|
c64.CHROUT('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
sub div_byte(byte a1, byte a2, byte c) {
|
||||||
|
byte r = a1/a2
|
||||||
|
if r==c
|
||||||
|
c64scr.print(" ok ")
|
||||||
|
else
|
||||||
|
c64scr.print("err! ")
|
||||||
|
c64scr.print("byte ")
|
||||||
|
c64scr.print_b(a1)
|
||||||
|
c64scr.print(" / ")
|
||||||
|
c64scr.print_b(a2)
|
||||||
|
c64scr.print(" = ")
|
||||||
|
c64scr.print_b(r)
|
||||||
|
c64.CHROUT('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
sub div_uword(uword a1, uword a2, uword c) {
|
||||||
|
uword r = a1/a2
|
||||||
|
if r==c
|
||||||
|
c64scr.print(" ok ")
|
||||||
|
else
|
||||||
|
c64scr.print("err! ")
|
||||||
|
c64scr.print("uword ")
|
||||||
|
c64scr.print_uw(a1)
|
||||||
|
c64scr.print(" / ")
|
||||||
|
c64scr.print_uw(a2)
|
||||||
|
c64scr.print(" = ")
|
||||||
|
c64scr.print_uw(r)
|
||||||
|
c64.CHROUT('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
sub div_word(word a1, word a2, word c) {
|
||||||
|
word r = a1/a2
|
||||||
|
if r==c
|
||||||
|
c64scr.print(" ok ")
|
||||||
|
else
|
||||||
|
c64scr.print("err! ")
|
||||||
|
c64scr.print("word ")
|
||||||
|
c64scr.print_w(a1)
|
||||||
|
c64scr.print(" / ")
|
||||||
|
c64scr.print_w(a2)
|
||||||
|
c64scr.print(" = ")
|
||||||
|
c64scr.print_w(r)
|
||||||
|
c64.CHROUT('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
sub div_float(float a1, float a2, float c) {
|
||||||
|
float r = a1/a2
|
||||||
|
if abs(r-c)<0.00001
|
||||||
|
c64scr.print(" ok ")
|
||||||
|
else
|
||||||
|
c64scr.print("err! ")
|
||||||
|
|
||||||
|
c64scr.print("float ")
|
||||||
|
c64flt.print_f(a1)
|
||||||
|
c64scr.print(" / ")
|
||||||
|
c64flt.print_f(a2)
|
||||||
|
c64scr.print(" = ")
|
||||||
|
c64flt.print_f(r)
|
||||||
|
c64.CHROUT('\n')
|
||||||
|
}
|
||||||
|
}
|
113
examples/arithmetic/minus.p8
Normal file
113
examples/arithmetic/minus.p8
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
%import c64lib
|
||||||
|
%import c64utils
|
||||||
|
%import c64flt
|
||||||
|
%zeropage basicsafe
|
||||||
|
|
||||||
|
main {
|
||||||
|
|
||||||
|
sub start() {
|
||||||
|
minus_ubyte(0, 0, 0)
|
||||||
|
minus_ubyte(200, 0, 200)
|
||||||
|
minus_ubyte(200, 100, 100)
|
||||||
|
minus_ubyte(100, 200, 156)
|
||||||
|
|
||||||
|
minus_byte(0, 0, 0)
|
||||||
|
minus_byte(100, 100, 0)
|
||||||
|
minus_byte(50, -50, 100)
|
||||||
|
minus_byte(0, -30, 30)
|
||||||
|
minus_byte(-30, 0, -30)
|
||||||
|
|
||||||
|
minus_uword(0,0,0)
|
||||||
|
minus_uword(50000,0, 50000)
|
||||||
|
minus_uword(50000,20000,30000)
|
||||||
|
minus_uword(20000,50000,35536)
|
||||||
|
|
||||||
|
minus_word(0,0,0)
|
||||||
|
minus_word(1000,1000,0)
|
||||||
|
minus_word(-1000,1000,-2000)
|
||||||
|
minus_word(1000,500,500)
|
||||||
|
minus_word(0,-3333,3333)
|
||||||
|
minus_word(-3333,0,-3333)
|
||||||
|
|
||||||
|
minus_float(0,0,0)
|
||||||
|
minus_float(2.5,1.5,1.0)
|
||||||
|
minus_float(-1.5,3.5,-5.0)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
sub minus_ubyte(ubyte a1, ubyte a2, ubyte c) {
|
||||||
|
ubyte r = a1-a2
|
||||||
|
if r==c
|
||||||
|
c64scr.print(" ok ")
|
||||||
|
else
|
||||||
|
c64scr.print("err! ")
|
||||||
|
c64scr.print("ubyte ")
|
||||||
|
c64scr.print_ub(a1)
|
||||||
|
c64scr.print(" - ")
|
||||||
|
c64scr.print_ub(a2)
|
||||||
|
c64scr.print(" = ")
|
||||||
|
c64scr.print_ub(r)
|
||||||
|
c64.CHROUT('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
sub minus_byte(byte a1, byte a2, byte c) {
|
||||||
|
byte r = a1-a2
|
||||||
|
if r==c
|
||||||
|
c64scr.print(" ok ")
|
||||||
|
else
|
||||||
|
c64scr.print("err! ")
|
||||||
|
c64scr.print("byte ")
|
||||||
|
c64scr.print_b(a1)
|
||||||
|
c64scr.print(" - ")
|
||||||
|
c64scr.print_b(a2)
|
||||||
|
c64scr.print(" = ")
|
||||||
|
c64scr.print_b(r)
|
||||||
|
c64.CHROUT('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
sub minus_uword(uword a1, uword a2, uword c) {
|
||||||
|
uword r = a1-a2
|
||||||
|
if r==c
|
||||||
|
c64scr.print(" ok ")
|
||||||
|
else
|
||||||
|
c64scr.print("err! ")
|
||||||
|
c64scr.print("uword ")
|
||||||
|
c64scr.print_uw(a1)
|
||||||
|
c64scr.print(" - ")
|
||||||
|
c64scr.print_uw(a2)
|
||||||
|
c64scr.print(" = ")
|
||||||
|
c64scr.print_uw(r)
|
||||||
|
c64.CHROUT('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
sub minus_word(word a1, word a2, word c) {
|
||||||
|
word r = a1-a2
|
||||||
|
if r==c
|
||||||
|
c64scr.print(" ok ")
|
||||||
|
else
|
||||||
|
c64scr.print("err! ")
|
||||||
|
c64scr.print("word ")
|
||||||
|
c64scr.print_w(a1)
|
||||||
|
c64scr.print(" - ")
|
||||||
|
c64scr.print_w(a2)
|
||||||
|
c64scr.print(" = ")
|
||||||
|
c64scr.print_w(r)
|
||||||
|
c64.CHROUT('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
sub minus_float(float a1, float a2, float c) {
|
||||||
|
float r = a1-a2
|
||||||
|
if abs(r-c)<0.00001
|
||||||
|
c64scr.print(" ok ")
|
||||||
|
else
|
||||||
|
c64scr.print("err! ")
|
||||||
|
|
||||||
|
c64scr.print("float ")
|
||||||
|
c64flt.print_f(a1)
|
||||||
|
c64scr.print(" - ")
|
||||||
|
c64flt.print_f(a2)
|
||||||
|
c64scr.print(" = ")
|
||||||
|
c64flt.print_f(r)
|
||||||
|
c64.CHROUT('\n')
|
||||||
|
}
|
||||||
|
}
|
107
examples/arithmetic/mult.p8
Normal file
107
examples/arithmetic/mult.p8
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
%import c64lib
|
||||||
|
%import c64utils
|
||||||
|
%import c64flt
|
||||||
|
%zeropage basicsafe
|
||||||
|
|
||||||
|
main {
|
||||||
|
|
||||||
|
sub start() {
|
||||||
|
mul_ubyte(0, 0, 0)
|
||||||
|
mul_ubyte(20, 1, 20)
|
||||||
|
mul_ubyte(20, 10, 200)
|
||||||
|
|
||||||
|
mul_byte(0, 0, 0)
|
||||||
|
mul_byte(10, 10, 100)
|
||||||
|
mul_byte(5, -5, -25)
|
||||||
|
mul_byte(0, -30, 0)
|
||||||
|
|
||||||
|
mul_uword(0,0,0)
|
||||||
|
mul_uword(50000,1, 50000)
|
||||||
|
mul_uword(500,100,50000)
|
||||||
|
|
||||||
|
mul_word(0,0,0)
|
||||||
|
mul_word(-10,1000,-10000)
|
||||||
|
mul_word(1,-3333,-3333)
|
||||||
|
|
||||||
|
mul_float(0,0,0)
|
||||||
|
mul_float(2.5,10,25)
|
||||||
|
mul_float(-1.5,10,-15)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
sub mul_ubyte(ubyte a1, ubyte a2, ubyte c) {
|
||||||
|
ubyte r = a1*a2
|
||||||
|
if r==c
|
||||||
|
c64scr.print(" ok ")
|
||||||
|
else
|
||||||
|
c64scr.print("err! ")
|
||||||
|
c64scr.print("ubyte ")
|
||||||
|
c64scr.print_ub(a1)
|
||||||
|
c64scr.print(" * ")
|
||||||
|
c64scr.print_ub(a2)
|
||||||
|
c64scr.print(" = ")
|
||||||
|
c64scr.print_ub(r)
|
||||||
|
c64.CHROUT('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
sub mul_byte(byte a1, byte a2, byte c) {
|
||||||
|
byte r = a1*a2
|
||||||
|
if r==c
|
||||||
|
c64scr.print(" ok ")
|
||||||
|
else
|
||||||
|
c64scr.print("err! ")
|
||||||
|
c64scr.print("byte ")
|
||||||
|
c64scr.print_b(a1)
|
||||||
|
c64scr.print(" * ")
|
||||||
|
c64scr.print_b(a2)
|
||||||
|
c64scr.print(" = ")
|
||||||
|
c64scr.print_b(r)
|
||||||
|
c64.CHROUT('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
sub mul_uword(uword a1, uword a2, uword c) {
|
||||||
|
uword r = a1*a2
|
||||||
|
if r==c
|
||||||
|
c64scr.print(" ok ")
|
||||||
|
else
|
||||||
|
c64scr.print("err! ")
|
||||||
|
c64scr.print("uword ")
|
||||||
|
c64scr.print_uw(a1)
|
||||||
|
c64scr.print(" * ")
|
||||||
|
c64scr.print_uw(a2)
|
||||||
|
c64scr.print(" = ")
|
||||||
|
c64scr.print_uw(r)
|
||||||
|
c64.CHROUT('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
sub mul_word(word a1, word a2, word c) {
|
||||||
|
word r = a1*a2
|
||||||
|
if r==c
|
||||||
|
c64scr.print(" ok ")
|
||||||
|
else
|
||||||
|
c64scr.print("err! ")
|
||||||
|
c64scr.print("word ")
|
||||||
|
c64scr.print_w(a1)
|
||||||
|
c64scr.print(" * ")
|
||||||
|
c64scr.print_w(a2)
|
||||||
|
c64scr.print(" = ")
|
||||||
|
c64scr.print_w(r)
|
||||||
|
c64.CHROUT('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
sub mul_float(float a1, float a2, float c) {
|
||||||
|
float r = a1*a2
|
||||||
|
if abs(r-c)<0.00001
|
||||||
|
c64scr.print(" ok ")
|
||||||
|
else
|
||||||
|
c64scr.print("err! ")
|
||||||
|
|
||||||
|
c64scr.print("float ")
|
||||||
|
c64flt.print_f(a1)
|
||||||
|
c64scr.print(" * ")
|
||||||
|
c64flt.print_f(a2)
|
||||||
|
c64scr.print(" = ")
|
||||||
|
c64flt.print_f(r)
|
||||||
|
c64.CHROUT('\n')
|
||||||
|
}
|
||||||
|
}
|
111
examples/arithmetic/plus.p8
Normal file
111
examples/arithmetic/plus.p8
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
%import c64lib
|
||||||
|
%import c64utils
|
||||||
|
%import c64flt
|
||||||
|
%zeropage basicsafe
|
||||||
|
|
||||||
|
main {
|
||||||
|
|
||||||
|
sub start() {
|
||||||
|
plus_ubyte(0, 0, 0)
|
||||||
|
plus_ubyte(0, 200, 200)
|
||||||
|
plus_ubyte(100, 200, 44)
|
||||||
|
|
||||||
|
plus_byte(0, 0, 0)
|
||||||
|
plus_byte(-100, 100, 0)
|
||||||
|
plus_byte(-50, 100, 50)
|
||||||
|
plus_byte(0, -30, -30)
|
||||||
|
plus_byte(-30, 0, -30)
|
||||||
|
|
||||||
|
plus_uword(0,0,0)
|
||||||
|
plus_uword(0,50000,50000)
|
||||||
|
plus_uword(50000,20000,4464)
|
||||||
|
|
||||||
|
plus_word(0,0,0)
|
||||||
|
plus_word(-1000,1000,0)
|
||||||
|
plus_word(-500,1000,500)
|
||||||
|
plus_word(0,-3333,-3333)
|
||||||
|
plus_word(-3333,0,-3333)
|
||||||
|
|
||||||
|
plus_float(0,0,0)
|
||||||
|
plus_float(1.5,2.5,4.0)
|
||||||
|
plus_float(-1.5,3.5,2.0)
|
||||||
|
plus_float(-1.1,3.3,2.2)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
sub plus_ubyte(ubyte a1, ubyte a2, ubyte c) {
|
||||||
|
ubyte r = a1+a2
|
||||||
|
if r==c
|
||||||
|
c64scr.print(" ok ")
|
||||||
|
else
|
||||||
|
c64scr.print("err! ")
|
||||||
|
c64scr.print("ubyte ")
|
||||||
|
c64scr.print_ub(a1)
|
||||||
|
c64scr.print(" + ")
|
||||||
|
c64scr.print_ub(a2)
|
||||||
|
c64scr.print(" = ")
|
||||||
|
c64scr.print_ub(r)
|
||||||
|
c64.CHROUT('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
sub plus_byte(byte a1, byte a2, byte c) {
|
||||||
|
byte r = a1+a2
|
||||||
|
if r==c
|
||||||
|
c64scr.print(" ok ")
|
||||||
|
else
|
||||||
|
c64scr.print("err! ")
|
||||||
|
c64scr.print("byte ")
|
||||||
|
c64scr.print_b(a1)
|
||||||
|
c64scr.print(" + ")
|
||||||
|
c64scr.print_b(a2)
|
||||||
|
c64scr.print(" = ")
|
||||||
|
c64scr.print_b(r)
|
||||||
|
c64.CHROUT('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
sub plus_uword(uword a1, uword a2, uword c) {
|
||||||
|
uword r = a1+a2
|
||||||
|
if r==c
|
||||||
|
c64scr.print(" ok ")
|
||||||
|
else
|
||||||
|
c64scr.print("err! ")
|
||||||
|
c64scr.print("uword ")
|
||||||
|
c64scr.print_uw(a1)
|
||||||
|
c64scr.print(" + ")
|
||||||
|
c64scr.print_uw(a2)
|
||||||
|
c64scr.print(" = ")
|
||||||
|
c64scr.print_uw(r)
|
||||||
|
c64.CHROUT('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
sub plus_word(word a1, word a2, word c) {
|
||||||
|
word r = a1+a2
|
||||||
|
if r==c
|
||||||
|
c64scr.print(" ok ")
|
||||||
|
else
|
||||||
|
c64scr.print("err! ")
|
||||||
|
c64scr.print("word ")
|
||||||
|
c64scr.print_w(a1)
|
||||||
|
c64scr.print(" + ")
|
||||||
|
c64scr.print_w(a2)
|
||||||
|
c64scr.print(" = ")
|
||||||
|
c64scr.print_w(r)
|
||||||
|
c64.CHROUT('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
sub plus_float(float a1, float a2, float c) {
|
||||||
|
float r = a1+a2
|
||||||
|
if abs(r-c)<0.00001
|
||||||
|
c64scr.print(" ok ")
|
||||||
|
else
|
||||||
|
c64scr.print("err! ")
|
||||||
|
|
||||||
|
c64scr.print("float ")
|
||||||
|
c64flt.print_f(a1)
|
||||||
|
c64scr.print(" + ")
|
||||||
|
c64flt.print_f(a2)
|
||||||
|
c64scr.print(" = ")
|
||||||
|
c64flt.print_f(r)
|
||||||
|
c64.CHROUT('\n')
|
||||||
|
}
|
||||||
|
}
|
142
examples/arithmetic/postincrdecr.p8
Normal file
142
examples/arithmetic/postincrdecr.p8
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
%import c64utils
|
||||||
|
%import c64flt
|
||||||
|
%option enable_floats
|
||||||
|
%zeropage basicsafe
|
||||||
|
|
||||||
|
main {
|
||||||
|
|
||||||
|
|
||||||
|
sub start() {
|
||||||
|
|
||||||
|
c64scr.plot(0,24)
|
||||||
|
|
||||||
|
ubyte ub=200
|
||||||
|
byte bb=-100
|
||||||
|
uword uw = 2000
|
||||||
|
word ww = -1000
|
||||||
|
float fl = 999.99
|
||||||
|
ubyte[3] ubarr = 200
|
||||||
|
byte[3] barr = -100
|
||||||
|
uword[3] uwarr = 2000
|
||||||
|
word[3] warr = -1000
|
||||||
|
float[3] flarr = 999.99
|
||||||
|
|
||||||
|
c64scr.print("++\n")
|
||||||
|
ub++
|
||||||
|
bb++
|
||||||
|
uw++
|
||||||
|
ww++
|
||||||
|
fl++
|
||||||
|
ubarr[1]++
|
||||||
|
barr[1]++
|
||||||
|
uwarr[1]++
|
||||||
|
warr[1]++
|
||||||
|
flarr[1] ++
|
||||||
|
|
||||||
|
check_ub(ub, 201)
|
||||||
|
Y=100
|
||||||
|
Y++
|
||||||
|
check_ub(Y, 101)
|
||||||
|
check_fl(fl, 1000.99)
|
||||||
|
check_b(bb, -99)
|
||||||
|
check_uw(uw, 2001)
|
||||||
|
check_w(ww, -999)
|
||||||
|
check_ub(ubarr[0], 200)
|
||||||
|
check_fl(flarr[0], 999.99)
|
||||||
|
check_b(barr[0], -100)
|
||||||
|
check_uw(uwarr[0], 2000)
|
||||||
|
check_w(warr[0], -1000)
|
||||||
|
check_ub(ubarr[1], 201)
|
||||||
|
check_fl(flarr[1], 1000.99)
|
||||||
|
check_b(barr[1], -99)
|
||||||
|
check_uw(uwarr[1], 2001)
|
||||||
|
check_w(warr[1], -999)
|
||||||
|
|
||||||
|
c64scr.print("--\n")
|
||||||
|
ub--
|
||||||
|
bb--
|
||||||
|
uw--
|
||||||
|
ww--
|
||||||
|
fl--
|
||||||
|
ubarr[1]--
|
||||||
|
barr[1]--
|
||||||
|
uwarr[1]--
|
||||||
|
warr[1]--
|
||||||
|
flarr[1] --
|
||||||
|
check_ub(ub, 200)
|
||||||
|
Y=100
|
||||||
|
Y--
|
||||||
|
check_ub(Y, 99)
|
||||||
|
check_fl(fl, 999.99)
|
||||||
|
check_b(bb, -100)
|
||||||
|
check_uw(uw, 2000)
|
||||||
|
check_w(ww, -1000)
|
||||||
|
check_ub(ubarr[1], 200)
|
||||||
|
check_fl(flarr[1], 999.99)
|
||||||
|
check_b(barr[1], -100)
|
||||||
|
check_uw(uwarr[1], 2000)
|
||||||
|
check_w(warr[1], -1000)
|
||||||
|
|
||||||
|
@($0400+400-1) = X
|
||||||
|
}
|
||||||
|
|
||||||
|
sub check_ub(ubyte value, ubyte expected) {
|
||||||
|
if value==expected
|
||||||
|
c64scr.print(" ok ")
|
||||||
|
else
|
||||||
|
c64scr.print("err! ")
|
||||||
|
c64scr.print(" ubyte ")
|
||||||
|
c64scr.print_ub(value)
|
||||||
|
c64.CHROUT(',')
|
||||||
|
c64scr.print_ub(expected)
|
||||||
|
c64.CHROUT('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
sub check_b(byte value, byte expected) {
|
||||||
|
if value==expected
|
||||||
|
c64scr.print(" ok ")
|
||||||
|
else
|
||||||
|
c64scr.print("err! ")
|
||||||
|
c64scr.print(" byte ")
|
||||||
|
c64scr.print_b(value)
|
||||||
|
c64.CHROUT(',')
|
||||||
|
c64scr.print_b(expected)
|
||||||
|
c64.CHROUT('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
sub check_uw(uword value, uword expected) {
|
||||||
|
if value==expected
|
||||||
|
c64scr.print(" ok ")
|
||||||
|
else
|
||||||
|
c64scr.print("err! ")
|
||||||
|
c64scr.print(" uword ")
|
||||||
|
c64scr.print_uw(value)
|
||||||
|
c64.CHROUT(',')
|
||||||
|
c64scr.print_uw(expected)
|
||||||
|
c64.CHROUT('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
sub check_w(word value, word expected) {
|
||||||
|
if value==expected
|
||||||
|
c64scr.print(" ok ")
|
||||||
|
else
|
||||||
|
c64scr.print("err! ")
|
||||||
|
c64scr.print(" word ")
|
||||||
|
c64scr.print_w(value)
|
||||||
|
c64.CHROUT(',')
|
||||||
|
c64scr.print_w(expected)
|
||||||
|
c64.CHROUT('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
sub check_fl(float value, float expected) {
|
||||||
|
if value==expected
|
||||||
|
c64scr.print(" ok ")
|
||||||
|
else
|
||||||
|
c64scr.print("err! ")
|
||||||
|
c64scr.print(" float ")
|
||||||
|
c64flt.print_f(value)
|
||||||
|
c64.CHROUT(',')
|
||||||
|
c64flt.print_f(expected)
|
||||||
|
c64.CHROUT('\n')
|
||||||
|
}
|
||||||
|
}
|
49
examples/arithmetic/remainder.p8
Normal file
49
examples/arithmetic/remainder.p8
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
%import c64lib
|
||||||
|
%import c64utils
|
||||||
|
%import c64flt
|
||||||
|
%zeropage basicsafe
|
||||||
|
|
||||||
|
main {
|
||||||
|
|
||||||
|
sub start() {
|
||||||
|
remainder_ubyte(0, 1, 0)
|
||||||
|
remainder_ubyte(100, 6, 4)
|
||||||
|
remainder_ubyte(255, 2, 1)
|
||||||
|
remainder_ubyte(255, 20, 15)
|
||||||
|
|
||||||
|
remainder_uword(0,1,0)
|
||||||
|
remainder_uword(40000,511,142)
|
||||||
|
remainder_uword(40000,500,0)
|
||||||
|
remainder_uword(43211,12,11)
|
||||||
|
}
|
||||||
|
|
||||||
|
sub remainder_ubyte(ubyte a1, ubyte a2, ubyte c) {
|
||||||
|
ubyte r = a1%a2
|
||||||
|
if r==c
|
||||||
|
c64scr.print(" ok ")
|
||||||
|
else
|
||||||
|
c64scr.print("err! ")
|
||||||
|
c64scr.print("ubyte ")
|
||||||
|
c64scr.print_ub(a1)
|
||||||
|
c64scr.print(" % ")
|
||||||
|
c64scr.print_ub(a2)
|
||||||
|
c64scr.print(" = ")
|
||||||
|
c64scr.print_ub(r)
|
||||||
|
c64.CHROUT('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
sub remainder_uword(uword a1, uword a2, uword c) {
|
||||||
|
uword r = a1%a2
|
||||||
|
if r==c
|
||||||
|
c64scr.print(" ok ")
|
||||||
|
else
|
||||||
|
c64scr.print("err! ")
|
||||||
|
c64scr.print("uword ")
|
||||||
|
c64scr.print_uw(a1)
|
||||||
|
c64scr.print(" % ")
|
||||||
|
c64scr.print_uw(a2)
|
||||||
|
c64scr.print(" = ")
|
||||||
|
c64scr.print_uw(r)
|
||||||
|
c64.CHROUT('\n')
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user