mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 20:02:27 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
No files matched your search
@@ -24,8 +24,6 @@ __pycache__/
|
||||
parser.out
|
||||
parsetab.py
|
||||
.pytest_cache/
|
||||
compiler/src/prog8_kotlin.jar
|
||||
compiler/src/compiled_java
|
||||
.attach_pid*
|
||||
|
||||
.gradle
|
||||
|
||||
Generated
+2
@@ -0,0 +1,2 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
Generated
+1
@@ -2,6 +2,7 @@
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/DeprecatedStackVm/DeprecatedStackVm.iml" filepath="$PROJECT_DIR$/DeprecatedStackVm/DeprecatedStackVm.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$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
|
||||
|
||||
@@ -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>
|
||||
File diff suppressed because it is too large.
Load diff
+5
-5
@@ -1,4 +1,4 @@
|
||||
package prog8.compiler.intermediate
|
||||
package compiler.intermediate
|
||||
|
||||
import prog8.vm.RuntimeValue
|
||||
import prog8.vm.stackvm.Syscall
|
||||
@@ -15,8 +15,8 @@ open class Instruction(val opcode: Opcode,
|
||||
val argStr = arg?.toString() ?: ""
|
||||
val result =
|
||||
when {
|
||||
opcode==Opcode.LINE -> "_line $callLabel"
|
||||
opcode==Opcode.INLINE_ASSEMBLY -> {
|
||||
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)
|
||||
@@ -24,10 +24,10 @@ open class Instruction(val opcode: Opcode,
|
||||
else
|
||||
"inline_assembly"
|
||||
}
|
||||
opcode==Opcode.INCLUDE_FILE -> {
|
||||
opcode== Opcode.INCLUDE_FILE -> {
|
||||
"include_file \"$callLabel\" $arg $arg2"
|
||||
}
|
||||
opcode==Opcode.SYSCALL -> {
|
||||
opcode== Opcode.SYSCALL -> {
|
||||
val syscall = Syscall.values().find { it.callNr==arg!!.numericValue() }
|
||||
"syscall $syscall"
|
||||
}
|
||||
+53
-41
@@ -1,58 +1,53 @@
|
||||
package prog8.compiler.intermediate
|
||||
package compiler.intermediate
|
||||
|
||||
import prog8.ast.antlr.escape
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.base.printWarning
|
||||
import prog8.ast.expressions.LiteralValue
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.ReferenceLiteralValue
|
||||
import prog8.ast.statements.StructDecl
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.vm.RuntimeValue
|
||||
import prog8.ast.statements.ZeropageWish
|
||||
import prog8.compiler.CompilerException
|
||||
import prog8.compiler.HeapValues
|
||||
import prog8.compiler.Zeropage
|
||||
import prog8.compiler.ZeropageDepletedError
|
||||
import prog8.vm.RuntimeValue
|
||||
import java.io.PrintStream
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
class IntermediateProgram(val name: String, var loadAddress: Int, val heap: HeapValues, val source: Path) {
|
||||
|
||||
class VariableParameters (val zp: ZeropageWish, val memberOfStruct: StructDecl?)
|
||||
class Variable(val scopedname: String, val value: RuntimeValue, val params: VariableParameters)
|
||||
|
||||
class ProgramBlock(val name: String,
|
||||
var address: Int?,
|
||||
val instructions: MutableList<Instruction> = mutableListOf(),
|
||||
val variables: MutableMap<String, RuntimeValue> = mutableMapOf(), // names are fully scoped
|
||||
val variables: MutableList<Variable> = mutableListOf(),
|
||||
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) {
|
||||
fun allocateZeropage(zeropage: Zeropage) { // TODO not used anymore???
|
||||
// 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 }
|
||||
val zpVariables = block.variables.filter { it.params.zp==ZeropageWish.REQUIRE_ZEROPAGE || it.params.zp==ZeropageWish.PREFER_ZEROPAGE }
|
||||
if (zpVariables.isNotEmpty()) {
|
||||
for (variable in zpVariables) {
|
||||
if(variable.params.zp==ZeropageWish.NOT_IN_ZEROPAGE || variable.params.memberOfStruct!=null)
|
||||
throw CompilerException("zp conflict")
|
||||
try {
|
||||
val address = zeropage.allocate(variable.key, variable.value.type, null)
|
||||
allocatedZeropageVariables[variable.key] = Pair(address, variable.value.type)
|
||||
val address = zeropage.allocate(variable.scopedname, variable.value.type, null)
|
||||
allocatedZeropageVariables[variable.scopedname] = Pair(address, variable.value.type)
|
||||
} catch (x: ZeropageDepletedError) {
|
||||
printWarning(x.toString() + " variable ${variable.key} type ${variable.value.type}")
|
||||
printWarning(x.toString() + " variable ${variable.scopedname} type ${variable.value.type}")
|
||||
notAllocated++
|
||||
}
|
||||
}
|
||||
@@ -90,7 +85,7 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
|
||||
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 {
|
||||
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
|
||||
@@ -143,8 +138,8 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
|
||||
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) {
|
||||
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)
|
||||
}
|
||||
@@ -320,12 +315,12 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
|
||||
instructionsToReplace[index0] = ins
|
||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
||||
}
|
||||
Opcode.CAST_B_TO_F, Opcode.CAST_UB_TO_F-> {
|
||||
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.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)
|
||||
@@ -393,39 +388,53 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
|
||||
fun variable(scopedname: String, decl: VarDecl) {
|
||||
when(decl.type) {
|
||||
VarDeclType.VAR -> {
|
||||
// var decls that are defined inside of a StructDecl are skipped in the output
|
||||
// because every occurrence of the members will have a separate mangled vardecl for that occurrence
|
||||
if(decl.parent is StructDecl)
|
||||
return
|
||||
|
||||
val valueparams = VariableParameters(decl.zeropage, decl.struct)
|
||||
val value = when(decl.datatype) {
|
||||
in NumericDatatypes -> RuntimeValue(decl.datatype, (decl.value as LiteralValue).asNumericValue!!)
|
||||
in NumericDatatypes -> {
|
||||
RuntimeValue(decl.datatype, (decl.value as NumericLiteralValue).number)
|
||||
}
|
||||
in StringDatatypes -> {
|
||||
val litval = (decl.value as LiteralValue)
|
||||
val litval = (decl.value as ReferenceLiteralValue)
|
||||
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)
|
||||
val litval = (decl.value as? ReferenceLiteralValue)
|
||||
if(litval!=null && litval.heapId==null)
|
||||
throw CompilerException("array should already be in the heap")
|
||||
RuntimeValue(decl.datatype, heapId = litval.heapId)
|
||||
if(litval!=null){
|
||||
RuntimeValue(decl.datatype, heapId = litval.heapId)
|
||||
} else {
|
||||
throw CompilerException("initialization value expected")
|
||||
}
|
||||
}
|
||||
DataType.STRUCT -> {
|
||||
// struct variables have been flattened already
|
||||
return
|
||||
}
|
||||
else -> throw CompilerException("weird datatype")
|
||||
}
|
||||
currentBlock.variables[scopedname] = value
|
||||
if(decl.zeropage)
|
||||
currentBlock.variablesMarkedForZeropage.add(scopedname)
|
||||
currentBlock.variables.add(Variable(scopedname, value, valueparams))
|
||||
}
|
||||
VarDeclType.MEMORY -> {
|
||||
// note that constants are all folded away, but assembly code may still refer to them
|
||||
val lv = decl.value as LiteralValue
|
||||
val lv = decl.value as NumericLiteralValue
|
||||
if(lv.type!= DataType.UWORD && lv.type!= DataType.UBYTE)
|
||||
throw CompilerException("expected integer memory address $lv")
|
||||
currentBlock.memoryPointers[scopedname] = Pair(lv.asIntegerValue!!, decl.datatype)
|
||||
currentBlock.memoryPointers[scopedname] = Pair(lv.number.toInt(), 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
|
||||
val lv = decl.value as NumericLiteralValue
|
||||
if(lv.type in IntegerDatatypes)
|
||||
currentBlock.memoryPointers[scopedname] = Pair(lv.asIntegerValue!!, decl.datatype)
|
||||
currentBlock.memoryPointers[scopedname] = Pair(lv.number.toInt(), decl.datatype)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -453,7 +462,7 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
|
||||
}
|
||||
|
||||
fun newBlock(name: String, address: Int?, options: Set<String>) {
|
||||
currentBlock = ProgramBlock(name, address, force_output="force_output" in options)
|
||||
currentBlock = ProgramBlock(name, address, force_output = "force_output" in options)
|
||||
blocks.add(currentBlock)
|
||||
}
|
||||
|
||||
@@ -502,8 +511,11 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
|
||||
|
||||
out.println("%variables")
|
||||
for (variable in blk.variables) {
|
||||
if(variable.params.zp==ZeropageWish.REQUIRE_ZEROPAGE)
|
||||
throw CompilerException("zp conflict")
|
||||
val valuestr = variable.value.toString()
|
||||
out.println("${variable.key} ${variable.value.type.name.toLowerCase()} $valuestr")
|
||||
val struct = if(variable.params.memberOfStruct==null) "" else "struct=${variable.params.memberOfStruct.name}"
|
||||
out.println("${variable.scopedname} ${variable.value.type.name.toLowerCase()} $valuestr zp=${variable.params.zp} s=$struct")
|
||||
}
|
||||
out.println("%end_variables")
|
||||
out.println("%memorypointers")
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package prog8.compiler.intermediate
|
||||
package compiler.intermediate
|
||||
|
||||
enum class Opcode {
|
||||
|
||||
+128
-102
@@ -1,4 +1,4 @@
|
||||
package prog8.compiler.target.c64
|
||||
package compiler.target.c64.codegen
|
||||
|
||||
// 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
|
||||
@@ -6,9 +6,16 @@ package prog8.compiler.target.c64
|
||||
import prog8.ast.antlr.escape
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.initvarsSubName
|
||||
import prog8.vm.RuntimeValue
|
||||
import prog8.ast.statements.ZeropageWish
|
||||
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.util.*
|
||||
import kotlin.math.abs
|
||||
@@ -44,8 +51,7 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
|
||||
// Convert invalid label names (such as "<anon-1>") to something that's allowed.
|
||||
val newblocks = mutableListOf<IntermediateProgram.ProgramBlock>()
|
||||
for(block in program.blocks) {
|
||||
val newvars = block.variables.map { symname(it.key, block) to it.value }.toMap().toMutableMap()
|
||||
val newvarsZeropaged = block.variablesMarkedForZeropage.map{symname(it, block)}.toMutableSet()
|
||||
val newvars = block.variables.map { IntermediateProgram.Variable(symname(it.scopedname, block), it.value, it.params) }.toMutableList()
|
||||
val newlabels = block.labels.map { symname(it.key, block) to it.value}.toMap().toMutableMap()
|
||||
val newinstructions = block.instructions.asSequence().map {
|
||||
when {
|
||||
@@ -66,8 +72,6 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
|
||||
newMempointers,
|
||||
newlabels,
|
||||
force_output = block.force_output)
|
||||
newblock.variablesMarkedForZeropage.clear()
|
||||
newblock.variablesMarkedForZeropage.addAll(newvarsZeropaged)
|
||||
newblocks.add(newblock)
|
||||
}
|
||||
program.blocks.clear()
|
||||
@@ -146,7 +150,7 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
|
||||
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 b1 = "$"+flt.b1.toString(16).padStart(2, '0')
|
||||
val b2 = "$"+flt.b2.toString(16).padStart(2, '0')
|
||||
@@ -164,7 +168,8 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
|
||||
out("\n.cpu '6502'\n.enc 'none'\n")
|
||||
|
||||
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 {
|
||||
options.launcher == LauncherType.BASIC -> {
|
||||
@@ -220,7 +225,7 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
|
||||
|
||||
// the global list of all floating point constants for the whole program
|
||||
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}")
|
||||
}
|
||||
}
|
||||
@@ -237,16 +242,17 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
|
||||
|
||||
// deal with zeropage 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]
|
||||
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.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 {
|
||||
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
|
||||
blk.variablesMarkedForZeropage.add(variable.key)
|
||||
program.allocatedZeropageVariables[sym] = Pair(address, variable.value.type)
|
||||
} catch (x: ZeropageDepletedError) {
|
||||
// leave it as it is.
|
||||
@@ -255,7 +261,7 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
|
||||
}
|
||||
else {
|
||||
// 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 +295,98 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
|
||||
}
|
||||
|
||||
private fun vardecls2asm(block: IntermediateProgram.ProgramBlock) {
|
||||
// these are the non-zeropage variables
|
||||
val sortedVars = block.variables.filter{it.key !in block.variablesMarkedForZeropage}.toList().sortedBy { it.second.type }
|
||||
for (v in sortedVars) {
|
||||
when (v.second.type) {
|
||||
DataType.UBYTE -> out("${v.first}\t.byte 0")
|
||||
DataType.BYTE -> out("${v.first}\t.char 0")
|
||||
DataType.UWORD -> out("${v.first}\t.word 0")
|
||||
DataType.WORD -> out("${v.first}\t.sint 0")
|
||||
DataType.FLOAT -> out("${v.first}\t.byte 0,0,0,0,0 ; float")
|
||||
DataType.STR, DataType.STR_S -> {
|
||||
val rawStr = heap.get(v.second.heapId!!).str!!
|
||||
val bytes = encodeStr(rawStr, v.second.type).map { "$" + it.toString(16).padStart(2, '0') }
|
||||
out("${v.first}\t; ${v.second.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(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}")
|
||||
}
|
||||
}
|
||||
val uniqueNames = block.variables.map { it.scopedname }.toSet()
|
||||
if (uniqueNames.size != block.variables.size)
|
||||
throw AssemblyError("not all variables have unique names")
|
||||
|
||||
// these are the non-zeropage variables.
|
||||
// first get all the flattened struct members, they MUST remain in order
|
||||
out("; flattened struct members")
|
||||
val (structMembers, normalVars) = block.variables.partition { it.params.memberOfStruct!=null }
|
||||
structMembers.forEach { vardecl2asm(it.scopedname, it.value, it.params) }
|
||||
|
||||
// sort the other variables by type
|
||||
out("; other variables sorted by type")
|
||||
val sortedVars = normalVars.sortedBy { it.value.type }
|
||||
for (variable in sortedVars) {
|
||||
val sym = symname(block.name + "." + variable.scopedname, null)
|
||||
if(sym in program.allocatedZeropageVariables)
|
||||
continue // skip the ones that already belong in the zero page
|
||||
vardecl2asm(variable.scopedname, variable.value, variable.params)
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
return when(dt) {
|
||||
DataType.STR -> {
|
||||
@@ -510,7 +536,7 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
|
||||
}
|
||||
|
||||
// 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))
|
||||
continue // don't accept patterns that don't fit
|
||||
val opcodesList = opcodes.subList(0, pattern.sequence.size)
|
||||
@@ -537,31 +563,31 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
|
||||
Opcode.SHL_BYTE -> AsmFragment(" asl $variable+$index", 8)
|
||||
Opcode.SHR_UBYTE -> AsmFragment(" lsr $variable+$index", 8)
|
||||
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.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.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_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.ROR_BYTE -> AsmFragment(" ror $variable+$index", 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.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.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.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.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.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.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.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.INC_INDEXED_VAR_FLOAT -> AsmFragment(
|
||||
"""
|
||||
lda #<($variable+${index*Mflpt5.MemorySize})
|
||||
ldy #>($variable+${index*Mflpt5.MemorySize})
|
||||
"""
|
||||
lda #<($variable+${index * MachineDefinition.Mflpt5.MemorySize})
|
||||
ldy #>($variable+${index * MachineDefinition.Mflpt5.MemorySize})
|
||||
jsr c64flt.inc_var_f
|
||||
""")
|
||||
Opcode.DEC_INDEXED_VAR_FLOAT -> AsmFragment(
|
||||
"""
|
||||
lda #<($variable+${index*Mflpt5.MemorySize})
|
||||
ldy #>($variable+${index*Mflpt5.MemorySize})
|
||||
"""
|
||||
lda #<($variable+${index * MachineDefinition.Mflpt5.MemorySize})
|
||||
ldy #>($variable+${index * MachineDefinition.Mflpt5.MemorySize})
|
||||
jsr c64flt.dec_var_f
|
||||
""")
|
||||
|
||||
@@ -571,8 +597,8 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
|
||||
|
||||
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
|
||||
val saveX = " stx ${C64Zeropage.SCRATCH_B1} |"
|
||||
val restoreX = " | ldx ${C64Zeropage.SCRATCH_B1}"
|
||||
val saveX = " stx ${MachineDefinition.C64Zeropage.SCRATCH_B1} |"
|
||||
val restoreX = " | ldx ${MachineDefinition.C64Zeropage.SCRATCH_B1}"
|
||||
val loadXWord: String
|
||||
val loadX: String
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+95
-89
@@ -1,10 +1,16 @@
|
||||
package prog8.compiler.target.c64
|
||||
package compiler.target.c64.codegen
|
||||
|
||||
import prog8.compiler.CompilerException
|
||||
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.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.vm.stackvm.Syscall
|
||||
import prog8.vm.stackvm.syscallsForStackVm
|
||||
@@ -66,23 +72,23 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
||||
Opcode.DISCARD_WORD -> " inx"
|
||||
Opcode.DISCARD_FLOAT -> " inx | inx | inx"
|
||||
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 -> {
|
||||
" 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 -> {
|
||||
" 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 -> {
|
||||
"""
|
||||
inx
|
||||
lda ${ESTACK_HI.toHex()},x
|
||||
lda $ESTACK_HI_HEX,x
|
||||
cmp #>${ins.arg!!.integerValue().toHex()}
|
||||
bne +
|
||||
lda ${ESTACK_LO.toHex()},x
|
||||
lda $ESTACK_LO_HEX,x
|
||||
cmp #<${ins.arg.integerValue().toHex()}
|
||||
; bne + not necessary?
|
||||
; lda #0 not necessary?
|
||||
@@ -130,11 +136,11 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
||||
}
|
||||
|
||||
Opcode.PUSH_BYTE -> {
|
||||
" lda #${hexVal(ins)} | sta ${ESTACK_LO.toHex()},x | dex"
|
||||
" lda #${hexVal(ins)} | sta $ESTACK_LO_HEX,x | dex"
|
||||
}
|
||||
Opcode.PUSH_WORD -> {
|
||||
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 -> {
|
||||
val floatConst = getFloatConst(ins.arg!!)
|
||||
@@ -143,28 +149,28 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
||||
Opcode.PUSH_VAR_BYTE -> {
|
||||
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)")
|
||||
"A" -> " sta ${ESTACK_LO.toHex()},x | dex"
|
||||
"Y" -> " tya | sta ${ESTACK_LO.toHex()},x | dex"
|
||||
else -> " lda ${ins.callLabel} | sta ${ESTACK_LO.toHex()},x | dex"
|
||||
"A" -> " sta $ESTACK_LO_HEX,x | dex"
|
||||
"Y" -> " tya | sta $ESTACK_LO_HEX,x | dex"
|
||||
else -> " lda ${ins.callLabel} | sta $ESTACK_LO_HEX,x | dex"
|
||||
}
|
||||
}
|
||||
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_MEM_B, Opcode.PUSH_MEM_UB -> {
|
||||
"""
|
||||
lda ${hexVal(ins)}
|
||||
sta ${ESTACK_LO.toHex()},x
|
||||
sta $ESTACK_LO_HEX,x
|
||||
dex
|
||||
"""
|
||||
}
|
||||
Opcode.PUSH_MEM_W, Opcode.PUSH_MEM_UW -> {
|
||||
"""
|
||||
lda ${hexVal(ins)}
|
||||
sta ${ESTACK_LO.toHex()},x
|
||||
sta $ESTACK_LO_HEX,x
|
||||
lda ${hexValPlusOne(ins)}
|
||||
sta ${ESTACK_HI.toHex()},x
|
||||
sta $ESTACK_HI_HEX,x
|
||||
dex
|
||||
"""
|
||||
}
|
||||
@@ -173,43 +179,43 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
||||
}
|
||||
Opcode.PUSH_MEMREAD -> {
|
||||
"""
|
||||
lda ${(ESTACK_LO+1).toHex()},x
|
||||
lda $ESTACK_LO_PLUS1_HEX,x
|
||||
sta (+) +1
|
||||
lda ${(ESTACK_HI+1).toHex()},x
|
||||
lda $ESTACK_HI_PLUS1_HEX,x
|
||||
sta (+) +2
|
||||
+ lda 65535 ; modified
|
||||
sta ${(ESTACK_LO+1).toHex()},x
|
||||
sta $ESTACK_LO_PLUS1_HEX,x
|
||||
"""
|
||||
}
|
||||
|
||||
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 -> {
|
||||
" 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_REGXY_WORD -> throw AssemblyError("cannot load X register from stack because it's used as the stack pointer itself")
|
||||
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 -> {
|
||||
"""
|
||||
ldy ${(ESTACK_LO+1).toHex()},x
|
||||
ldy $ESTACK_LO_PLUS1_HEX,x
|
||||
lda ${ins.callLabel},y
|
||||
sta ${(ESTACK_LO+1).toHex()},x
|
||||
sta $ESTACK_LO_PLUS1_HEX,x
|
||||
"""
|
||||
}
|
||||
Opcode.READ_INDEXED_VAR_WORD -> {
|
||||
"""
|
||||
lda ${(ESTACK_LO+1).toHex()},x
|
||||
lda $ESTACK_LO_PLUS1_HEX,x
|
||||
asl a
|
||||
tay
|
||||
lda ${ins.callLabel},y
|
||||
sta ${(ESTACK_LO+1).toHex()},x
|
||||
sta $ESTACK_LO_PLUS1_HEX,x
|
||||
lda ${ins.callLabel}+1,y
|
||||
sta ${(ESTACK_HI+1).toHex()},x
|
||||
sta $ESTACK_HI_PLUS1_HEX,x
|
||||
"""
|
||||
}
|
||||
Opcode.READ_INDEXED_VAR_FLOAT -> {
|
||||
@@ -222,22 +228,22 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
||||
Opcode.WRITE_INDEXED_VAR_BYTE -> {
|
||||
"""
|
||||
inx
|
||||
ldy ${ESTACK_LO.toHex()},x
|
||||
ldy $ESTACK_LO_HEX,x
|
||||
inx
|
||||
lda ${ESTACK_LO.toHex()},x
|
||||
lda $ESTACK_LO_HEX,x
|
||||
sta ${ins.callLabel},y
|
||||
"""
|
||||
}
|
||||
Opcode.WRITE_INDEXED_VAR_WORD -> {
|
||||
"""
|
||||
inx
|
||||
lda ${ESTACK_LO.toHex()},x
|
||||
lda $ESTACK_LO_HEX,x
|
||||
asl a
|
||||
tay
|
||||
inx
|
||||
lda ${ESTACK_LO.toHex()},x
|
||||
lda $ESTACK_LO_HEX,x
|
||||
sta ${ins.callLabel},y
|
||||
lda ${ESTACK_HI.toHex()},x
|
||||
lda $ESTACK_HI_HEX,x
|
||||
sta ${ins.callLabel}+1,y
|
||||
"""
|
||||
}
|
||||
@@ -251,16 +257,16 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
||||
Opcode.POP_MEM_BYTE -> {
|
||||
"""
|
||||
inx
|
||||
lda ${ESTACK_LO.toHex()},x
|
||||
lda $ESTACK_LO_HEX,x
|
||||
sta ${hexVal(ins)}
|
||||
"""
|
||||
}
|
||||
Opcode.POP_MEM_WORD -> {
|
||||
"""
|
||||
inx
|
||||
lda ${ESTACK_LO.toHex()},x
|
||||
lda $ESTACK_LO_HEX,x
|
||||
sta ${hexVal(ins)}
|
||||
lda ${ESTACK_HI.toHex()},x
|
||||
lda $ESTACK_HI_HEX,x
|
||||
sta ${hexValPlusOne(ins)}
|
||||
"""
|
||||
}
|
||||
@@ -270,12 +276,12 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
||||
Opcode.POP_MEMWRITE -> {
|
||||
"""
|
||||
inx
|
||||
lda ${ESTACK_LO.toHex()},x
|
||||
lda $ESTACK_LO_HEX,x
|
||||
sta (+) +1
|
||||
lda ${ESTACK_HI.toHex()},x
|
||||
lda $ESTACK_HI_HEX,x
|
||||
sta (+) +2
|
||||
inx
|
||||
lda ${ESTACK_LO.toHex()},x
|
||||
lda $ESTACK_LO_HEX,x
|
||||
+ sta 65535 ; modified
|
||||
"""
|
||||
}
|
||||
@@ -283,13 +289,13 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
||||
Opcode.POP_VAR_BYTE -> {
|
||||
when (ins.callLabel) {
|
||||
"X" -> throw CompilerException("makes no sense to pop X, it's used as a stack pointer itself")
|
||||
"A" -> " inx | lda ${ESTACK_LO.toHex()},x"
|
||||
"Y" -> " inx | ldy ${ESTACK_LO.toHex()},x"
|
||||
else -> " inx | lda ${ESTACK_LO.toHex()},x | sta ${ins.callLabel}"
|
||||
"A" -> " inx | lda $ESTACK_LO_HEX,x"
|
||||
"Y" -> " inx | ldy $ESTACK_LO_HEX,x"
|
||||
else -> " inx | lda $ESTACK_LO_HEX,x | sta ${ins.callLabel}"
|
||||
}
|
||||
}
|
||||
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 -> {
|
||||
" lda #<${ins.callLabel} | ldy #>${ins.callLabel} | jsr c64flt.pop_float"
|
||||
@@ -316,9 +322,9 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
||||
Opcode.POP_INC_MEMORY -> {
|
||||
"""
|
||||
inx
|
||||
lda ${ESTACK_LO.toHex()},x
|
||||
lda $ESTACK_LO_HEX,x
|
||||
sta (+) +1
|
||||
lda ${ESTACK_HI.toHex()},x
|
||||
lda $ESTACK_HI_HEX,x
|
||||
sta (+) +2
|
||||
+ inc 65535 ; modified
|
||||
"""
|
||||
@@ -326,9 +332,9 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
||||
Opcode.POP_DEC_MEMORY -> {
|
||||
"""
|
||||
inx
|
||||
lda ${ESTACK_LO.toHex()},x
|
||||
lda $ESTACK_LO_HEX,x
|
||||
sta (+) +1
|
||||
lda ${ESTACK_HI.toHex()},x
|
||||
lda $ESTACK_HI_HEX,x
|
||||
sta (+) +2
|
||||
+ dec 65535 ; modified
|
||||
"""
|
||||
@@ -353,8 +359,8 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
||||
}
|
||||
Opcode.INC_MEMORY -> " inc ${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.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.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_HEX,x | tax | dec ${ins.callLabel},x | pla | tax"
|
||||
|
||||
Opcode.NEG_B -> " jsr prog8_lib.neg_b"
|
||||
Opcode.NEG_W -> " jsr prog8_lib.neg_w"
|
||||
@@ -365,9 +371,9 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
||||
Opcode.POW_F -> " jsr c64flt.pow_f"
|
||||
Opcode.INV_BYTE -> {
|
||||
"""
|
||||
lda ${(ESTACK_LO + 1).toHex()},x
|
||||
lda $ESTACK_LO_PLUS1_HEX,x
|
||||
eor #255
|
||||
sta ${(ESTACK_LO + 1).toHex()},x
|
||||
sta $ESTACK_LO_PLUS1_HEX,x
|
||||
"""
|
||||
}
|
||||
Opcode.INV_WORD -> " jsr prog8_lib.inv_word"
|
||||
@@ -409,7 +415,7 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
||||
val label = ins.callLabel ?: hexVal(ins)
|
||||
"""
|
||||
inx
|
||||
lda ${(ESTACK_LO).toHex()},x
|
||||
lda $ESTACK_LO_HEX,x
|
||||
beq $label
|
||||
"""
|
||||
}
|
||||
@@ -417,9 +423,9 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
||||
val label = ins.callLabel ?: hexVal(ins)
|
||||
"""
|
||||
inx
|
||||
lda ${(ESTACK_LO).toHex()},x
|
||||
lda $ESTACK_LO_HEX,x
|
||||
beq $label
|
||||
lda ${(ESTACK_HI).toHex()},x
|
||||
lda $ESTACK_HI_HEX,x
|
||||
beq $label
|
||||
"""
|
||||
}
|
||||
@@ -427,7 +433,7 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
||||
val label = ins.callLabel ?: hexVal(ins)
|
||||
"""
|
||||
inx
|
||||
lda ${(ESTACK_LO).toHex()},x
|
||||
lda $ESTACK_LO_HEX,x
|
||||
bne $label
|
||||
"""
|
||||
}
|
||||
@@ -435,9 +441,9 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
||||
val label = ins.callLabel ?: hexVal(ins)
|
||||
"""
|
||||
inx
|
||||
lda ${(ESTACK_LO).toHex()},x
|
||||
lda $ESTACK_LO_HEX,x
|
||||
bne $label
|
||||
lda ${(ESTACK_HI).toHex()},x
|
||||
lda $ESTACK_HI_HEX,x
|
||||
bne $label
|
||||
"""
|
||||
}
|
||||
@@ -453,31 +459,31 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
||||
Opcode.CAST_B_TO_F -> " jsr c64flt.stack_b2float"
|
||||
Opcode.CAST_UW_TO_F -> " jsr c64flt.stack_uw2float"
|
||||
Opcode.CAST_W_TO_F -> " jsr c64flt.stack_w2float"
|
||||
Opcode.CAST_F_TO_UB -> " jsr c64flt.stack_float2uw"
|
||||
Opcode.CAST_F_TO_B -> " jsr c64flt.stack_float2w"
|
||||
Opcode.CAST_F_TO_UB -> " jsr c64flt.stack_float2ub"
|
||||
Opcode.CAST_F_TO_B -> " jsr c64flt.stack_float2b"
|
||||
Opcode.CAST_F_TO_UW -> " jsr c64flt.stack_float2uw"
|
||||
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_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.MSB -> " lda ${(ESTACK_HI+1).toHex()},x | sta ${(ESTACK_LO+1).toHex()},x"
|
||||
Opcode.MKWORD -> " inx | lda ${ESTACK_LO.toHex()},x | sta ${(ESTACK_HI+1).toHex()},x "
|
||||
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_PLUS1_HEX,x | ${signExtendA("$ESTACK_HI_PLUS1_HEX,x")}" // sign extend the lsb
|
||||
Opcode.MSB -> " lda $ESTACK_HI_PLUS1_HEX,x | sta $ESTACK_LO_PLUS1_HEX,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)
|
||||
"""
|
||||
lda ${(ESTACK_LO + 2).toHex()},x
|
||||
lda $ESTACK_LO_PLUS2_HEX,x
|
||||
clc
|
||||
adc ${(ESTACK_LO + 1).toHex()},x
|
||||
adc $ESTACK_LO_PLUS1_HEX,x
|
||||
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)
|
||||
"""
|
||||
lda ${(ESTACK_LO + 2).toHex()},x
|
||||
lda $ESTACK_LO_PLUS2_HEX,x
|
||||
sec
|
||||
sbc ${(ESTACK_LO + 1).toHex()},x
|
||||
sbc $ESTACK_LO_PLUS1_HEX,x
|
||||
inx
|
||||
sta ${(ESTACK_LO + 1).toHex()},x
|
||||
sta $ESTACK_LO_PLUS1_HEX,x
|
||||
"""
|
||||
}
|
||||
Opcode.ADD_W, Opcode.ADD_UW -> " jsr prog8_lib.add_w"
|
||||
@@ -507,27 +513,27 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
||||
Opcode.BITOR_WORD -> " jsr prog8_lib.bitor_w"
|
||||
Opcode.BITXOR_WORD -> " jsr prog8_lib.bitxor_w"
|
||||
|
||||
Opcode.REMAINDER_UB -> " jsr prog8_lib.remainder_ub"
|
||||
Opcode.REMAINDER_UW -> " jsr prog8_lib.remainder_uw"
|
||||
Opcode.REMAINDER_UB -> " jsr prog8_lib.remainder_ub"
|
||||
Opcode.REMAINDER_UW -> " jsr prog8_lib.remainder_uw"
|
||||
|
||||
Opcode.GREATER_B -> " jsr prog8_lib.greater_b"
|
||||
Opcode.GREATER_UB -> " jsr prog8_lib.greater_ub"
|
||||
Opcode.GREATER_W -> " jsr prog8_lib.greater_w"
|
||||
Opcode.GREATER_UW -> " jsr prog8_lib.greater_uw"
|
||||
Opcode.GREATER_F -> " jsr c64flt.greater_f"
|
||||
Opcode.GREATER_B -> " jsr prog8_lib.greater_b"
|
||||
Opcode.GREATER_UB -> " jsr prog8_lib.greater_ub"
|
||||
Opcode.GREATER_W -> " jsr prog8_lib.greater_w"
|
||||
Opcode.GREATER_UW -> " jsr prog8_lib.greater_uw"
|
||||
Opcode.GREATER_F -> " jsr c64flt.greater_f"
|
||||
|
||||
Opcode.GREATEREQ_B -> " jsr prog8_lib.greatereq_b"
|
||||
Opcode.GREATEREQ_UB -> " jsr prog8_lib.greatereq_ub"
|
||||
Opcode.GREATEREQ_W -> " jsr prog8_lib.greatereq_w"
|
||||
Opcode.GREATEREQ_UW -> " jsr prog8_lib.greatereq_uw"
|
||||
Opcode.GREATEREQ_F -> " jsr c64flt.greatereq_f"
|
||||
Opcode.GREATEREQ_B -> " jsr prog8_lib.greatereq_b"
|
||||
Opcode.GREATEREQ_UB -> " jsr prog8_lib.greatereq_ub"
|
||||
Opcode.GREATEREQ_W -> " jsr prog8_lib.greatereq_w"
|
||||
Opcode.GREATEREQ_UW -> " jsr prog8_lib.greatereq_uw"
|
||||
Opcode.GREATEREQ_F -> " jsr c64flt.greatereq_f"
|
||||
|
||||
Opcode.EQUAL_BYTE -> " jsr prog8_lib.equal_b"
|
||||
Opcode.EQUAL_WORD -> " jsr prog8_lib.equal_w"
|
||||
Opcode.EQUAL_F -> " jsr c64flt.equal_f"
|
||||
Opcode.EQUAL_BYTE -> " jsr prog8_lib.equal_b"
|
||||
Opcode.EQUAL_WORD -> " jsr prog8_lib.equal_w"
|
||||
Opcode.EQUAL_F -> " jsr c64flt.equal_f"
|
||||
Opcode.NOTEQUAL_BYTE -> " jsr prog8_lib.notequal_b"
|
||||
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_B -> " jsr prog8_lib.less_b"
|
||||
@@ -541,12 +547,12 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
|
||||
Opcode.LESSEQ_W -> " jsr prog8_lib.lesseq_w"
|
||||
Opcode.LESSEQ_F -> " jsr c64flt.lesseq_f"
|
||||
|
||||
Opcode.SHIFTEDL_BYTE -> " asl ${(ESTACK_LO+1).toHex()},x"
|
||||
Opcode.SHIFTEDL_WORD -> " asl ${(ESTACK_LO+1).toHex()},x | rol ${(ESTACK_HI+1).toHex()},x"
|
||||
Opcode.SHIFTEDR_SBYTE -> " lda ${(ESTACK_LO+1).toHex()},x | asl a | ror ${(ESTACK_LO+1).toHex()},x"
|
||||
Opcode.SHIFTEDR_UBYTE -> " lsr ${(ESTACK_LO+1).toHex()},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_UWORD -> " lsr ${(ESTACK_HI+1).toHex()},x | ror ${(ESTACK_LO+1).toHex()},x"
|
||||
Opcode.SHIFTEDL_BYTE -> " asl $ESTACK_LO_PLUS1_HEX,x"
|
||||
Opcode.SHIFTEDL_WORD -> " asl $ESTACK_LO_PLUS1_HEX,x | rol $ESTACK_HI_PLUS1_HEX,x"
|
||||
Opcode.SHIFTEDR_SBYTE -> " lda $ESTACK_LO_PLUS1_HEX,x | asl a | ror $ESTACK_LO_PLUS1_HEX,x"
|
||||
Opcode.SHIFTEDR_UBYTE -> " lsr $ESTACK_LO_PLUS1_HEX,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_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x"
|
||||
|
||||
else -> null
|
||||
}
|
||||
File renamed without changes.
+12
-9
@@ -4,10 +4,13 @@ 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 prog8.compiler.intermediate.Instruction
|
||||
import prog8.compiler.intermediate.LabelInstr
|
||||
import prog8.compiler.intermediate.Opcode
|
||||
import prog8.compiler.intermediate.opcodesWithVarArgument
|
||||
import prog8.vm.RuntimeValue
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import java.util.regex.Pattern
|
||||
@@ -226,18 +229,18 @@ class Program (val name: String,
|
||||
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())
|
||||
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, valueStr.substring(3).substringBefore(' ').toShort(16))// TODO process ZP and struct info?
|
||||
DataType.BYTE -> RuntimeValue(DataType.BYTE, valueStr.substring(2).substringBefore(' ').toShort(16))// TODO process ZP and struct info?
|
||||
DataType.UWORD -> RuntimeValue(DataType.UWORD, valueStr.substring(3).substringBefore(' ').toInt(16))// TODO process ZP and struct info?
|
||||
DataType.WORD -> RuntimeValue(DataType.WORD, valueStr.substring(2).substringBefore(' ').toInt(16))// TODO process ZP and struct info?
|
||||
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, valueStr.substring(2).substringBefore(' ').toDouble())// TODO process ZP and struct info?
|
||||
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()
|
||||
val heapId = valueStr.substring(5).substringBefore(' ').toInt() // TODO process ZP and struct info?
|
||||
RuntimeValue(type, heapId = heapId)
|
||||
}
|
||||
}
|
||||
@@ -245,7 +248,7 @@ class Program (val name: String,
|
||||
if(!valueStr.startsWith("heap:"))
|
||||
throw VmExecutionException("invalid array value, should be a heap reference")
|
||||
else {
|
||||
val heapId = valueStr.substring(5).toInt()
|
||||
val heapId = valueStr.substring(5).substringBefore(' ').toInt() // TODO process ZP and struct info?
|
||||
RuntimeValue(type, heapId = heapId)
|
||||
}
|
||||
}
|
||||
+15
-12
@@ -1,19 +1,15 @@
|
||||
package prog8.vm.stackvm
|
||||
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.IterableDatatypes
|
||||
import prog8.ast.base.NumericDatatypes
|
||||
import prog8.ast.base.Register
|
||||
import prog8.ast.base.initvarsSubName
|
||||
import prog8.vm.astvm.BitmapScreenPanel
|
||||
import prog8.vm.astvm.Memory
|
||||
import prog8.vm.RuntimeValue
|
||||
import prog8.ast.base.*
|
||||
import prog8.compiler.HeapValues
|
||||
import prog8.compiler.IntegerOrAddressOf
|
||||
import prog8.compiler.intermediate.Instruction
|
||||
import prog8.compiler.intermediate.Opcode
|
||||
import prog8.compiler.target.c64.Petscii
|
||||
import prog8.compiler.toHex
|
||||
import prog8.vm.RuntimeValue
|
||||
import prog8.vm.astvm.BitmapScreenPanel
|
||||
import prog8.vm.astvm.Memory
|
||||
import java.io.File
|
||||
import java.io.PrintStream
|
||||
import java.util.*
|
||||
@@ -1873,10 +1869,17 @@ class StackVm(private var traceOutputFile: String?) {
|
||||
Opcode.INLINE_ASSEMBLY -> throw VmExecutionException("stackVm doesn't support executing inline assembly code $ins")
|
||||
Opcode.INCLUDE_FILE -> throw VmExecutionException("stackVm doesn't support including a file $ins")
|
||||
Opcode.PUSH_ADDR_HEAPVAR -> {
|
||||
val heapId = variables.getValue(ins.callLabel!!).heapId!!
|
||||
if(heapId<0)
|
||||
throw VmExecutionException("expected variable on heap")
|
||||
evalstack.push(RuntimeValue(DataType.UWORD, heapId)) // push the "address" of the string or array variable (this is taken care of properly in the assembly code generator)
|
||||
val variable = variables.getValue(ins.callLabel!!)
|
||||
if(variable.heapId!=null) {
|
||||
val heapId = variable.heapId
|
||||
if (heapId < 0)
|
||||
throw VmExecutionException("expected variable on heap")
|
||||
evalstack.push(RuntimeValue(DataType.UWORD, heapId)) // push the "address" of the string or array variable (this is taken care of properly in the assembly code generator)
|
||||
} else {
|
||||
// hack: return hash of the name, so we have at least *a* value...
|
||||
val addr = ins.callLabel.hashCode() and 65535
|
||||
evalstack.push(RuntimeValue(DataType.UWORD, addr))
|
||||
}
|
||||
}
|
||||
Opcode.CAST_UB_TO_B -> typecast(DataType.UBYTE, DataType.BYTE)
|
||||
Opcode.CAST_W_TO_B -> typecast(DataType.WORD, DataType.BYTE)
|
||||
@@ -8,10 +8,10 @@ import prog8.ast.base.ByteDatatypes
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.IterableDatatypes
|
||||
import prog8.ast.base.WordDatatypes
|
||||
import prog8.vm.RuntimeValue
|
||||
import prog8.compiler.HeapValues
|
||||
import prog8.compiler.intermediate.Instruction
|
||||
import prog8.compiler.intermediate.Opcode
|
||||
import prog8.vm.RuntimeValue
|
||||
import prog8.vm.stackvm.*
|
||||
import kotlin.test.*
|
||||
|
||||
@@ -17,33 +17,40 @@ which aims to provide many conveniences over raw assembly code (even when using
|
||||
- easier program understanding (because it's higher level, and way more compact)
|
||||
- modularity, symbol scoping, subroutines
|
||||
- subroutines have enforced input- and output parameter definitions
|
||||
- various data types other than just bytes (16-bit words, floats, strings, 16-bit register pairs)
|
||||
- various data types other than just bytes (16-bit words, floats, strings)
|
||||
- automatic variable allocations, automatic string variables and string sharing
|
||||
- constant folding in expressions (compile-time evaluation)
|
||||
- conditional branches
|
||||
- when statement to provide a 'jump table' alternative to if/elseif chains
|
||||
- structs to group together sets of variables and manipulate them at once
|
||||
- automatic type conversions
|
||||
- floating point operations (uses the C64 Basic ROM routines for this)
|
||||
- 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
|
||||
|
||||
|
||||
Rapid edit-compile-run-debug cycle:
|
||||
|
||||
- use modern PC to work on
|
||||
- quick compilation times (less than 1 second)
|
||||
- quick compilation times (around a second, and less than 0.1 seconds when using the continuous compilation mode)
|
||||
- option to automatically run the program in the Vice emulator
|
||||
- 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
|
||||
|
||||
- the compiler includes a 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.
|
||||
This allows for very quick experimentation and debugging
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
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 +72,7 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
||||
%import c64utils
|
||||
%zeropage basicsafe
|
||||
|
||||
~ main {
|
||||
main {
|
||||
|
||||
ubyte[256] sieve
|
||||
ubyte candidate_prime = 2
|
||||
|
||||
@@ -76,17 +76,6 @@ artifacts {
|
||||
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/
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
%option enable_floats
|
||||
|
||||
|
||||
~ c64flt {
|
||||
c64flt {
|
||||
; ---- this block contains C-64 floating point related functions ----
|
||||
|
||||
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)
|
||||
; (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)
|
||||
; (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.
|
||||
%asm {{
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
lda #<print_f_value
|
||||
ldy #>print_f_value
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr MOVFM ; load float into fac1
|
||||
jsr FOUT ; fac1 to string in A/Y
|
||||
jsr c64.STROUT ; print string in A/Y
|
||||
@@ -310,7 +310,7 @@ stack_uw2float .proc
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
stack_float2w .proc
|
||||
stack_float2w .proc ; also used for float2b
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr AYINT
|
||||
@@ -323,7 +323,7 @@ stack_float2w .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
stack_float2uw .proc
|
||||
stack_float2uw .proc ; also used for float2ub
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr GETADR
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
; indent format: TABS, size=8
|
||||
|
||||
|
||||
~ c64 {
|
||||
c64 {
|
||||
const uword ESTACK_LO = $ce00 ; evaluation stack (lsb)
|
||||
const uword ESTACK_HI = $cf00 ; evaluation stack (msb)
|
||||
&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 STKEY = $91 ; various keyboard statuses (updated by IRQ)
|
||||
&ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
|
||||
|
||||
|
||||
&ubyte COLOR = $0286 ; cursor color
|
||||
&ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address)
|
||||
&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 RAMTAS () clobbers(A,X,Y) = $FF87 ; initialize RAM, tape buffer, screen
|
||||
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 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 MEMTOP (ubyte dir @ Pc, uword address @ XY) -> 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 MEMTOP (uword address @ XY, ubyte dir @ Pc) -> uword @ XY = $FF99 ; read/set top 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 SETTMO (ubyte timeout @ A) = $FFA2 ; set time-out flag for IEEE 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 UDTIM () clobbers(A,X) = $FFEA ; update the software clock
|
||||
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
|
||||
|
||||
; ---- end of C64 kernal routines ----
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
%import c64lib
|
||||
|
||||
|
||||
~ c64utils {
|
||||
c64utils {
|
||||
|
||||
const uword ESTACK_LO = $ce00
|
||||
const uword ESTACK_HI = $cf00
|
||||
@@ -461,7 +461,7 @@ _raster_irq_handler
|
||||
|
||||
|
||||
|
||||
~ c64scr {
|
||||
c64scr {
|
||||
; ---- this block contains (character) Screen and text I/O related functions ----
|
||||
|
||||
|
||||
@@ -821,7 +821,7 @@ asmsub print_b (byte value @ A) clobbers(A,Y) {
|
||||
}
|
||||
|
||||
|
||||
asmsub print_ubhex (ubyte prefix @ Pc, ubyte value @ A) clobbers(A,Y) {
|
||||
asmsub print_ubhex (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
|
||||
%asm {{
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
@@ -840,7 +840,7 @@ asmsub print_ubhex (ubyte prefix @ Pc, ubyte value @ A) clobbers(A,Y) {
|
||||
}
|
||||
|
||||
|
||||
asmsub print_ubbin (ubyte prefix @ Pc, ubyte value @ A) clobbers(A,Y) {
|
||||
asmsub print_ubbin (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
||||
%asm {{
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
@@ -862,7 +862,7 @@ asmsub print_ubbin (ubyte prefix @ Pc, ubyte value @ A) clobbers(A,Y) {
|
||||
}
|
||||
|
||||
|
||||
asmsub print_uwbin (ubyte prefix @ Pc, uword value @ AY) clobbers(A,Y) {
|
||||
asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
||||
%asm {{
|
||||
pha
|
||||
@@ -875,7 +875,7 @@ asmsub print_uwbin (ubyte prefix @ Pc, uword value @ AY) clobbers(A,Y) {
|
||||
}
|
||||
|
||||
|
||||
asmsub print_uwhex (ubyte prefix @ Pc, uword value @ AY) clobbers(A,Y) {
|
||||
asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
; ---- print the uword in A/Y in hexadecimal form (4 digits)
|
||||
; (if Carry is set, a radix prefix '$' is printed as well)
|
||||
%asm {{
|
||||
@@ -1063,7 +1063,7 @@ _mod lda $ffff ; modified
|
||||
sub setcc (ubyte column, ubyte row, ubyte char, ubyte color) {
|
||||
; ---- set char+color at the given position on the screen
|
||||
%asm {{
|
||||
lda setcc_row
|
||||
lda row
|
||||
asl a
|
||||
tay
|
||||
lda setchr._screenrows+1,y
|
||||
@@ -1072,15 +1072,15 @@ sub setcc (ubyte column, ubyte row, ubyte char, ubyte color) {
|
||||
sta _colormod+2
|
||||
lda setchr._screenrows,y
|
||||
clc
|
||||
adc setcc_column
|
||||
adc column
|
||||
sta _charmod+1
|
||||
sta _colormod+1
|
||||
bcc +
|
||||
inc _charmod+2
|
||||
inc _colormod+2
|
||||
+ lda setcc_char
|
||||
+ lda char
|
||||
_charmod sta $ffff ; modified
|
||||
lda setcc_color
|
||||
lda color
|
||||
_colormod sta $ffff ; modified
|
||||
rts
|
||||
}}
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
|
||||
%import c64lib
|
||||
|
||||
~ math {
|
||||
math {
|
||||
%asminclude "library:math.asm", ""
|
||||
}
|
||||
@@ -35,6 +35,17 @@ init_system .proc
|
||||
rts
|
||||
.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 ubyte in A to the uword in c64.SCRATCH_ZPWORD1
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
|
||||
%import c64lib
|
||||
|
||||
~ prog8_lib {
|
||||
prog8_lib {
|
||||
%asminclude "library:prog8lib.asm", ""
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
1.10
|
||||
1.50
|
||||
@@ -1,21 +1,20 @@
|
||||
package prog8
|
||||
|
||||
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.stackvm.stackVmMain
|
||||
import prog8.compiler.*
|
||||
import java.nio.file.FileSystems
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.nio.file.StandardWatchEventKinds
|
||||
import java.util.*
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
|
||||
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")
|
||||
|
||||
if (args.isEmpty())
|
||||
@@ -33,18 +32,16 @@ internal fun printSoftwareHeader(what: String) {
|
||||
private fun compileMain(args: Array<String>) {
|
||||
var emulatorToStart = ""
|
||||
var moduleFile = ""
|
||||
var writeVmCode = false
|
||||
var writeAssembly = true
|
||||
var optimize = true
|
||||
var optimizeInlining = true
|
||||
var launchAstVm = false
|
||||
var watchMode = false
|
||||
for (arg in args) {
|
||||
if(arg=="-emu")
|
||||
emulatorToStart = "x64"
|
||||
else if(arg=="-emu2")
|
||||
emulatorToStart = "x64sc"
|
||||
else if(arg=="-writevm")
|
||||
writeVmCode = true
|
||||
else if(arg=="-noasm")
|
||||
writeAssembly = false
|
||||
else if(arg=="-noopt")
|
||||
@@ -53,34 +50,78 @@ private fun compileMain(args: Array<String>) {
|
||||
optimizeInlining = false
|
||||
else if(arg=="-avm")
|
||||
launchAstVm = true
|
||||
else if(arg=="-watch")
|
||||
watchMode = true
|
||||
else if(!arg.startsWith("-"))
|
||||
moduleFile = arg
|
||||
else
|
||||
usage()
|
||||
}
|
||||
if(moduleFile.isBlank())
|
||||
usage()
|
||||
|
||||
val filepath = Paths.get(moduleFile).normalize()
|
||||
if(watchMode) {
|
||||
if(moduleFile.isBlank())
|
||||
usage()
|
||||
|
||||
val (programAst, programName) = compileProgram(filepath, optimize, optimizeInlining,
|
||||
!launchAstVm, writeVmCode, writeAssembly)
|
||||
val watchservice = FileSystems.getDefault().newWatchService()
|
||||
|
||||
if(launchAstVm) {
|
||||
println("\nLaunching AST-based vm...")
|
||||
val vm = AstVm(programAst)
|
||||
vm.run()
|
||||
}
|
||||
while(true) {
|
||||
val filepath = Paths.get(moduleFile).normalize()
|
||||
println("Continuous watch mode active. Main module: $filepath")
|
||||
|
||||
if(emulatorToStart.isNotEmpty()) {
|
||||
if(programName==null)
|
||||
println("\nCan't start emulator because no program was assembled.")
|
||||
else {
|
||||
println("\nStarting C-64 emulator $emulatorToStart...")
|
||||
val cmdline = listOf(emulatorToStart, "-silent", "-moncommands", "$programName.vice-mon-list",
|
||||
"-autostartprgmode", "1", "-autostart-warp", "-autostart", programName + ".prg")
|
||||
val process = ProcessBuilder(cmdline).inheritIO().start()
|
||||
process.waitFor()
|
||||
try {
|
||||
val compilationResult = compileProgram(filepath, optimize, optimizeInlining, writeAssembly)
|
||||
println("Imported files (now watching:)")
|
||||
for (importedFile in compilationResult.importedFiles) {
|
||||
print(" ")
|
||||
println(importedFile)
|
||||
importedFile.parent.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY)
|
||||
}
|
||||
println("${Date()}: 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 {
|
||||
if(moduleFile.isBlank())
|
||||
usage()
|
||||
|
||||
val filepath = Paths.get(moduleFile).normalize()
|
||||
val compilationResult: CompilationResult
|
||||
|
||||
try {
|
||||
compilationResult = compileProgram(filepath, optimize, optimizeInlining, writeAssembly)
|
||||
if(!compilationResult.success)
|
||||
exitProcess(1)
|
||||
} catch (x: ParsingFailedError) {
|
||||
exitProcess(1)
|
||||
} catch (x: AstException) {
|
||||
exitProcess(1)
|
||||
}
|
||||
|
||||
if (launchAstVm) {
|
||||
println("\nLaunching AST-based vm...")
|
||||
val vm = AstVm(compilationResult.programAst)
|
||||
vm.run()
|
||||
}
|
||||
|
||||
if (emulatorToStart.isNotEmpty()) {
|
||||
if (compilationResult.programName.isEmpty())
|
||||
println("\nCan't start emulator because no program was assembled.")
|
||||
else {
|
||||
println("\nStarting C-64 emulator $emulatorToStart...")
|
||||
val cmdline = listOf(emulatorToStart, "-silent", "-moncommands", "${compilationResult.programName}.vice-mon-list",
|
||||
"-autostartprgmode", "1", "-autostart-warp", "-autostart", compilationResult.programName + ".prg")
|
||||
val process = ProcessBuilder(cmdline).inheritIO().start()
|
||||
process.waitFor()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,14 +129,13 @@ private fun compileMain(args: Array<String>) {
|
||||
|
||||
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(" [-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(" [-avm] launch the prog8 ast-based virtual machine after compilation")
|
||||
System.err.println(" [-watch] continuous compilation mode (watches for file changes)")
|
||||
System.err.println(" modulefile main module file to compile")
|
||||
exitProcess(1)
|
||||
}
|
||||
+81
-51
@@ -1,22 +1,22 @@
|
||||
package prog8.compiler
|
||||
package prog8.ast
|
||||
|
||||
import prog8.ast.antlr.escape
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.IStatement
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.NumericDatatypes
|
||||
import prog8.ast.base.StringDatatypes
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.processing.IAstVisitor
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.toHex
|
||||
|
||||
class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
|
||||
var scopelevel = 0
|
||||
class AstToSourceCode(val output: (text: String) -> Unit, val program: Program): IAstVisitor {
|
||||
private var scopelevel = 0
|
||||
|
||||
fun indent(s: String) = " ".repeat(scopelevel) + s
|
||||
fun outputln(text: String) = output(text + "\n")
|
||||
fun outputlni(s: Any) = outputln(indent(s.toString()))
|
||||
fun outputi(s: Any) = output(indent(s.toString()))
|
||||
private fun indent(s: String) = " ".repeat(scopelevel) + s
|
||||
private fun outputln(text: String) = output(text + "\n")
|
||||
private fun outputlni(s: Any) = outputln(indent(s.toString()))
|
||||
private fun outputi(s: Any) = output(indent(s.toString()))
|
||||
|
||||
override fun visit(program: Program) {
|
||||
outputln("============= PROGRAM ${program.name} (FROM AST) ===============")
|
||||
@@ -76,7 +76,7 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
|
||||
output("\n")
|
||||
}
|
||||
|
||||
fun datatypeString(dt: DataType): String {
|
||||
private fun datatypeString(dt: DataType): String {
|
||||
return when(dt) {
|
||||
in NumericDatatypes -> 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_W -> "word["
|
||||
DataType.ARRAY_F -> "float["
|
||||
else -> "?????"
|
||||
DataType.STRUCT -> "" // the name of the struct is enough
|
||||
else -> "?????2"
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(decl: VarDecl) {
|
||||
if(decl.autoGenerated) {
|
||||
// skip autogenerated vardecl
|
||||
return
|
||||
override fun visit(structDecl: StructDecl) {
|
||||
outputln("struct ${structDecl.name} {")
|
||||
scopelevel++
|
||||
for(decl in structDecl.statements) {
|
||||
outputi("")
|
||||
decl.accept(this)
|
||||
output("\n")
|
||||
}
|
||||
scopelevel--
|
||||
outputlni("}")
|
||||
}
|
||||
|
||||
override fun visit(decl: VarDecl) {
|
||||
when(decl.type) {
|
||||
VarDeclType.VAR -> {}
|
||||
VarDeclType.CONST -> output("const ")
|
||||
VarDeclType.MEMORY -> output("&")
|
||||
}
|
||||
output(decl.struct?.name ?: "")
|
||||
output(datatypeString(decl.datatype))
|
||||
if(decl.arraysize!=null) {
|
||||
decl.arraysize!!.index.accept(this)
|
||||
@@ -107,7 +116,7 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
|
||||
if(decl.isArray)
|
||||
output("]")
|
||||
|
||||
if(decl.zeropage)
|
||||
if(decl.zeropage == ZeropageWish.REQUIRE_ZEROPAGE || decl.zeropage==ZeropageWish.PREFER_ZEROPAGE)
|
||||
output(" @zp")
|
||||
output(" ${decl.name} ")
|
||||
if(decl.value!=null) {
|
||||
@@ -126,7 +135,7 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
|
||||
true==param.second.stack -> "stack"
|
||||
param.second.registerOrPair!=null -> param.second.registerOrPair.toString()
|
||||
param.second.statusflag!=null -> param.second.statusflag.toString()
|
||||
else -> "?????"
|
||||
else -> "?????1"
|
||||
}
|
||||
output("${datatypeString(param.first.type)} ${param.first.name} @$reg")
|
||||
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) {
|
||||
if(stmt is VarDecl && stmt.autoGenerated)
|
||||
continue // skip autogenerated decls
|
||||
if(stmt is VarDecl && stmt.autogeneratedDontRemove)
|
||||
continue // skip autogenerated decls (to avoid generating a newline)
|
||||
outputi("")
|
||||
stmt.accept(this)
|
||||
output("\n")
|
||||
@@ -243,36 +252,51 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
|
||||
output("${label.name}:")
|
||||
}
|
||||
|
||||
override fun visit(literalValue: LiteralValue) {
|
||||
override fun visit(numLiteral: NumericLiteralValue) {
|
||||
output(numLiteral.number.toString())
|
||||
}
|
||||
|
||||
override fun visit(refLiteral: ReferenceLiteralValue) {
|
||||
when {
|
||||
literalValue.isNumeric -> output(literalValue.asNumericValue.toString())
|
||||
literalValue.isString -> output("\"${escape(literalValue.strvalue!!)}\"")
|
||||
literalValue.isArray -> {
|
||||
if(literalValue.arrayvalue!=null) {
|
||||
var counter = 0
|
||||
output("[")
|
||||
scopelevel++
|
||||
for (v in literalValue.arrayvalue) {
|
||||
v.accept(this)
|
||||
if (v !== literalValue.arrayvalue.last())
|
||||
output(", ")
|
||||
counter++
|
||||
if(counter > 16) {
|
||||
outputln("")
|
||||
outputi("")
|
||||
counter=0
|
||||
}
|
||||
}
|
||||
scopelevel--
|
||||
output("]")
|
||||
refLiteral.isString -> output("\"${escape(refLiteral.str!!)}\"")
|
||||
refLiteral.isArray -> {
|
||||
if(refLiteral.array!=null) {
|
||||
outputListMembers(refLiteral.array.asSequence(), '[', ']')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun outputListMembers(array: Sequence<Expression>, openchar: Char, closechar: Char) {
|
||||
var counter = 0
|
||||
output(openchar.toString())
|
||||
scopelevel++
|
||||
for (v in array) {
|
||||
v.accept(this)
|
||||
if (v !== array.last())
|
||||
output(", ")
|
||||
counter++
|
||||
if (counter > 16) {
|
||||
outputln("")
|
||||
outputi("")
|
||||
counter = 0
|
||||
}
|
||||
}
|
||||
scopelevel--
|
||||
output(closechar.toString())
|
||||
}
|
||||
|
||||
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)
|
||||
if(assignment.aug_op!=null)
|
||||
if (assignment.aug_op != null)
|
||||
output(" ${assignment.aug_op} ")
|
||||
else
|
||||
output(" = ")
|
||||
@@ -296,7 +320,7 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
|
||||
output("for ")
|
||||
if(forLoop.decltype!=null) {
|
||||
output(datatypeString(forLoop.decltype))
|
||||
if (forLoop.zeropage)
|
||||
if (forLoop.zeropage==ZeropageWish.REQUIRE_ZEROPAGE || forLoop.zeropage==ZeropageWish.PREFER_ZEROPAGE)
|
||||
output(" @zp ")
|
||||
else
|
||||
output(" ")
|
||||
@@ -319,7 +343,7 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
|
||||
}
|
||||
|
||||
override fun visit(repeatLoop: RepeatLoop) {
|
||||
outputln("repeat ")
|
||||
output("repeat ")
|
||||
repeatLoop.body.accept(this)
|
||||
output(" until ")
|
||||
repeatLoop.untilCondition.accept(this)
|
||||
@@ -403,13 +427,14 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
|
||||
}
|
||||
|
||||
override fun visit(whenChoice: WhenChoice) {
|
||||
if(whenChoice.values==null)
|
||||
val choiceValues = whenChoice.values
|
||||
if(choiceValues==null)
|
||||
outputi("else -> ")
|
||||
else {
|
||||
outputi("")
|
||||
for(value in whenChoice.values) {
|
||||
for(value in choiceValues) {
|
||||
value.accept(this)
|
||||
if(value !== whenChoice.values.last())
|
||||
if(value !== choiceValues.last())
|
||||
output(",")
|
||||
}
|
||||
output(" -> ")
|
||||
@@ -420,7 +445,12 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
|
||||
whenChoice.statements.accept(this)
|
||||
outputln("")
|
||||
}
|
||||
|
||||
override fun visit(structLv: StructLiteralValue) {
|
||||
outputListMembers(structLv.values.asSequence(), '{', '}')
|
||||
}
|
||||
|
||||
override fun visit(nopStatement: NopStatement) {
|
||||
output("; NOP")
|
||||
output("; NOP @ ${nopStatement.position} $nopStatement")
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,178 @@
|
||||
package prog8.ast
|
||||
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.base.NameError
|
||||
import prog8.ast.base.ParentSentinel
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.statements.Block
|
||||
import prog8.ast.statements.Label
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.HeapValues
|
||||
import prog8.functions.BuiltinFunctions
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*********** Everything starts from here, the Program; zero or more modules *************/
|
||||
|
||||
class Program(val name: String, val modules: MutableList<Module>) {
|
||||
val namespace = GlobalNamespace(modules)
|
||||
val heap = HeapValues()
|
||||
|
||||
val loadAddress: Int
|
||||
val definedLoadAddress: Int
|
||||
get() = modules.first().loadAddress
|
||||
|
||||
var actualLoadAddress: Int = 0
|
||||
|
||||
fun entrypoint(): Subroutine? {
|
||||
val mainBlocks = modules.flatMap { it.statements }.filter { b -> b is Block && b.name=="main" }.map { it as Block }
|
||||
if(mainBlocks.size > 1)
|
||||
@@ -32,10 +183,12 @@ class Program(val name: String, val modules: MutableList<Module>) {
|
||||
mainBlocks[0].subScopes()["start"] as Subroutine?
|
||||
}
|
||||
}
|
||||
|
||||
fun allBlocks(): List<Block> = modules.flatMap { it.statements.filterIsInstance<Block>() }
|
||||
}
|
||||
|
||||
class Module(override val name: String,
|
||||
override var statements: MutableList<IStatement>,
|
||||
override var statements: MutableList<Statement>,
|
||||
override val position: Position,
|
||||
val isLibraryModule: Boolean,
|
||||
val source: Path) : Node, INameScope {
|
||||
@@ -59,14 +212,14 @@ class Module(override val name: String,
|
||||
class GlobalNamespace(val modules: List<Module>): Node, INameScope {
|
||||
override val name = "<<<global>>>"
|
||||
override val position = Position("<<<global>>>", 0, 0, 0)
|
||||
override val statements = mutableListOf<IStatement>()
|
||||
override val statements = mutableListOf<Statement>()
|
||||
override var parent: Node = ParentSentinel
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
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) {
|
||||
// builtin functions always exist, return a dummy localContext for them
|
||||
val builtinPlaceholder = Label("builtin::${scopedName.last()}", localContext.position)
|
||||
@@ -74,6 +227,20 @@ class GlobalNamespace(val modules: List<Module>): Node, INameScope {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val stmt = localContext.definingModule().lookup(scopedName, localContext)
|
||||
return when (stmt) {
|
||||
is Label, is VarDecl, is Block, is Subroutine -> stmt
|
||||
@@ -86,7 +253,11 @@ class GlobalNamespace(val modules: List<Module>): Node, INameScope {
|
||||
object BuiltinFunctionScopePlaceholder : INameScope {
|
||||
override val name = "<<builtin-functions-scope-placeholder>>"
|
||||
override val position = Position("<<placeholder>>", 0, 0, 0)
|
||||
override var statements = mutableListOf<IStatement>()
|
||||
override var statements = mutableListOf<Statement>()
|
||||
override var parent: Node = ParentSentinel
|
||||
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.ParserRuleContext
|
||||
import org.antlr.v4.runtime.tree.TerminalNode
|
||||
import prog8.ast.*
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
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.parser.CustomLexer
|
||||
import prog8.parser.prog8Parser
|
||||
import java.io.CharConversionException
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
/***************** 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()
|
||||
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())
|
||||
|
||||
|
||||
private fun prog8Parser.Statement_blockContext.toAst(): MutableList<IStatement> =
|
||||
private fun prog8Parser.Statement_blockContext.toAst(): MutableList<Statement> =
|
||||
statement().asSequence().map { it.toAst() }.toMutableList()
|
||||
|
||||
|
||||
private fun prog8Parser.StatementContext.toAst() : IStatement {
|
||||
vardecl()?.let {
|
||||
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())
|
||||
}
|
||||
private fun prog8Parser.StatementContext.toAst() : Statement {
|
||||
vardecl()?.let { return it.toAst() }
|
||||
|
||||
varinitializer()?.let {
|
||||
val vd = it.vardecl()
|
||||
return VarDecl(VarDeclType.VAR,
|
||||
vd.datatype().toAst(),
|
||||
vd.ZEROPAGE() != null,
|
||||
return VarDecl(
|
||||
VarDeclType.VAR,
|
||||
vd.datatype()?.toAst() ?: DataType.STRUCT,
|
||||
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||
vd.arrayindex()?.toAst(),
|
||||
vd.identifier().text,
|
||||
vd.varname.text,
|
||||
null,
|
||||
it.expression().toAst(),
|
||||
vd.ARRAYSIG() != null || vd.arrayindex() != null,
|
||||
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 {
|
||||
val cvarinit = it.varinitializer()
|
||||
val vd = cvarinit.vardecl()
|
||||
return VarDecl(VarDeclType.CONST,
|
||||
vd.datatype().toAst(),
|
||||
vd.ZEROPAGE() != null,
|
||||
return VarDecl(
|
||||
VarDeclType.CONST,
|
||||
vd.datatype()?.toAst() ?: DataType.STRUCT,
|
||||
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||
vd.arrayindex()?.toAst(),
|
||||
vd.identifier().text,
|
||||
vd.varname.text,
|
||||
null,
|
||||
cvarinit.expression().toAst(),
|
||||
vd.ARRAYSIG() != null || vd.arrayindex() != null,
|
||||
false,
|
||||
cvarinit.toPosition())
|
||||
cvarinit.toPosition()
|
||||
)
|
||||
}
|
||||
|
||||
memoryvardecl()?.let {
|
||||
val mvarinit = it.varinitializer()
|
||||
val vd = mvarinit.vardecl()
|
||||
return VarDecl(VarDeclType.MEMORY,
|
||||
vd.datatype().toAst(),
|
||||
vd.ZEROPAGE() != null,
|
||||
return VarDecl(
|
||||
VarDeclType.MEMORY,
|
||||
vd.datatype()?.toAst() ?: DataType.STRUCT,
|
||||
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||
vd.arrayindex()?.toAst(),
|
||||
vd.identifier().text,
|
||||
vd.varname.text,
|
||||
null,
|
||||
mvarinit.expression().toAst(),
|
||||
vd.ARRAYSIG() != null || vd.arrayindex() != null,
|
||||
false,
|
||||
mvarinit.toPosition())
|
||||
mvarinit.toPosition()
|
||||
)
|
||||
}
|
||||
|
||||
assignment()?.let {
|
||||
@@ -175,10 +205,16 @@ private fun prog8Parser.StatementContext.toAst() : IStatement {
|
||||
val whenstmt = whenstmt()?.toAst()
|
||||
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")
|
||||
}
|
||||
|
||||
private fun prog8Parser.AsmsubroutineContext.toAst(): IStatement {
|
||||
private fun prog8Parser.AsmsubroutineContext.toAst(): Statement {
|
||||
val name = identifier().text
|
||||
val address = asmsub_address()?.address?.toAst()?.number?.toInt()
|
||||
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>
|
||||
= asmsub_param().map {
|
||||
AsmSubroutineParameter(it.vardecl().identifier().text, it.vardecl().datatype().toAst(),
|
||||
it.registerorpair()?.toAst(), it.statusregister()?.toAst(), !it.stack?.text.isNullOrEmpty(), toPosition())
|
||||
val vardecl = it.vardecl()
|
||||
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.Functioncall_stmtContext.toAst(): IStatement {
|
||||
private fun prog8Parser.Functioncall_stmtContext.toAst(): Statement {
|
||||
val location = scoped_identifier().toAst()
|
||||
return if(expression_list() == null)
|
||||
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())
|
||||
|
||||
|
||||
@@ -281,7 +321,8 @@ private fun prog8Parser.Sub_return_partContext.toAst(): List<DataType> {
|
||||
|
||||
private fun prog8Parser.Sub_paramsContext.toAst(): List<SubroutineParameter> =
|
||||
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,30 +409,30 @@ private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
|
||||
}
|
||||
|
||||
|
||||
private fun prog8Parser.ExpressionContext.toAst() : IExpression {
|
||||
private fun prog8Parser.ExpressionContext.toAst() : Expression {
|
||||
|
||||
val litval = literalvalue()
|
||||
if(litval!=null) {
|
||||
val booleanlit = litval.booleanliteral()?.toAst()
|
||||
return if(booleanlit!=null) {
|
||||
LiteralValue.fromBoolean(booleanlit, litval.toPosition())
|
||||
NumericLiteralValue.fromBoolean(booleanlit, litval.toPosition())
|
||||
}
|
||||
else {
|
||||
val intLit = litval.integerliteral()?.toAst()
|
||||
when {
|
||||
intLit!=null -> when(intLit.datatype) {
|
||||
DataType.UBYTE -> LiteralValue(DataType.UBYTE, bytevalue = intLit.number.toShort(), position = litval.toPosition())
|
||||
DataType.BYTE -> LiteralValue(DataType.BYTE, bytevalue = intLit.number.toShort(), position = litval.toPosition())
|
||||
DataType.UWORD -> LiteralValue(DataType.UWORD, wordvalue = intLit.number.toInt(), position = litval.toPosition())
|
||||
DataType.WORD -> LiteralValue(DataType.WORD, wordvalue = intLit.number.toInt(), position = litval.toPosition())
|
||||
DataType.FLOAT -> LiteralValue(DataType.FLOAT, floatvalue = intLit.number.toDouble(), position = litval.toPosition())
|
||||
DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, intLit.number.toShort(), litval.toPosition())
|
||||
DataType.BYTE -> NumericLiteralValue(DataType.BYTE, intLit.number.toShort(), litval.toPosition())
|
||||
DataType.UWORD -> NumericLiteralValue(DataType.UWORD, intLit.number.toInt(), litval.toPosition())
|
||||
DataType.WORD -> NumericLiteralValue(DataType.WORD, intLit.number.toInt(), litval.toPosition())
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, intLit.number.toDouble(), litval.toPosition())
|
||||
else -> throw FatalAstException("invalid datatype for numeric literal")
|
||||
}
|
||||
litval.floatliteral()!=null -> LiteralValue(DataType.FLOAT, floatvalue = litval.floatliteral().toAst(), position = litval.toPosition())
|
||||
litval.stringliteral()!=null -> LiteralValue(DataType.STR, strvalue = unescape(litval.stringliteral().text, litval.toPosition()), position = litval.toPosition())
|
||||
litval.floatliteral()!=null -> NumericLiteralValue(DataType.FLOAT, litval.floatliteral().toAst(), litval.toPosition())
|
||||
litval.stringliteral()!=null -> ReferenceLiteralValue(DataType.STR, unescape(litval.stringliteral().text, litval.toPosition()), position = litval.toPosition())
|
||||
litval.charliteral()!=null -> {
|
||||
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) {
|
||||
throw SyntaxError(ce.message ?: ce.toString(), litval.toPosition())
|
||||
}
|
||||
@@ -399,8 +440,12 @@ private fun prog8Parser.ExpressionContext.toAst() : IExpression {
|
||||
litval.arrayliteral()!=null -> {
|
||||
val array = litval.arrayliteral()?.toAst()
|
||||
// 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.
|
||||
LiteralValue(DataType.ARRAY_UB, arrayvalue = array, position = litval.toPosition())
|
||||
// the ConstantFold takes care of that and converts the type if needed.
|
||||
ReferenceLiteralValue(DataType.ARRAY_UB, array = 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")
|
||||
}
|
||||
@@ -423,7 +468,7 @@ private fun prog8Parser.ExpressionContext.toAst() : IExpression {
|
||||
if(funcall!=null) return funcall
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
private fun prog8Parser.If_stmtContext.toAst(): IfStatement {
|
||||
val condition = expression().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())
|
||||
}
|
||||
|
||||
private fun prog8Parser.Else_partContext.toAst(): MutableList<IStatement> {
|
||||
private fun prog8Parser.Else_partContext.toAst(): MutableList<Statement> {
|
||||
return statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||
}
|
||||
|
||||
@@ -509,7 +553,7 @@ private fun prog8Parser.BranchconditionContext.toAst() = BranchCondition.valueOf
|
||||
private fun prog8Parser.ForloopContext.toAst(): ForLoop {
|
||||
val loopregister = register()?.toAst()
|
||||
val datatype = datatype()?.toAst()
|
||||
val zeropage = ZEROPAGE()!=null
|
||||
val zeropage = if(ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE
|
||||
val loopvar = identifier()?.toAst()
|
||||
val iterable = expression()!!.toAst()
|
||||
val scope =
|
||||
@@ -559,6 +603,21 @@ private fun prog8Parser.When_choiceContext.toAst(): WhenChoice {
|
||||
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 unescape(str: String, position: Position): String {
|
||||
@@ -584,3 +643,4 @@ internal fun unescape(str: String, position: Position): String {
|
||||
}
|
||||
return result.joinToString("")
|
||||
}
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
package prog8.ast.base
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.compiler.target.c64.MachineDefinition
|
||||
|
||||
/**************************** AST Data classes ****************************/
|
||||
|
||||
enum class DataType {
|
||||
UBYTE,
|
||||
BYTE,
|
||||
UWORD,
|
||||
WORD,
|
||||
FLOAT,
|
||||
STR,
|
||||
STR_S,
|
||||
ARRAY_UB,
|
||||
ARRAY_B,
|
||||
ARRAY_UW,
|
||||
ARRAY_W,
|
||||
ARRAY_F;
|
||||
UBYTE, // pass by value
|
||||
BYTE, // pass by value
|
||||
UWORD, // pass by value
|
||||
WORD, // pass by value
|
||||
FLOAT, // pass by value
|
||||
STR, // pass by reference
|
||||
STR_S, // pass by reference
|
||||
ARRAY_UB, // pass by reference
|
||||
ARRAY_B, // pass by reference
|
||||
ARRAY_UW, // pass by reference
|
||||
ARRAY_W, // pass by reference
|
||||
ARRAY_F, // pass by reference
|
||||
STRUCT; // pass by reference
|
||||
|
||||
/**
|
||||
* is the type assignable to the given other type?
|
||||
@@ -24,14 +26,14 @@ enum class DataType {
|
||||
infix fun isAssignableTo(targetType: DataType) =
|
||||
// what types are assignable to others without loss of precision?
|
||||
when(this) {
|
||||
UBYTE -> targetType == UBYTE || targetType == UWORD || targetType==WORD || targetType == FLOAT
|
||||
BYTE -> targetType == BYTE || targetType == UBYTE || targetType == UWORD || targetType==WORD || targetType == FLOAT
|
||||
UWORD -> targetType == UWORD || targetType == FLOAT
|
||||
WORD -> targetType == WORD || targetType==UWORD || targetType == FLOAT
|
||||
UBYTE -> targetType in setOf(UBYTE, WORD, UWORD, FLOAT)
|
||||
BYTE -> targetType in setOf(BYTE, WORD, FLOAT)
|
||||
UWORD -> targetType in setOf(UWORD, FLOAT)
|
||||
WORD -> targetType in setOf(WORD, FLOAT)
|
||||
FLOAT -> targetType == FLOAT
|
||||
STR -> targetType == STR || targetType==STR_S
|
||||
STR_S -> targetType == STR || targetType==STR_S
|
||||
in ArrayDatatypes -> targetType === this
|
||||
in ArrayDatatypes -> targetType == this
|
||||
else -> false
|
||||
}
|
||||
|
||||
@@ -51,6 +53,16 @@ enum class DataType {
|
||||
in WordDatatypes -> other in WordDatatypes
|
||||
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 {
|
||||
@@ -96,18 +108,22 @@ enum class VarDeclType {
|
||||
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 WordDatatypes = setOf(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 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 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(
|
||||
DataType.STR to DataType.UBYTE,
|
||||
DataType.STR_S to DataType.UBYTE,
|
||||
DataType.ARRAY_B to DataType.BYTE,
|
||||
DataType.ARRAY_UB to DataType.UBYTE,
|
||||
DataType.ARRAY_W to DataType.WORD,
|
||||
|
||||
@@ -1,25 +1,20 @@
|
||||
package prog8.ast.base
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.processing.*
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.ForLoop
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.optimizer.RemoveNops
|
||||
import prog8.compiler.target.c64.codegen2.AnonymousScopeVarsCleanup
|
||||
import prog8.optimizer.FlattenAnonymousScopesAndRemoveNops
|
||||
|
||||
|
||||
// the name of the subroutine that should be called for every block to initialize its variables
|
||||
internal const val initvarsSubName="prog8_init_vars"
|
||||
|
||||
|
||||
// prefix for literal values that are turned into a variable on the heap
|
||||
internal const val autoHeapValuePrefix = "auto_heap_value_"
|
||||
|
||||
|
||||
internal fun Program.removeNops() {
|
||||
val remover = RemoveNops()
|
||||
remover.visit(this)
|
||||
internal fun Program.removeNopsFlattenAnonScopes() {
|
||||
val flattener = FlattenAnonymousScopesAndRemoveNops()
|
||||
flattener.visit(this)
|
||||
}
|
||||
|
||||
|
||||
@@ -30,8 +25,15 @@ 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() {
|
||||
val initvalueCreator = VarInitValueAndAddressOfCreator(namespace)
|
||||
val initvalueCreator = VarInitValueAndAddressOfCreator(this)
|
||||
initvalueCreator.visit(this)
|
||||
|
||||
val checker = StatementReorderer(this)
|
||||
@@ -52,41 +54,12 @@ internal fun Program.checkRecursion() {
|
||||
|
||||
|
||||
internal fun Program.checkIdentifiers() {
|
||||
val checker = AstIdentifiersChecker(namespace)
|
||||
val checker = AstIdentifiersChecker(this)
|
||||
checker.visit(this)
|
||||
|
||||
if(modules.map {it.name}.toSet().size != modules.size) {
|
||||
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)
|
||||
}
|
||||
@@ -1,11 +1,17 @@
|
||||
package prog8.ast.expressions
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.antlr.escape
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.processing.IAstModifyingVisitor
|
||||
import prog8.ast.processing.IAstVisitor
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.statements.ArrayIndex
|
||||
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.compiler.HeapValues
|
||||
import prog8.compiler.IntegerOrAddressOf
|
||||
import prog8.compiler.target.c64.Petscii
|
||||
@@ -18,7 +24,38 @@ import kotlin.math.abs
|
||||
val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "==", "!=")
|
||||
|
||||
|
||||
class PrefixExpression(val operator: String, var expression: IExpression, override val position: Position) : IExpression {
|
||||
sealed class Expression: Node {
|
||||
abstract fun constValue(program: Program): NumericLiteralValue?
|
||||
abstract fun accept(visitor: IAstModifyingVisitor): Expression
|
||||
abstract fun accept(visitor: IAstVisitor)
|
||||
abstract fun referencesIdentifiers(vararg name: String): Boolean // todo: remove this here and move it into CallGraph instead
|
||||
abstract fun inferType(program: Program): DataType?
|
||||
|
||||
infix fun isSameAs(other: Expression): 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)
|
||||
}
|
||||
else -> return other==this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PrefixExpression(val operator: String, var expression: Expression, override val position: Position) : Expression() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
@@ -26,7 +63,7 @@ class PrefixExpression(val operator: String, var expression: IExpression, overri
|
||||
expression.linkParents(this)
|
||||
}
|
||||
|
||||
override fun constValue(program: Program): LiteralValue? = null
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name)
|
||||
@@ -37,7 +74,7 @@ class PrefixExpression(val operator: String, var expression: IExpression, overri
|
||||
}
|
||||
}
|
||||
|
||||
class BinaryExpression(var left: IExpression, var operator: String, var right: IExpression, override val position: Position) : IExpression {
|
||||
class BinaryExpression(var left: Expression, var operator: String, var right: Expression, override val position: Position) : Expression() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
@@ -51,7 +88,7 @@ class BinaryExpression(var left: IExpression, var operator: String, var right: I
|
||||
}
|
||||
|
||||
// binary expression should actually have been optimized away into a single value, before const value was requested...
|
||||
override fun constValue(program: Program): LiteralValue? = null
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
@@ -60,14 +97,13 @@ class BinaryExpression(var left: IExpression, var operator: String, var right: I
|
||||
val leftDt = left.inferType(program)
|
||||
val rightDt = right.inferType(program)
|
||||
return when (operator) {
|
||||
"+", "-", "*", "**", "%" -> if (leftDt == null || rightDt == null) null else {
|
||||
"+", "-", "*", "**", "%", "/" -> if (leftDt == null || rightDt == null) null else {
|
||||
try {
|
||||
arithmeticOpDt(leftDt, rightDt)
|
||||
commonDatatype(leftDt, rightDt, null, null).first
|
||||
} catch (x: FatalAstException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
"/" -> if (leftDt == null || rightDt == null) null else divisionOpDt(leftDt, rightDt)
|
||||
"&" -> leftDt
|
||||
"|" -> leftDt
|
||||
"^" -> leftDt
|
||||
@@ -81,138 +117,67 @@ class BinaryExpression(var left: IExpression, var operator: String, var right: I
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun divisionOpDt(leftDt: DataType, rightDt: DataType): DataType {
|
||||
fun commonDatatype(leftDt: DataType, rightDt: DataType,
|
||||
left: Expression?, right: Expression?): Pair<DataType, Expression?> {
|
||||
// byte + byte -> byte
|
||||
// byte + word -> word
|
||||
// word + byte -> word
|
||||
// word + word -> word
|
||||
// a combination with a float will be float (but give a warning about this!)
|
||||
|
||||
return when (leftDt) {
|
||||
DataType.UBYTE -> when (rightDt) {
|
||||
DataType.UBYTE, DataType.UWORD -> DataType.UBYTE
|
||||
DataType.BYTE, DataType.WORD -> DataType.WORD
|
||||
DataType.FLOAT -> DataType.BYTE
|
||||
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
|
||||
DataType.UBYTE -> {
|
||||
when (rightDt) {
|
||||
DataType.UBYTE -> Pair(DataType.UBYTE, null)
|
||||
DataType.BYTE -> Pair(DataType.BYTE, left)
|
||||
DataType.UWORD -> Pair(DataType.UWORD, left)
|
||||
DataType.WORD -> Pair(DataType.WORD, left)
|
||||
DataType.FLOAT -> Pair(DataType.FLOAT, left)
|
||||
else -> Pair(leftDt, null) // non-numeric datatype
|
||||
}
|
||||
}
|
||||
DataType.BYTE -> when (rightDt) {
|
||||
in NumericDatatypes -> DataType.BYTE
|
||||
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
|
||||
DataType.BYTE -> {
|
||||
when (rightDt) {
|
||||
DataType.UBYTE -> Pair(DataType.BYTE, right)
|
||||
DataType.BYTE -> Pair(DataType.BYTE, null)
|
||||
DataType.UWORD -> Pair(DataType.WORD, left)
|
||||
DataType.WORD -> Pair(DataType.WORD, left)
|
||||
DataType.FLOAT -> Pair(DataType.FLOAT, left)
|
||||
else -> Pair(leftDt, null) // non-numeric datatype
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> when (rightDt) {
|
||||
DataType.UBYTE, DataType.UWORD -> DataType.UWORD
|
||||
DataType.BYTE, DataType.WORD -> DataType.WORD
|
||||
DataType.FLOAT -> DataType.FLOAT
|
||||
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
|
||||
DataType.UWORD -> {
|
||||
when (rightDt) {
|
||||
DataType.UBYTE -> Pair(DataType.UWORD, right)
|
||||
DataType.BYTE -> Pair(DataType.WORD, right)
|
||||
DataType.UWORD -> Pair(DataType.UWORD, null)
|
||||
DataType.WORD -> Pair(DataType.WORD, left)
|
||||
DataType.FLOAT -> Pair(DataType.FLOAT, left)
|
||||
else -> Pair(leftDt, null) // non-numeric datatype
|
||||
}
|
||||
}
|
||||
DataType.WORD -> when (rightDt) {
|
||||
in NumericDatatypes -> DataType.WORD
|
||||
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
|
||||
DataType.WORD -> {
|
||||
when (rightDt) {
|
||||
DataType.UBYTE -> Pair(DataType.WORD, right)
|
||||
DataType.BYTE -> Pair(DataType.WORD, right)
|
||||
DataType.UWORD -> Pair(DataType.WORD, right)
|
||||
DataType.WORD -> Pair(DataType.WORD, null)
|
||||
DataType.FLOAT -> Pair(DataType.FLOAT, left)
|
||||
else -> Pair(leftDt, null) // non-numeric datatype
|
||||
}
|
||||
}
|
||||
DataType.FLOAT -> when (rightDt) {
|
||||
in NumericDatatypes -> DataType.FLOAT
|
||||
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
|
||||
DataType.FLOAT -> {
|
||||
Pair(DataType.FLOAT, right)
|
||||
}
|
||||
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
|
||||
else -> Pair(leftDt, null) // non-numeric datatype
|
||||
}
|
||||
}
|
||||
|
||||
fun arithmeticOpDt(leftDt: DataType, rightDt: DataType): DataType {
|
||||
return when (leftDt) {
|
||||
DataType.UBYTE -> when (rightDt) {
|
||||
DataType.UBYTE -> DataType.UBYTE
|
||||
DataType.BYTE -> DataType.BYTE
|
||||
DataType.UWORD -> DataType.UWORD
|
||||
DataType.WORD -> DataType.WORD
|
||||
DataType.FLOAT -> DataType.FLOAT
|
||||
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
|
||||
}
|
||||
DataType.BYTE -> when (rightDt) {
|
||||
in ByteDatatypes -> DataType.BYTE
|
||||
in WordDatatypes -> DataType.WORD
|
||||
DataType.FLOAT -> DataType.FLOAT
|
||||
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
|
||||
}
|
||||
DataType.UWORD -> when (rightDt) {
|
||||
DataType.UBYTE, DataType.UWORD -> DataType.UWORD
|
||||
DataType.BYTE, DataType.WORD -> DataType.WORD
|
||||
DataType.FLOAT -> DataType.FLOAT
|
||||
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
|
||||
}
|
||||
DataType.WORD -> when (rightDt) {
|
||||
in IntegerDatatypes -> DataType.WORD
|
||||
DataType.FLOAT -> DataType.FLOAT
|
||||
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
|
||||
}
|
||||
DataType.FLOAT -> when (rightDt) {
|
||||
in NumericDatatypes -> DataType.FLOAT
|
||||
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
|
||||
}
|
||||
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun commonDatatype(leftDt: DataType, rightDt: DataType,
|
||||
left: IExpression, right: IExpression): Pair<DataType, IExpression?> {
|
||||
// byte + byte -> byte
|
||||
// byte + word -> word
|
||||
// word + byte -> word
|
||||
// word + word -> word
|
||||
// a combination with a float will be float (but give a warning about this!)
|
||||
|
||||
if(this.operator=="/") {
|
||||
// division is a bit weird, don't cast the operands
|
||||
val commondt = divisionOpDt(leftDt, rightDt)
|
||||
return Pair(commondt, null)
|
||||
}
|
||||
|
||||
return when (leftDt) {
|
||||
DataType.UBYTE -> {
|
||||
when (rightDt) {
|
||||
DataType.UBYTE -> Pair(DataType.UBYTE, null)
|
||||
DataType.BYTE -> Pair(DataType.BYTE, left)
|
||||
DataType.UWORD -> Pair(DataType.UWORD, left)
|
||||
DataType.WORD -> Pair(DataType.WORD, left)
|
||||
DataType.FLOAT -> Pair(DataType.FLOAT, left)
|
||||
else -> throw FatalAstException("non-numeric datatype $rightDt")
|
||||
}
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
when (rightDt) {
|
||||
DataType.UBYTE -> Pair(DataType.BYTE, right)
|
||||
DataType.BYTE -> Pair(DataType.BYTE, null)
|
||||
DataType.UWORD -> Pair(DataType.WORD, left)
|
||||
DataType.WORD -> Pair(DataType.WORD, left)
|
||||
DataType.FLOAT -> Pair(DataType.FLOAT, left)
|
||||
else -> throw FatalAstException("non-numeric datatype $rightDt")
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
when (rightDt) {
|
||||
DataType.UBYTE -> Pair(DataType.UWORD, right)
|
||||
DataType.BYTE -> Pair(DataType.UWORD, right)
|
||||
DataType.UWORD -> Pair(DataType.UWORD, null)
|
||||
DataType.WORD -> Pair(DataType.WORD, left)
|
||||
DataType.FLOAT -> Pair(DataType.FLOAT, left)
|
||||
else -> throw FatalAstException("non-numeric datatype $rightDt")
|
||||
}
|
||||
}
|
||||
DataType.WORD -> {
|
||||
when (rightDt) {
|
||||
DataType.UBYTE -> Pair(DataType.WORD, right)
|
||||
DataType.BYTE -> Pair(DataType.WORD, right)
|
||||
DataType.UWORD -> Pair(DataType.WORD, right)
|
||||
DataType.WORD -> Pair(DataType.WORD, null)
|
||||
DataType.FLOAT -> Pair(DataType.FLOAT, left)
|
||||
else -> throw FatalAstException("non-numeric datatype $rightDt")
|
||||
}
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
Pair(DataType.FLOAT, right)
|
||||
}
|
||||
else -> throw FatalAstException("non-numeric datatype $leftDt")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ArrayIndexedExpression(val identifier: IdentifierReference,
|
||||
var arrayspec: ArrayIndex,
|
||||
override val position: Position) : IExpression {
|
||||
class ArrayIndexedExpression(var identifier: IdentifierReference,
|
||||
val arrayspec: ArrayIndex,
|
||||
override val position: Position) : Expression() {
|
||||
override lateinit var parent: Node
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@@ -220,7 +185,7 @@ class ArrayIndexedExpression(val identifier: IdentifierReference,
|
||||
arrayspec.linkParents(this)
|
||||
}
|
||||
|
||||
override fun constValue(program: Program): LiteralValue? = null
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun referencesIdentifiers(vararg name: String) = identifier.referencesIdentifiers(*name)
|
||||
@@ -231,11 +196,7 @@ class ArrayIndexedExpression(val identifier: IdentifierReference,
|
||||
return when (target.datatype) {
|
||||
in NumericDatatypes -> null
|
||||
in StringDatatypes -> DataType.UBYTE
|
||||
DataType.ARRAY_UB -> DataType.UBYTE
|
||||
DataType.ARRAY_B -> DataType.BYTE
|
||||
DataType.ARRAY_UW -> DataType.UWORD
|
||||
DataType.ARRAY_W -> DataType.WORD
|
||||
DataType.ARRAY_F -> DataType.FLOAT
|
||||
in ArrayDatatypes -> ArrayElementTypes[target.datatype]
|
||||
else -> throw FatalAstException("invalid dt")
|
||||
}
|
||||
}
|
||||
@@ -247,7 +208,7 @@ class ArrayIndexedExpression(val identifier: IdentifierReference,
|
||||
}
|
||||
}
|
||||
|
||||
class TypecastExpression(var expression: IExpression, var type: DataType, val implicit: Boolean, override val position: Position) : IExpression {
|
||||
class TypecastExpression(var expression: Expression, var type: DataType, val implicit: Boolean, override val position: Position) : Expression() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
@@ -259,7 +220,7 @@ class TypecastExpression(var expression: IExpression, var type: DataType, val im
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name)
|
||||
override fun inferType(program: Program): DataType? = type
|
||||
override fun constValue(program: Program): LiteralValue? {
|
||||
override fun constValue(program: Program): NumericLiteralValue? {
|
||||
val cv = expression.constValue(program) ?: return null
|
||||
return cv.cast(type)
|
||||
// val value = RuntimeValue(cv.type, cv.asNumericValue!!).cast(type)
|
||||
@@ -271,7 +232,7 @@ class TypecastExpression(var expression: IExpression, var type: DataType, val im
|
||||
}
|
||||
}
|
||||
|
||||
data class AddressOf(val identifier: IdentifierReference, override val position: Position) : IExpression {
|
||||
data class AddressOf(var identifier: IdentifierReference, override val position: Position) : Expression() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
@@ -279,15 +240,15 @@ data class AddressOf(val identifier: IdentifierReference, override val position:
|
||||
identifier.parent=this
|
||||
}
|
||||
|
||||
var scopedname: String? = null // will be set in a later state by the compiler
|
||||
override fun constValue(program: Program): LiteralValue? = null
|
||||
var scopedname: String? = null // will be set in a later state by the compiler // TODO get rid of this??
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun referencesIdentifiers(vararg name: String) = false
|
||||
override fun inferType(program: Program) = DataType.UWORD
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
}
|
||||
|
||||
class DirectMemoryRead(var addressExpression: IExpression, override val position: Position) : IExpression {
|
||||
class DirectMemoryRead(var addressExpression: Expression, override val position: Position) : Expression() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
@@ -299,163 +260,217 @@ class DirectMemoryRead(var addressExpression: IExpression, override val position
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun referencesIdentifiers(vararg name: String) = false
|
||||
override fun inferType(program: Program): DataType? = DataType.UBYTE
|
||||
override fun constValue(program: Program): LiteralValue? = null
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
|
||||
override fun toString(): String {
|
||||
return "DirectMemoryRead($addressExpression)"
|
||||
}
|
||||
}
|
||||
|
||||
open class LiteralValue(val type: DataType,
|
||||
val bytevalue: Short? = null,
|
||||
val wordvalue: Int? = null,
|
||||
val floatvalue: Double? = null,
|
||||
val strvalue: String? = null,
|
||||
val arrayvalue: Array<IExpression>? = null,
|
||||
initHeapId: Int? =null,
|
||||
override val position: Position) : IExpression {
|
||||
class NumericLiteralValue(val type: DataType, // only numerical types allowed
|
||||
val number: Number, // can be byte, word or float depending on the type
|
||||
override val position: Position) : Expression() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String) = arrayvalue?.any { it.referencesIdentifiers(*name) } ?: false
|
||||
|
||||
val isString = type in StringDatatypes
|
||||
val isNumeric = type in NumericDatatypes
|
||||
val isArray = type in ArrayDatatypes
|
||||
var heapId = initHeapId
|
||||
private set
|
||||
|
||||
companion object {
|
||||
fun fromBoolean(bool: Boolean, position: Position) =
|
||||
LiteralValue(DataType.UBYTE, bytevalue = if (bool) 1 else 0, position = position)
|
||||
NumericLiteralValue(DataType.UBYTE, if (bool) 1 else 0, position)
|
||||
|
||||
fun fromNumber(value: Number, type: DataType, position: Position) : LiteralValue {
|
||||
return when(type) {
|
||||
in ByteDatatypes -> LiteralValue(type, bytevalue = value.toShort(), position = position)
|
||||
in WordDatatypes -> LiteralValue(type, wordvalue = value.toInt(), position = position)
|
||||
DataType.FLOAT -> LiteralValue(type, floatvalue = value.toDouble(), position = position)
|
||||
else -> throw FatalAstException("non numeric datatype")
|
||||
}
|
||||
}
|
||||
|
||||
fun optimalNumeric(value: Number, position: Position): LiteralValue {
|
||||
fun optimalNumeric(value: Number, position: Position): NumericLiteralValue {
|
||||
return if(value is Double) {
|
||||
LiteralValue(DataType.FLOAT, floatvalue = value, position = position)
|
||||
NumericLiteralValue(DataType.FLOAT, value, position)
|
||||
} else {
|
||||
when (val intval = value.toInt()) {
|
||||
in 0..255 -> LiteralValue(DataType.UBYTE, bytevalue = intval.toShort(), position = position)
|
||||
in -128..127 -> LiteralValue(DataType.BYTE, bytevalue = intval.toShort(), position = position)
|
||||
in 0..65535 -> LiteralValue(DataType.UWORD, wordvalue = intval, position = position)
|
||||
in -32768..32767 -> LiteralValue(DataType.WORD, wordvalue = intval, position = position)
|
||||
else -> LiteralValue(DataType.FLOAT, floatvalue = intval.toDouble(), position = position)
|
||||
in 0..255 -> NumericLiteralValue(DataType.UBYTE, intval, position)
|
||||
in -128..127 -> NumericLiteralValue(DataType.BYTE, intval, position)
|
||||
in 0..65535 -> NumericLiteralValue(DataType.UWORD, intval, position)
|
||||
in -32768..32767 -> NumericLiteralValue(DataType.WORD, intval, position)
|
||||
else -> NumericLiteralValue(DataType.FLOAT, intval.toDouble(), position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun optimalInteger(value: Number, position: Position): LiteralValue {
|
||||
val intval = value.toInt()
|
||||
if(intval.toDouble() != value.toDouble())
|
||||
throw FatalAstException("value is not an integer: $value")
|
||||
return when (intval) {
|
||||
in 0..255 -> LiteralValue(DataType.UBYTE, bytevalue = value.toShort(), position = position)
|
||||
in -128..127 -> LiteralValue(DataType.BYTE, bytevalue = value.toShort(), position = position)
|
||||
in 0..65535 -> LiteralValue(DataType.UWORD, wordvalue = value.toInt(), position = position)
|
||||
fun optimalInteger(value: Int, position: Position): NumericLiteralValue {
|
||||
return when (value) {
|
||||
in 0..255 -> NumericLiteralValue(DataType.UBYTE, value, position)
|
||||
in -128..127 -> NumericLiteralValue(DataType.BYTE, value, position)
|
||||
in 0..65535 -> NumericLiteralValue(DataType.UWORD, value, position)
|
||||
in -32768..32767 -> NumericLiteralValue(DataType.WORD, value, position)
|
||||
else -> throw FatalAstException("integer overflow: $value")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
when(type){
|
||||
in ByteDatatypes -> if(bytevalue==null) throw FatalAstException("literal value missing bytevalue")
|
||||
in WordDatatypes -> if(wordvalue==null) throw FatalAstException("literal value missing wordvalue")
|
||||
DataType.FLOAT -> if(floatvalue==null) throw FatalAstException("literal value missing floatvalue")
|
||||
in StringDatatypes ->
|
||||
if(strvalue==null && heapId==null) throw FatalAstException("literal value missing strvalue/heapId")
|
||||
in ArrayDatatypes ->
|
||||
if(arrayvalue==null && heapId==null) throw FatalAstException("literal value missing arrayvalue/heapId")
|
||||
else -> throw FatalAstException("invalid type $type")
|
||||
}
|
||||
if(bytevalue==null && wordvalue==null && floatvalue==null && arrayvalue==null && strvalue==null && heapId==null)
|
||||
throw FatalAstException("literal value without actual value")
|
||||
}
|
||||
|
||||
val asNumericValue: Number? = when {
|
||||
bytevalue!=null -> bytevalue
|
||||
wordvalue!=null -> wordvalue
|
||||
floatvalue!=null -> floatvalue
|
||||
else -> null
|
||||
}
|
||||
|
||||
val asIntegerValue: Int? = when {
|
||||
bytevalue!=null -> bytevalue.toInt()
|
||||
wordvalue!=null -> wordvalue
|
||||
// don't round a float value, otherwise code will not detect that it's not an integer
|
||||
else -> null
|
||||
}
|
||||
|
||||
val asBooleanValue: Boolean =
|
||||
(floatvalue!=null && floatvalue != 0.0) ||
|
||||
(bytevalue!=null && bytevalue != 0.toShort()) ||
|
||||
(wordvalue!=null && wordvalue != 0) ||
|
||||
(strvalue!=null && strvalue.isNotEmpty()) ||
|
||||
(arrayvalue != null && arrayvalue.isNotEmpty())
|
||||
val asBooleanValue: Boolean = number!=0
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
arrayvalue?.forEach {it.linkParents(this)}
|
||||
}
|
||||
|
||||
override fun constValue(program: Program): LiteralValue? {
|
||||
if(arrayvalue!=null) {
|
||||
for(v in arrayvalue) {
|
||||
if(v.constValue(program)==null) return null
|
||||
override fun referencesIdentifiers(vararg name: String) = false
|
||||
override fun constValue(program: Program) = this
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
|
||||
override fun toString(): String = "NumericLiteral(${type.name}:$number)"
|
||||
|
||||
override fun inferType(program: Program) = type
|
||||
|
||||
override fun hashCode(): Int = type.hashCode() * 31 xor number.hashCode()
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if(other==null || other !is NumericLiteralValue)
|
||||
return false
|
||||
return number.toDouble()==other.number.toDouble()
|
||||
}
|
||||
|
||||
operator fun compareTo(other: NumericLiteralValue): Int = number.toDouble().compareTo(other.number.toDouble())
|
||||
|
||||
fun cast(targettype: DataType): NumericLiteralValue? {
|
||||
if(type==targettype)
|
||||
return this
|
||||
val numval = number.toDouble()
|
||||
when(type) {
|
||||
DataType.UBYTE -> {
|
||||
if(targettype== DataType.BYTE && numval <= 127)
|
||||
return NumericLiteralValue(targettype, number.toShort(), position)
|
||||
if(targettype== DataType.WORD || targettype== DataType.UWORD)
|
||||
return NumericLiteralValue(targettype, number.toInt(), position)
|
||||
if(targettype== DataType.FLOAT)
|
||||
return NumericLiteralValue(targettype, number.toDouble(), position)
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
if(targettype== DataType.UBYTE && numval >= 0)
|
||||
return NumericLiteralValue(targettype, number.toShort(), position)
|
||||
if(targettype== DataType.UWORD && numval >= 0)
|
||||
return NumericLiteralValue(targettype, number.toInt(), position)
|
||||
if(targettype== DataType.WORD)
|
||||
return NumericLiteralValue(targettype, number.toInt(), position)
|
||||
if(targettype== DataType.FLOAT)
|
||||
return NumericLiteralValue(targettype, number.toDouble(), position)
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
if(targettype== DataType.BYTE && numval <= 127)
|
||||
return NumericLiteralValue(targettype, number.toShort(), position)
|
||||
if(targettype== DataType.UBYTE && numval <= 255)
|
||||
return NumericLiteralValue(targettype, number.toShort(), position)
|
||||
if(targettype== DataType.WORD && numval <= 32767)
|
||||
return NumericLiteralValue(targettype, number.toInt(), position)
|
||||
if(targettype== DataType.FLOAT)
|
||||
return NumericLiteralValue(targettype, number.toDouble(), position)
|
||||
}
|
||||
DataType.WORD -> {
|
||||
if(targettype== DataType.BYTE && numval >= -128 && numval <=127)
|
||||
return NumericLiteralValue(targettype, number.toShort(), position)
|
||||
if(targettype== DataType.UBYTE && numval >= 0 && numval <= 255)
|
||||
return NumericLiteralValue(targettype, number.toShort(), position)
|
||||
if(targettype== DataType.UWORD && numval >=0)
|
||||
return NumericLiteralValue(targettype, number.toInt(), position)
|
||||
if(targettype== DataType.FLOAT)
|
||||
return NumericLiteralValue(targettype, number.toDouble(), position)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
if (targettype == DataType.BYTE && numval >= -128 && numval <=127)
|
||||
return NumericLiteralValue(targettype, number.toShort(), position)
|
||||
if (targettype == DataType.UBYTE && numval >=0 && numval <= 255)
|
||||
return NumericLiteralValue(targettype, number.toShort(), position)
|
||||
if (targettype == DataType.WORD && numval >= -32768 && numval <= 32767)
|
||||
return NumericLiteralValue(targettype, number.toInt(), position)
|
||||
if (targettype == DataType.UWORD && numval >=0 && numval <= 65535)
|
||||
return NumericLiteralValue(targettype, number.toInt(), position)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
return this
|
||||
return null // invalid type conversion from $this to $targettype
|
||||
}
|
||||
}
|
||||
|
||||
class StructLiteralValue(var values: List<Expression>,
|
||||
override val position: Position): Expression() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent=parent
|
||||
values.forEach { it.linkParents(this) }
|
||||
}
|
||||
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun referencesIdentifiers(vararg name: String) = values.any { it.referencesIdentifiers(*name) }
|
||||
override fun inferType(program: Program) = DataType.STRUCT
|
||||
|
||||
override fun toString(): String {
|
||||
return "struct{ ${values.joinToString(", ")} }"
|
||||
}
|
||||
}
|
||||
|
||||
class ReferenceLiteralValue(val type: DataType, // only reference types allowed here
|
||||
val str: String? = null,
|
||||
val array: Array<Expression>? = null,
|
||||
// actually, at the moment, we don't have struct literals in the language
|
||||
initHeapId: Int? =null,
|
||||
override val position: Position) : Expression() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String) = array?.any { it.referencesIdentifiers(*name) } ?: false
|
||||
|
||||
val isString = type in StringDatatypes
|
||||
val isArray = type in ArrayDatatypes
|
||||
var heapId = initHeapId
|
||||
private set
|
||||
|
||||
init {
|
||||
when(type){
|
||||
in StringDatatypes ->
|
||||
if(str==null) throw FatalAstException("literal value missing strvalue/heapId")
|
||||
in ArrayDatatypes ->
|
||||
if(array==null) throw FatalAstException("literal value missing arrayvalue/heapId")
|
||||
else -> throw FatalAstException("invalid type $type")
|
||||
}
|
||||
if(array==null && str==null)
|
||||
throw FatalAstException("literal ref value without actual value")
|
||||
}
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
array?.forEach {it.linkParents(this)}
|
||||
}
|
||||
|
||||
override fun constValue(program: Program): NumericLiteralValue? {
|
||||
// note that we can't handle arrays that only contain constant numbers here anymore
|
||||
// so they're not treated as constants anymore
|
||||
return null
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
|
||||
override fun toString(): String {
|
||||
val vstr = when(type) {
|
||||
DataType.UBYTE -> "ubyte:$bytevalue"
|
||||
DataType.BYTE -> "byte:$bytevalue"
|
||||
DataType.UWORD -> "uword:$wordvalue"
|
||||
DataType.WORD -> "word:$wordvalue"
|
||||
DataType.FLOAT -> "float:$floatvalue"
|
||||
in StringDatatypes -> "str:'${escape(strvalue?:"")}'"
|
||||
in ArrayDatatypes -> "array:$arrayvalue"
|
||||
else -> throw FatalAstException("weird datatype")
|
||||
val valueStr = when(type) {
|
||||
in StringDatatypes -> "'${escape(str!!)}'"
|
||||
in ArrayDatatypes -> "$array"
|
||||
else -> throw FatalAstException("weird ref type")
|
||||
}
|
||||
return "LiteralValue($vstr)"
|
||||
return "RefValueLit($type, $valueStr)"
|
||||
}
|
||||
|
||||
override fun inferType(program: Program) = type
|
||||
|
||||
override fun hashCode(): Int {
|
||||
val bh = bytevalue?.hashCode() ?: 0x10001234
|
||||
val wh = wordvalue?.hashCode() ?: 0x01002345
|
||||
val fh = floatvalue?.hashCode() ?: 0x00103456
|
||||
val sh = strvalue?.hashCode() ?: 0x00014567
|
||||
val ah = arrayvalue?.hashCode() ?: 0x11119876
|
||||
var hash = bh * 31 xor wh
|
||||
hash = hash*31 xor fh
|
||||
hash = hash*31 xor sh
|
||||
hash = hash*31 xor ah
|
||||
hash = hash*31 xor type.hashCode()
|
||||
return hash
|
||||
val sh = str?.hashCode() ?: 0x00014567
|
||||
val ah = array?.hashCode() ?: 0x11119876
|
||||
return sh * 31 xor ah xor type.hashCode()
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if(other==null || other !is LiteralValue)
|
||||
if(other==null || other !is ReferenceLiteralValue)
|
||||
return false
|
||||
if(isNumeric && other.isNumeric)
|
||||
return asNumericValue?.toDouble()==other.asNumericValue?.toDouble()
|
||||
if(isArray && other.isArray)
|
||||
return arrayvalue!!.contentEquals(other.arrayvalue!!) && heapId==other.heapId
|
||||
return array!!.contentEquals(other.array!!) && heapId==other.heapId
|
||||
if(isString && other.isString)
|
||||
return strvalue==other.strvalue && heapId==other.heapId
|
||||
return str==other.str && heapId==other.heapId
|
||||
|
||||
if(type!=other.type)
|
||||
return false
|
||||
@@ -463,74 +478,34 @@ open class LiteralValue(val type: DataType,
|
||||
return compareTo(other) == 0
|
||||
}
|
||||
|
||||
operator fun compareTo(other: LiteralValue): Int {
|
||||
val numLeft = asNumericValue?.toDouble()
|
||||
val numRight = other.asNumericValue?.toDouble()
|
||||
if(numLeft!=null && numRight!=null)
|
||||
return numLeft.compareTo(numRight)
|
||||
|
||||
if(strvalue!=null && other.strvalue!=null)
|
||||
return strvalue.compareTo(other.strvalue)
|
||||
|
||||
operator fun compareTo(other: ReferenceLiteralValue): Int {
|
||||
throw ExpressionError("cannot order compare type $type with ${other.type}", other.position)
|
||||
}
|
||||
|
||||
fun cast(targettype: DataType): LiteralValue? {
|
||||
fun cast(targettype: DataType): ReferenceLiteralValue? {
|
||||
if(type==targettype)
|
||||
return this
|
||||
when(type) {
|
||||
DataType.UBYTE -> {
|
||||
if(targettype== DataType.BYTE && bytevalue!! <= 127)
|
||||
return LiteralValue(targettype, bytevalue = bytevalue, position = position)
|
||||
if(targettype== DataType.WORD || targettype== DataType.UWORD)
|
||||
return LiteralValue(targettype, wordvalue = bytevalue!!.toInt(), position = position)
|
||||
if(targettype== DataType.FLOAT)
|
||||
return LiteralValue(targettype, floatvalue = bytevalue!!.toDouble(), position = position)
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
if(targettype== DataType.UBYTE && bytevalue!! >= 0)
|
||||
return LiteralValue(targettype, bytevalue = bytevalue, position = position)
|
||||
if(targettype== DataType.UWORD && bytevalue!! >= 0)
|
||||
return LiteralValue(targettype, wordvalue = bytevalue.toInt(), position = position)
|
||||
if(targettype== DataType.WORD)
|
||||
return LiteralValue(targettype, wordvalue = bytevalue!!.toInt(), position = position)
|
||||
if(targettype== DataType.FLOAT)
|
||||
return LiteralValue(targettype, floatvalue = bytevalue!!.toDouble(), position = position)
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
if(targettype== DataType.BYTE && wordvalue!! <= 127)
|
||||
return LiteralValue(targettype, bytevalue = wordvalue.toShort(), position = position)
|
||||
if(targettype== DataType.UBYTE && wordvalue!! <= 255)
|
||||
return LiteralValue(targettype, bytevalue = wordvalue.toShort(), position = position)
|
||||
if(targettype== DataType.WORD && wordvalue!! <= 32767)
|
||||
return LiteralValue(targettype, wordvalue = wordvalue, position = position)
|
||||
if(targettype== DataType.FLOAT)
|
||||
return LiteralValue(targettype, floatvalue = wordvalue!!.toDouble(), position = position)
|
||||
}
|
||||
DataType.WORD -> {
|
||||
if(targettype== DataType.BYTE && wordvalue!! in -128..127)
|
||||
return LiteralValue(targettype, bytevalue = wordvalue.toShort(), position = position)
|
||||
if(targettype== DataType.UBYTE && wordvalue!! in 0..255)
|
||||
return LiteralValue(targettype, bytevalue = wordvalue.toShort(), position = position)
|
||||
if(targettype== DataType.UWORD && wordvalue!! >=0)
|
||||
return LiteralValue(targettype, wordvalue = wordvalue, position = position)
|
||||
if(targettype== DataType.FLOAT)
|
||||
return LiteralValue(targettype, floatvalue = wordvalue!!.toDouble(), position = position)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
val value = floatvalue!!.toInt()
|
||||
if (targettype == DataType.BYTE && value in -128..127)
|
||||
return LiteralValue(targettype, bytevalue = value.toShort(), position = position)
|
||||
if (targettype == DataType.UBYTE && value in 0..255)
|
||||
return LiteralValue(targettype, bytevalue = value.toShort(), position = position)
|
||||
if (targettype == DataType.WORD && value in -32768..32767)
|
||||
return LiteralValue(targettype, wordvalue = value, position = position)
|
||||
if (targettype == DataType.UWORD && value in 0..65535)
|
||||
return LiteralValue(targettype, wordvalue = value, position = position)
|
||||
}
|
||||
in StringDatatypes -> {
|
||||
if(targettype in StringDatatypes)
|
||||
return this
|
||||
return ReferenceLiteralValue(targettype, str, position = position)
|
||||
}
|
||||
in ArrayDatatypes -> {
|
||||
if(targettype in ArrayDatatypes) {
|
||||
val elementType = ArrayElementTypes.getValue(targettype)
|
||||
val castArray = array!!.map{
|
||||
val num = it as? NumericLiteralValue
|
||||
if(num==null) {
|
||||
// an array of UWORDs could possibly also contain AddressOfs
|
||||
if (elementType != DataType.UWORD || it !is AddressOf)
|
||||
throw FatalAstException("weird array element $it")
|
||||
it
|
||||
} else {
|
||||
num.cast(elementType)!!
|
||||
}
|
||||
}.toTypedArray()
|
||||
return ReferenceLiteralValue(targettype, null, array=castArray, position = position)
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
@@ -538,27 +513,28 @@ open class LiteralValue(val type: DataType,
|
||||
}
|
||||
|
||||
fun addToHeap(heap: HeapValues) {
|
||||
if(heapId==null) {
|
||||
if (strvalue != null) {
|
||||
heapId = heap.addString(type, strvalue)
|
||||
}
|
||||
else if (arrayvalue!=null) {
|
||||
if(arrayvalue.any {it is AddressOf }) {
|
||||
val intArrayWithAddressOfs = arrayvalue.map {
|
||||
when (it) {
|
||||
is AddressOf -> IntegerOrAddressOf(null, it)
|
||||
is LiteralValue -> IntegerOrAddressOf(it.asIntegerValue, null)
|
||||
else -> throw FatalAstException("invalid datatype in array")
|
||||
}
|
||||
if (heapId != null) return
|
||||
if (str != null) {
|
||||
heapId = heap.addString(type, str)
|
||||
}
|
||||
else if (array!=null) {
|
||||
if(array.any {it is AddressOf }) {
|
||||
val intArrayWithAddressOfs = array.map {
|
||||
when (it) {
|
||||
is AddressOf -> IntegerOrAddressOf(null, it)
|
||||
is NumericLiteralValue -> IntegerOrAddressOf(it.number.toInt(), null)
|
||||
else -> throw FatalAstException("invalid datatype in array")
|
||||
}
|
||||
heapId = heap.addIntegerArray(type, intArrayWithAddressOfs.toTypedArray())
|
||||
} else {
|
||||
val valuesInArray = arrayvalue.map { (it as LiteralValue).asNumericValue!! }
|
||||
heapId = if(type== DataType.ARRAY_F) {
|
||||
val doubleArray = valuesInArray.map { it.toDouble() }.toDoubleArray()
|
||||
}
|
||||
heapId = heap.addIntegerArray(type, intArrayWithAddressOfs.toTypedArray())
|
||||
} else {
|
||||
val valuesInArray = array.map { (it as? NumericLiteralValue)?.number }
|
||||
if(null !in valuesInArray) {
|
||||
heapId = if (type == DataType.ARRAY_F) {
|
||||
val doubleArray = valuesInArray.map { it!!.toDouble() }.toDoubleArray()
|
||||
heap.addDoublesArray(doubleArray)
|
||||
} else {
|
||||
val integerArray = valuesInArray.map { it.toInt() }
|
||||
val integerArray = valuesInArray.map { it!!.toInt() }
|
||||
heap.addIntegerArray(type, integerArray.map { IntegerOrAddressOf(it, null) }.toTypedArray())
|
||||
}
|
||||
}
|
||||
@@ -567,10 +543,10 @@ open class LiteralValue(val type: DataType,
|
||||
}
|
||||
}
|
||||
|
||||
class RangeExpr(var from: IExpression,
|
||||
var to: IExpression,
|
||||
var step: IExpression,
|
||||
override val position: Position) : IExpression {
|
||||
class RangeExpr(var from: Expression,
|
||||
var to: Expression,
|
||||
var step: Expression,
|
||||
override val position: Position) : Expression() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
@@ -580,7 +556,7 @@ class RangeExpr(var from: IExpression,
|
||||
step.linkParents(this)
|
||||
}
|
||||
|
||||
override fun constValue(program: Program): LiteralValue? = null
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun referencesIdentifiers(vararg name: String): Boolean = from.referencesIdentifiers(*name) || to.referencesIdentifiers(*name)
|
||||
@@ -589,13 +565,13 @@ class RangeExpr(var from: IExpression,
|
||||
val toDt=to.inferType(program)
|
||||
return when {
|
||||
fromDt==null || toDt==null -> null
|
||||
fromDt== DataType.UBYTE && toDt== DataType.UBYTE -> DataType.UBYTE
|
||||
fromDt== DataType.UWORD && toDt== DataType.UWORD -> DataType.UWORD
|
||||
fromDt== DataType.UBYTE && toDt== DataType.UBYTE -> DataType.ARRAY_UB
|
||||
fromDt== DataType.UWORD && toDt== DataType.UWORD -> DataType.ARRAY_UW
|
||||
fromDt== DataType.STR && toDt== DataType.STR -> DataType.STR
|
||||
fromDt== DataType.STR_S && toDt== DataType.STR_S -> DataType.STR_S
|
||||
fromDt== DataType.WORD || toDt== DataType.WORD -> DataType.WORD
|
||||
fromDt== DataType.BYTE || toDt== DataType.BYTE -> DataType.BYTE
|
||||
else -> DataType.UBYTE
|
||||
fromDt== DataType.WORD || toDt== DataType.WORD -> DataType.ARRAY_W
|
||||
fromDt== DataType.BYTE || toDt== DataType.BYTE -> DataType.ARRAY_B
|
||||
else -> DataType.ARRAY_UB
|
||||
}
|
||||
}
|
||||
override fun toString(): String {
|
||||
@@ -603,30 +579,32 @@ class RangeExpr(var from: IExpression,
|
||||
}
|
||||
|
||||
fun size(): Int? {
|
||||
val fromLv = (from as? LiteralValue)
|
||||
val toLv = (to as? LiteralValue)
|
||||
val fromLv = (from as? NumericLiteralValue)
|
||||
val toLv = (to as? NumericLiteralValue)
|
||||
if(fromLv==null || toLv==null)
|
||||
return null
|
||||
return toConstantIntegerRange()?.count()
|
||||
}
|
||||
|
||||
fun toConstantIntegerRange(): IntProgression? {
|
||||
val fromLv = from as? LiteralValue
|
||||
val toLv = to as? LiteralValue
|
||||
if(fromLv==null || toLv==null)
|
||||
return null // non-constant range
|
||||
val fromVal: Int
|
||||
val toVal: Int
|
||||
if(fromLv.isString && toLv.isString) {
|
||||
val fromRlv = from as? ReferenceLiteralValue
|
||||
val toRlv = to as? ReferenceLiteralValue
|
||||
if(fromRlv!=null && fromRlv.isString && toRlv!=null && toRlv.isString) {
|
||||
// string range -> int range over petscii values
|
||||
fromVal = Petscii.encodePetscii(fromLv.strvalue!!, true)[0].toInt()
|
||||
toVal = Petscii.encodePetscii(toLv.strvalue!!, true)[0].toInt()
|
||||
fromVal = Petscii.encodePetscii(fromRlv.str!!, true)[0].toInt()
|
||||
toVal = Petscii.encodePetscii(toRlv.str!!, true)[0].toInt()
|
||||
} else {
|
||||
val fromLv = from as? NumericLiteralValue
|
||||
val toLv = to as? NumericLiteralValue
|
||||
if(fromLv==null || toLv==null)
|
||||
return null // non-constant range
|
||||
// integer range
|
||||
fromVal = (from as LiteralValue).asIntegerValue!!
|
||||
toVal = (to as LiteralValue).asIntegerValue!!
|
||||
fromVal = fromLv.number.toInt()
|
||||
toVal = toLv.number.toInt()
|
||||
}
|
||||
val stepVal = (step as? LiteralValue)?.asIntegerValue ?: 1
|
||||
val stepVal = (step as? NumericLiteralValue)?.number?.toInt() ?: 1
|
||||
return when {
|
||||
fromVal <= toVal -> when {
|
||||
stepVal <= 0 -> IntRange.EMPTY
|
||||
@@ -642,14 +620,14 @@ class RangeExpr(var from: IExpression,
|
||||
}
|
||||
}
|
||||
|
||||
class RegisterExpr(val register: Register, override val position: Position) : IExpression {
|
||||
class RegisterExpr(val register: Register, override val position: Position) : Expression() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
}
|
||||
|
||||
override fun constValue(program: Program): LiteralValue? = null
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun referencesIdentifiers(vararg name: String): Boolean = register.name in name
|
||||
@@ -660,7 +638,7 @@ class RegisterExpr(val register: Register, override val position: Position) : IE
|
||||
override fun inferType(program: Program) = DataType.UBYTE
|
||||
}
|
||||
|
||||
data class IdentifierReference(val nameInSource: List<String>, override val position: Position) : IExpression {
|
||||
data class IdentifierReference(val nameInSource: List<String>, override val position: Position) : Expression() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
fun targetStatement(namespace: INameScope) =
|
||||
@@ -676,7 +654,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
|
||||
this.parent = parent
|
||||
}
|
||||
|
||||
override fun constValue(program: Program): LiteralValue? {
|
||||
override fun constValue(program: Program): NumericLiteralValue? {
|
||||
val node = program.namespace.lookup(nameInSource, this)
|
||||
?: throw UndefinedSymbolError(this)
|
||||
val vardecl = node as? VarDecl
|
||||
@@ -705,15 +683,29 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
|
||||
}
|
||||
}
|
||||
|
||||
fun memberOfStruct(namespace: INameScope) = this.targetVarDecl(namespace)?.struct
|
||||
|
||||
fun heapId(namespace: INameScope): Int {
|
||||
val node = namespace.lookup(nameInSource, this) ?: throw UndefinedSymbolError(this)
|
||||
return ((node as? VarDecl)?.value as? LiteralValue)?.heapId ?: throw FatalAstException("identifier is not on the heap: $this")
|
||||
val value = (node as? VarDecl)?.value ?: throw FatalAstException("requires a reference value")
|
||||
return when (value) {
|
||||
is IdentifierReference -> value.heapId(namespace)
|
||||
is ReferenceLiteralValue -> value.heapId ?: throw FatalAstException("refLv is not on the heap: $value")
|
||||
else -> throw FatalAstException("requires a reference value")
|
||||
}
|
||||
}
|
||||
|
||||
fun withPrefixedName(nameprefix: String): IdentifierReference {
|
||||
val prefixed = nameInSource.dropLast(1) + listOf(nameprefix+nameInSource.last())
|
||||
val new = IdentifierReference(prefixed, position)
|
||||
new.parent = parent
|
||||
return new
|
||||
}
|
||||
}
|
||||
|
||||
class FunctionCall(override var target: IdentifierReference,
|
||||
override var arglist: MutableList<IExpression>,
|
||||
override val position: Position) : IExpression, IFunctionCall {
|
||||
override var arglist: MutableList<Expression>,
|
||||
override val position: Position) : Expression(), IFunctionCall {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
@@ -724,12 +716,12 @@ class FunctionCall(override var target: IdentifierReference,
|
||||
|
||||
override fun constValue(program: Program) = constValue(program, true)
|
||||
|
||||
private fun constValue(program: Program, withDatatypeCheck: Boolean): LiteralValue? {
|
||||
private fun constValue(program: Program, withDatatypeCheck: Boolean): NumericLiteralValue? {
|
||||
// if the function is a built-in function and the args are consts, should try to const-evaluate!
|
||||
// lenghts of arrays and strings are constants that are determined at compile time!
|
||||
if(target.nameInSource.size>1) return null
|
||||
try {
|
||||
var resultValue: LiteralValue? = null
|
||||
var resultValue: NumericLiteralValue? = null
|
||||
val func = BuiltinFunctions[target.nameInSource[0]]
|
||||
if(func!=null) {
|
||||
val exprfunc = func.constExpressionFunc
|
||||
@@ -782,8 +774,7 @@ class FunctionCall(override var target: IdentifierReference,
|
||||
return stmt.returntypes[0]
|
||||
return null // has multiple return types... so not a single resulting datatype possible
|
||||
}
|
||||
is Label -> return null
|
||||
else -> return null
|
||||
}
|
||||
return null // calling something we don't recognise...
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,32 +1,44 @@
|
||||
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.autoHeapValuePrefix
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.HeapValues
|
||||
import prog8.compiler.target.c64.AssemblyProgram
|
||||
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 var blocks: MutableMap<String, Block> = mutableMapOf()
|
||||
private var blocks = mutableMapOf<String, Block>()
|
||||
private val vardeclsToAdd = mutableMapOf<INameScope, MutableList<VarDecl>>()
|
||||
|
||||
internal fun result(): List<AstException> {
|
||||
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))
|
||||
}
|
||||
|
||||
override fun visit(module: Module) {
|
||||
vardeclsToAdd.clear()
|
||||
blocks.clear() // blocks may be redefined within a different 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]
|
||||
if(existing!=null)
|
||||
nameError(block.name, block.position, existing)
|
||||
@@ -36,7 +48,7 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
|
||||
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") {
|
||||
// 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)
|
||||
@@ -46,7 +58,7 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
|
||||
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
|
||||
decl.datatypeErrors.forEach { checkResult.add(it) }
|
||||
|
||||
@@ -55,25 +67,65 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
|
||||
// the builtin functions can't be redefined
|
||||
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.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)
|
||||
nameError(decl.name, decl.position, existing)
|
||||
|
||||
return super.visit(decl)
|
||||
}
|
||||
|
||||
override fun visit(subroutine: Subroutine): IStatement {
|
||||
if(subroutine.name in BuiltinFunctions) {
|
||||
override fun visit(subroutine: Subroutine): Statement {
|
||||
if(subroutine.name in AssemblyProgram.opcodeNames) {
|
||||
checkResult.add(NameError("can't use a cpu opcode name as a symbol", subroutine.position))
|
||||
} else if(subroutine.name in BuiltinFunctions) {
|
||||
// the builtin functions can't be redefined
|
||||
checkResult.add(NameError("builtin function cannot be redefined", subroutine.position))
|
||||
} else {
|
||||
if (subroutine.parameters.any { it.name in BuiltinFunctions })
|
||||
checkResult.add(NameError("builtin function name cannot be used as parameter", subroutine.position))
|
||||
// already reported elsewhere:
|
||||
// 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)
|
||||
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
|
||||
val symbolsInSub = subroutine.allDefinedSymbols()
|
||||
val namesInSub = symbolsInSub.map{ it.first }.toSet()
|
||||
@@ -98,8 +150,8 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
|
||||
subroutine.parameters
|
||||
.filter { it.name !in namesInSub }
|
||||
.forEach {
|
||||
val vardecl = VarDecl(VarDeclType.VAR, it.type, false, null, it.name, null,
|
||||
isArray = false, autoGenerated = true, position = subroutine.position)
|
||||
val vardecl = VarDecl(VarDeclType.VAR, it.type, ZeropageWish.NOT_IN_ZEROPAGE, null, it.name, null, null,
|
||||
isArray = false, autogeneratedDontRemove = true, position = subroutine.position)
|
||||
vardecl.linkParents(subroutine)
|
||||
subroutine.statements.add(0, vardecl)
|
||||
}
|
||||
@@ -109,19 +161,22 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
|
||||
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.position))
|
||||
|
||||
if(label.name in BuiltinFunctions) {
|
||||
// the builtin functions can't be redefined
|
||||
checkResult.add(NameError("builtin function cannot be redefined", label.position))
|
||||
} else {
|
||||
val existing = namespace.lookup(listOf(label.name), label)
|
||||
val existing = program.namespace.lookup(listOf(label.name), label)
|
||||
if (existing != null && existing !== label)
|
||||
nameError(label.name, label.position, existing)
|
||||
}
|
||||
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
|
||||
// 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
|
||||
@@ -131,30 +186,36 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
|
||||
checkResult.add(SyntaxError("register loop variables have a fixed implicit datatype", forLoop.position))
|
||||
if(forLoop.loopRegister == Register.X)
|
||||
printWarning("writing to the X register is dangerous, because it's used as an internal pointer", forLoop.position)
|
||||
} else if(forLoop.loopVar!=null) {
|
||||
val varName = forLoop.loopVar.nameInSource.last()
|
||||
if(forLoop.decltype!=null) {
|
||||
val existing = if(forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(forLoop.loopVar.nameInSource, forLoop.body.statements.first())
|
||||
if(existing==null) {
|
||||
// create the local scoped for loop variable itself
|
||||
val vardecl = VarDecl(VarDeclType.VAR, forLoop.decltype, forLoop.zeropage, null, varName, 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'
|
||||
} else {
|
||||
val loopVar = forLoop.loopVar
|
||||
if (loopVar != null) {
|
||||
val varName = loopVar.nameInSource.last()
|
||||
if (forLoop.decltype != null) {
|
||||
val existing = if (forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(loopVar.nameInSource, forLoop.body.statements.first())
|
||||
if (existing == null) {
|
||||
// create the local scoped for loop variable itself
|
||||
val vardecl = VarDecl(VarDeclType.VAR, forLoop.decltype, forLoop.zeropage, null, varName, null, null,
|
||||
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'
|
||||
} else if(existing.parent!==forLoop && existing.parent.parent!==forLoop) {
|
||||
checkResult.add(NameError("for loop var was already defined at ${existing.position}", loopVar.position))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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'
|
||||
val validName = forLoop.body.name.replace("<", "").replace(">", "").replace("-", "")
|
||||
val loopvarName = "prog8_loopvar_$validName"
|
||||
if (forLoop.iterable !is RangeExpr) {
|
||||
val existing = if (forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(listOf(loopvarName), 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, ZeropageWish.PREFER_ZEROPAGE, null, loopvarName, null, null,
|
||||
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'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,49 +228,168 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
|
||||
return super.visit(assignTarget)
|
||||
}
|
||||
|
||||
override fun visit(returnStmt: Return): IStatement {
|
||||
override fun visit(returnStmt: Return): Statement {
|
||||
if(returnStmt.value!=null) {
|
||||
// possibly adjust any literal values returned, into the desired returning data type
|
||||
val subroutine = returnStmt.definingSubroutine()!!
|
||||
if(subroutine.returntypes.size!=1)
|
||||
return returnStmt // mismatch in number of return values, error will be printed later.
|
||||
val newValue: IExpression
|
||||
val lval = returnStmt.value as? LiteralValue
|
||||
val newValue: Expression
|
||||
val lval = returnStmt.value as? NumericLiteralValue
|
||||
if(lval!=null) {
|
||||
val adjusted = lval.cast(subroutine.returntypes.single())
|
||||
if(adjusted!=null && adjusted !== lval)
|
||||
newValue = adjusted
|
||||
else
|
||||
newValue = lval
|
||||
} else
|
||||
newValue = if(adjusted!=null && adjusted !== lval) adjusted else lval
|
||||
} else {
|
||||
newValue = returnStmt.value!!
|
||||
}
|
||||
|
||||
returnStmt.value = newValue
|
||||
}
|
||||
return super.visit(returnStmt)
|
||||
}
|
||||
|
||||
|
||||
internal val anonymousVariablesFromHeap = mutableMapOf<String, Pair<LiteralValue, VarDecl>>()
|
||||
|
||||
|
||||
override fun visit(literalValue: LiteralValue): LiteralValue {
|
||||
if(literalValue.heapId!=null && literalValue.parent !is VarDecl) {
|
||||
// a literal value that's not declared as a variable, which refers to something on the heap.
|
||||
// we need to introduce an auto-generated variable for this to be able to refer to the value!
|
||||
// (note: ususally, this has been taken care of already when the var was created)
|
||||
val variable = VarDecl(VarDeclType.VAR, literalValue.type, false, null, "$autoHeapValuePrefix${literalValue.heapId}", literalValue,
|
||||
isArray = false, autoGenerated = false, position = literalValue.position)
|
||||
anonymousVariablesFromHeap[variable.name] = Pair(literalValue, variable)
|
||||
override fun visit(refLiteral: ReferenceLiteralValue): Expression {
|
||||
val litval = super.visit(refLiteral)
|
||||
if(litval is ReferenceLiteralValue) {
|
||||
val vardecl = litval.parent as? VarDecl
|
||||
if (litval.isString) {
|
||||
// intern the string; move it into the heap
|
||||
if (litval.str!!.length !in 1..255)
|
||||
checkResult.add(ExpressionError("string literal length must be between 1 and 255", litval.position))
|
||||
else {
|
||||
litval.addToHeap(program.heap)
|
||||
}
|
||||
return if(vardecl!=null)
|
||||
litval
|
||||
else
|
||||
makeIdentifierFromRefLv(litval) // replace the literal string by a identifier reference.
|
||||
} else if (litval.isArray) {
|
||||
if (vardecl!=null) {
|
||||
return fixupArrayDatatype(litval, vardecl, program.heap)
|
||||
} else {
|
||||
// fix the datatype of the array (also on the heap) to the 'biggest' datatype in the array
|
||||
// (we don't know the desired datatype here exactly so we guess)
|
||||
val datatype = determineArrayDt(litval.array!!) ?: return litval
|
||||
val litval2 = litval.cast(datatype)!!
|
||||
litval2.parent = litval.parent
|
||||
// finally, replace the literal array by a identifier reference.
|
||||
return makeIdentifierFromRefLv(litval2)
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.visit(literalValue)
|
||||
|
||||
return litval
|
||||
}
|
||||
|
||||
override fun visit(addressOf: AddressOf): IExpression {
|
||||
private fun determineArrayDt(array: Array<Expression>): DataType? {
|
||||
val datatypesInArray = array.mapNotNull { it.inferType(program) }
|
||||
if(datatypesInArray.isEmpty())
|
||||
return null
|
||||
if(DataType.FLOAT in datatypesInArray)
|
||||
return DataType.ARRAY_F
|
||||
if(DataType.WORD in datatypesInArray)
|
||||
return DataType.ARRAY_W
|
||||
if(DataType.UWORD in datatypesInArray)
|
||||
return DataType.ARRAY_UW
|
||||
if(DataType.BYTE in datatypesInArray)
|
||||
return DataType.ARRAY_B
|
||||
if(DataType.UBYTE in datatypesInArray)
|
||||
return DataType.ARRAY_UB
|
||||
return null
|
||||
}
|
||||
|
||||
private fun makeIdentifierFromRefLv(refLiteral: ReferenceLiteralValue): 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.
|
||||
refLiteral.addToHeap(program.heap)
|
||||
val scope = refLiteral.definingScope()
|
||||
var variable = VarDecl.createAuto(refLiteral, program.heap)
|
||||
val existing = scope.lookup(listOf(variable.name), refLiteral)
|
||||
variable = addVarDecl(scope, variable)
|
||||
// replace the reference literal by a identifier reference
|
||||
val identifier = IdentifierReference(listOf(variable.name), variable.position)
|
||||
identifier.parent = refLiteral.parent
|
||||
return identifier
|
||||
}
|
||||
|
||||
override fun visit(addressOf: AddressOf): Expression {
|
||||
// register the scoped name of the referenced identifier
|
||||
val variable= addressOf.identifier.targetVarDecl(namespace) ?: return addressOf
|
||||
val variable= addressOf.identifier.targetVarDecl(program.namespace) ?: return addressOf
|
||||
addressOf.scopedname = variable.scopedname
|
||||
return super.visit(addressOf)
|
||||
}
|
||||
|
||||
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 ReferenceLiteralValue ->
|
||||
processBinaryExprWithReferenceVal(expr.left as ReferenceLiteralValue, expr.right, expr)
|
||||
expr.right is ReferenceLiteralValue ->
|
||||
processBinaryExprWithReferenceVal(expr.right as ReferenceLiteralValue, expr.left, expr)
|
||||
else -> super.visit(expr)
|
||||
}
|
||||
}
|
||||
|
||||
private fun processBinaryExprWithReferenceVal(refLv: ReferenceLiteralValue, operand: Expression, expr: BinaryExpression): Expression {
|
||||
// expressions on strings or arrays
|
||||
if(refLv.isString) {
|
||||
val constvalue = operand.constValue(program)
|
||||
if(constvalue!=null) {
|
||||
if (expr.operator == "*") {
|
||||
// repeat a string a number of times
|
||||
return ReferenceLiteralValue(refLv.inferType(program),
|
||||
refLv.str!!.repeat(constvalue.number.toInt()), null, null, expr.position)
|
||||
}
|
||||
}
|
||||
if(expr.operator == "+" && operand is ReferenceLiteralValue) {
|
||||
if (operand.isString) {
|
||||
// concatenate two strings
|
||||
return ReferenceLiteralValue(refLv.inferType(program),
|
||||
"${refLv.str}${operand.str}", null, null, 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: ReferenceLiteralValue, vardecl: VarDecl, heap: HeapValues): ReferenceLiteralValue {
|
||||
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 = array.cast(vardecl.datatype)!!
|
||||
vardecl.value = litval2
|
||||
litval2.linkParents(vardecl)
|
||||
litval2.addToHeap(heap) // TODO is the previous array discarded from the resulting asm code?
|
||||
return litval2
|
||||
}
|
||||
} else {
|
||||
array.addToHeap(heap)
|
||||
}
|
||||
return array
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.base.AstException
|
||||
import prog8.ast.expressions.FunctionCall
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
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.statements.*
|
||||
|
||||
@@ -10,63 +12,67 @@ interface IAstModifyingVisitor {
|
||||
}
|
||||
|
||||
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)
|
||||
return expr
|
||||
}
|
||||
|
||||
fun visit(expr: BinaryExpression): IExpression {
|
||||
fun visit(expr: BinaryExpression): Expression {
|
||||
expr.left = expr.left.accept(this)
|
||||
expr.right = expr.right.accept(this)
|
||||
return expr
|
||||
}
|
||||
|
||||
fun visit(directive: Directive): IStatement {
|
||||
fun visit(directive: Directive): Statement {
|
||||
return directive
|
||||
}
|
||||
|
||||
fun visit(block: Block): IStatement {
|
||||
block.statements = block.statements.asSequence().map { it.accept(this) }.toMutableList()
|
||||
fun visit(block: Block): Statement {
|
||||
block.statements = block.statements.map { it.accept(this) }.toMutableList()
|
||||
return block
|
||||
}
|
||||
|
||||
fun visit(decl: VarDecl): IStatement {
|
||||
fun visit(decl: VarDecl): Statement {
|
||||
decl.value = decl.value?.accept(this)
|
||||
decl.arraysize?.accept(this)
|
||||
return decl
|
||||
}
|
||||
|
||||
fun visit(subroutine: Subroutine): IStatement {
|
||||
subroutine.statements = subroutine.statements.asSequence().map { it.accept(this) }.toMutableList()
|
||||
fun visit(subroutine: Subroutine): Statement {
|
||||
subroutine.statements = subroutine.statements.map { it.accept(this) }.toMutableList()
|
||||
return subroutine
|
||||
}
|
||||
|
||||
fun visit(functionCall: FunctionCall): IExpression {
|
||||
fun visit(functionCall: FunctionCall): Expression {
|
||||
val newtarget = functionCall.target.accept(this)
|
||||
if(newtarget is IdentifierReference)
|
||||
functionCall.target = newtarget
|
||||
else
|
||||
throw FatalAstException("cannot change class of function call target")
|
||||
functionCall.arglist = functionCall.arglist.map { it.accept(this) }.toMutableList()
|
||||
return functionCall
|
||||
}
|
||||
|
||||
fun visit(functionCallStatement: FunctionCallStatement): IStatement {
|
||||
fun visit(functionCallStatement: FunctionCallStatement): Statement {
|
||||
val newtarget = functionCallStatement.target.accept(this)
|
||||
if(newtarget is IdentifierReference)
|
||||
functionCallStatement.target = newtarget
|
||||
else
|
||||
throw FatalAstException("cannot change class of function call target")
|
||||
functionCallStatement.arglist = functionCallStatement.arglist.map { it.accept(this) }.toMutableList()
|
||||
return functionCallStatement
|
||||
}
|
||||
|
||||
fun visit(identifier: IdentifierReference): IExpression {
|
||||
fun visit(identifier: IdentifierReference): 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)
|
||||
return identifier
|
||||
}
|
||||
|
||||
fun visit(jump: Jump): IStatement {
|
||||
fun visit(jump: Jump): Statement {
|
||||
if(jump.identifier!=null) {
|
||||
val ident = jump.identifier.accept(this)
|
||||
if(ident is IdentifierReference && ident!==jump.identifier) {
|
||||
@@ -76,107 +82,123 @@ interface IAstModifyingVisitor {
|
||||
return jump
|
||||
}
|
||||
|
||||
fun visit(ifStatement: IfStatement): IStatement {
|
||||
fun visit(ifStatement: IfStatement): Statement {
|
||||
ifStatement.condition = ifStatement.condition.accept(this)
|
||||
ifStatement.truepart = ifStatement.truepart.accept(this) as AnonymousScope
|
||||
ifStatement.elsepart = ifStatement.elsepart.accept(this) as AnonymousScope
|
||||
return ifStatement
|
||||
}
|
||||
|
||||
fun visit(branchStatement: BranchStatement): IStatement {
|
||||
fun visit(branchStatement: BranchStatement): Statement {
|
||||
branchStatement.truepart = branchStatement.truepart.accept(this) as AnonymousScope
|
||||
branchStatement.elsepart = branchStatement.elsepart.accept(this) as AnonymousScope
|
||||
return branchStatement
|
||||
}
|
||||
|
||||
fun visit(range: RangeExpr): IExpression {
|
||||
fun visit(range: RangeExpr): Expression {
|
||||
range.from = range.from.accept(this)
|
||||
range.to = range.to.accept(this)
|
||||
range.step = range.step.accept(this)
|
||||
return range
|
||||
}
|
||||
|
||||
fun visit(label: Label): IStatement {
|
||||
fun visit(label: Label): Statement {
|
||||
return label
|
||||
}
|
||||
|
||||
fun visit(literalValue: LiteralValue): LiteralValue {
|
||||
if(literalValue.arrayvalue!=null) {
|
||||
for(av in literalValue.arrayvalue.withIndex()) {
|
||||
val newvalue = av.value.accept(this)
|
||||
literalValue.arrayvalue[av.index] = newvalue
|
||||
}
|
||||
}
|
||||
fun visit(literalValue: NumericLiteralValue): NumericLiteralValue {
|
||||
return literalValue
|
||||
}
|
||||
|
||||
fun visit(assignment: Assignment): IStatement {
|
||||
fun visit(refLiteral: ReferenceLiteralValue): Expression {
|
||||
if(refLiteral.array!=null) {
|
||||
for(av in refLiteral.array.withIndex()) {
|
||||
val newvalue = av.value.accept(this)
|
||||
refLiteral.array[av.index] = newvalue
|
||||
}
|
||||
}
|
||||
return refLiteral
|
||||
}
|
||||
|
||||
fun visit(assignment: Assignment): Statement {
|
||||
assignment.target = assignment.target.accept(this)
|
||||
assignment.value = assignment.value.accept(this)
|
||||
return assignment
|
||||
}
|
||||
|
||||
fun visit(postIncrDecr: PostIncrDecr): IStatement {
|
||||
fun visit(postIncrDecr: PostIncrDecr): Statement {
|
||||
postIncrDecr.target = postIncrDecr.target.accept(this)
|
||||
return postIncrDecr
|
||||
}
|
||||
|
||||
fun visit(contStmt: Continue): IStatement {
|
||||
fun visit(contStmt: Continue): Statement {
|
||||
return contStmt
|
||||
}
|
||||
|
||||
fun visit(breakStmt: Break): IStatement {
|
||||
fun visit(breakStmt: Break): Statement {
|
||||
return breakStmt
|
||||
}
|
||||
|
||||
fun visit(forLoop: ForLoop): IStatement {
|
||||
forLoop.loopVar?.accept(this)
|
||||
fun visit(forLoop: ForLoop): Statement {
|
||||
val newloopvar = forLoop.loopVar?.accept(this)
|
||||
when(newloopvar) {
|
||||
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.body = forLoop.body.accept(this) as AnonymousScope
|
||||
return forLoop
|
||||
}
|
||||
|
||||
fun visit(whileLoop: WhileLoop): IStatement {
|
||||
fun visit(whileLoop: WhileLoop): Statement {
|
||||
whileLoop.condition = whileLoop.condition.accept(this)
|
||||
whileLoop.body = whileLoop.body.accept(this) as AnonymousScope
|
||||
return whileLoop
|
||||
}
|
||||
|
||||
fun visit(repeatLoop: RepeatLoop): IStatement {
|
||||
fun visit(repeatLoop: RepeatLoop): Statement {
|
||||
repeatLoop.untilCondition = repeatLoop.untilCondition.accept(this)
|
||||
repeatLoop.body = repeatLoop.body.accept(this) as AnonymousScope
|
||||
return repeatLoop
|
||||
}
|
||||
|
||||
fun visit(returnStmt: Return): IStatement {
|
||||
fun visit(returnStmt: Return): Statement {
|
||||
returnStmt.value = returnStmt.value?.accept(this)
|
||||
return returnStmt
|
||||
}
|
||||
|
||||
fun visit(arrayIndexedExpression: ArrayIndexedExpression): IExpression {
|
||||
arrayIndexedExpression.identifier.accept(this)
|
||||
fun visit(arrayIndexedExpression: ArrayIndexedExpression): ArrayIndexedExpression {
|
||||
val ident = arrayIndexedExpression.identifier.accept(this)
|
||||
if(ident is IdentifierReference)
|
||||
arrayIndexedExpression.identifier = ident
|
||||
arrayIndexedExpression.arrayspec.accept(this)
|
||||
return arrayIndexedExpression
|
||||
}
|
||||
|
||||
fun visit(assignTarget: AssignTarget): AssignTarget {
|
||||
assignTarget.arrayindexed?.accept(this)
|
||||
assignTarget.identifier?.accept(this)
|
||||
val ident = assignTarget.identifier?.accept(this)
|
||||
when (ident) {
|
||||
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) }
|
||||
return assignTarget
|
||||
}
|
||||
|
||||
fun visit(scope: AnonymousScope): IStatement {
|
||||
scope.statements = scope.statements.asSequence().map { it.accept(this) }.toMutableList()
|
||||
fun visit(scope: AnonymousScope): Statement {
|
||||
scope.statements = scope.statements.map { it.accept(this) }.toMutableList()
|
||||
return scope
|
||||
}
|
||||
|
||||
fun visit(typecast: TypecastExpression): IExpression {
|
||||
fun visit(typecast: TypecastExpression): Expression {
|
||||
typecast.expression = typecast.expression.accept(this)
|
||||
return typecast
|
||||
}
|
||||
|
||||
fun visit(memread: DirectMemoryRead): IExpression {
|
||||
fun visit(memread: DirectMemoryRead): Expression {
|
||||
memread.addressExpression = memread.addressExpression.accept(this)
|
||||
return memread
|
||||
}
|
||||
@@ -185,35 +207,55 @@ interface IAstModifyingVisitor {
|
||||
memwrite.addressExpression = memwrite.addressExpression.accept(this)
|
||||
}
|
||||
|
||||
fun visit(addressOf: AddressOf): IExpression {
|
||||
addressOf.identifier.accept(this)
|
||||
fun visit(addressOf: AddressOf): Expression {
|
||||
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
|
||||
}
|
||||
|
||||
fun visit(inlineAssembly: InlineAssembly): IStatement {
|
||||
fun visit(inlineAssembly: InlineAssembly): Statement {
|
||||
return inlineAssembly
|
||||
}
|
||||
|
||||
fun visit(registerExpr: RegisterExpr): IExpression {
|
||||
fun visit(registerExpr: RegisterExpr): Expression {
|
||||
return registerExpr
|
||||
}
|
||||
|
||||
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder): IStatement {
|
||||
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder): Statement {
|
||||
return builtinFunctionStatementPlaceholder
|
||||
}
|
||||
|
||||
fun visit(nopStatement: NopStatement): IStatement {
|
||||
fun visit(nopStatement: NopStatement): Statement {
|
||||
return nopStatement
|
||||
}
|
||||
|
||||
fun visit(whenStatement: WhenStatement): IStatement {
|
||||
whenStatement.condition.accept(this)
|
||||
fun visit(whenStatement: WhenStatement): Statement {
|
||||
whenStatement.condition = whenStatement.condition.accept(this)
|
||||
whenStatement.choices.forEach { it.accept(this) }
|
||||
return whenStatement
|
||||
}
|
||||
|
||||
fun visit(whenChoice: WhenChoice) {
|
||||
whenChoice.values?.forEach { it.accept(this) }
|
||||
whenChoice.statements.accept(this)
|
||||
whenChoice.values = whenChoice.values?.map { it.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
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
|
||||
@@ -75,8 +76,11 @@ interface IAstVisitor {
|
||||
fun visit(label: Label) {
|
||||
}
|
||||
|
||||
fun visit(literalValue: LiteralValue) {
|
||||
literalValue.arrayvalue?.let { it.forEach { v->v.accept(this) }}
|
||||
fun visit(numLiteral: NumericLiteralValue) {
|
||||
}
|
||||
|
||||
fun visit(refLiteral: ReferenceLiteralValue) {
|
||||
refLiteral.array?.let { it.forEach { v->v.accept(this) }}
|
||||
}
|
||||
|
||||
fun visit(assignment: Assignment) {
|
||||
@@ -166,4 +170,12 @@ interface IAstVisitor {
|
||||
whenChoice.values?.forEach { it.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
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.base.SyntaxError
|
||||
import prog8.ast.base.printWarning
|
||||
import prog8.ast.statements.Directive
|
||||
import prog8.ast.statements.Statement
|
||||
|
||||
internal class ImportedModuleDirectiveRemover : IAstModifyingVisitor {
|
||||
private val checkResult: MutableList<SyntaxError> = mutableListOf()
|
||||
@@ -17,7 +18,7 @@ internal class ImportedModuleDirectiveRemover : IAstModifyingVisitor {
|
||||
*/
|
||||
override fun visit(module: Module) {
|
||||
super.visit(module)
|
||||
val newStatements : MutableList<IStatement> = mutableListOf()
|
||||
val newStatements : MutableList<Statement> = mutableListOf()
|
||||
|
||||
val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address")
|
||||
for (sourceStmt in module.statements) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import kotlin.comparisons.nullsLast
|
||||
import prog8.ast.*
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.FatalAstException
|
||||
@@ -10,6 +9,46 @@ import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.functions.BuiltinFunctions
|
||||
|
||||
|
||||
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 {
|
||||
// Reorders the statements in a way the compiler needs.
|
||||
// - 'main' block must be the very first statement UNLESS it has an address set.
|
||||
@@ -58,7 +97,7 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
|
||||
module.statements.addAll(0, directives)
|
||||
}
|
||||
|
||||
override fun visit(block: Block): IStatement {
|
||||
override fun visit(block: Block): Statement {
|
||||
|
||||
val subroutines = block.statements.filterIsInstance<Subroutine>()
|
||||
var numSubroutinesAtEnd = 0
|
||||
@@ -122,7 +161,7 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
|
||||
return super.visit(block)
|
||||
}
|
||||
|
||||
override fun visit(subroutine: Subroutine): IStatement {
|
||||
override fun visit(subroutine: Subroutine): Statement {
|
||||
super.visit(subroutine)
|
||||
|
||||
val varDecls = subroutine.statements.filterIsInstance<VarDecl>()
|
||||
@@ -147,50 +186,94 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
|
||||
return subroutine
|
||||
}
|
||||
|
||||
override fun visit(expr: BinaryExpression): IExpression {
|
||||
val leftDt = expr.left.inferType(program)
|
||||
val rightDt = expr.right.inferType(program)
|
||||
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!=null && rightDt!=null && leftDt!=rightDt) {
|
||||
// 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)
|
||||
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt, rightDt, expr2.left, expr2.right)
|
||||
if(toFix!=null) {
|
||||
when {
|
||||
toFix===expr.left -> {
|
||||
expr.left = TypecastExpression(expr.left, commonDt, true, expr.left.position)
|
||||
expr.left.linkParents(expr)
|
||||
toFix===expr2.left -> {
|
||||
expr2.left = TypecastExpression(expr2.left, commonDt, true, expr2.left.position)
|
||||
expr2.left.linkParents(expr2)
|
||||
}
|
||||
toFix===expr.right -> {
|
||||
expr.right = TypecastExpression(expr.right, commonDt, true, expr.right.position)
|
||||
expr.right.linkParents(expr)
|
||||
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 super.visit(expr)
|
||||
return expr2
|
||||
}
|
||||
|
||||
override fun visit(assignment: Assignment): IStatement {
|
||||
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 valuetype = assignment.value.inferType(program)
|
||||
val targettype = assignment.target.inferType(program, assignment)
|
||||
if(targettype!=null && valuetype!=null && valuetype!=targettype) {
|
||||
if(valuetype isAssignableTo targettype) {
|
||||
assignment.value = TypecastExpression(assignment.value, targettype, true, assignment.value.position)
|
||||
assignment.value.linkParents(assignment)
|
||||
val valuetype = assg.value.inferType(program)
|
||||
val targettype = assg.target.inferType(program, assg)
|
||||
if(targettype!=null && valuetype!=null) {
|
||||
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
|
||||
}
|
||||
// if they're not assignable, we'll get a proper error later from the AstChecker
|
||||
}
|
||||
|
||||
return super.visit(assignment)
|
||||
// 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(assg.aug_op!=null) {
|
||||
// transform augmented assg into normal assg so we have one case less to deal with later
|
||||
val newTarget: Expression =
|
||||
when {
|
||||
assg.target.register != null -> RegisterExpr(assg.target.register!!, assg.target.position)
|
||||
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)
|
||||
else -> throw FatalAstException("strange assg")
|
||||
}
|
||||
|
||||
val expression = BinaryExpression(newTarget, assg.aug_op.substringBeforeLast('='), assg.value, assg.position)
|
||||
expression.linkParents(assg.parent)
|
||||
val convertedAssignment = Assignment(assg.target, null, expression, assg.position)
|
||||
convertedAssignment.linkParents(assg.parent)
|
||||
return super.visit(convertedAssignment)
|
||||
}
|
||||
|
||||
return assg
|
||||
}
|
||||
|
||||
override fun visit(functionCallStatement: FunctionCallStatement): IStatement {
|
||||
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
|
||||
checkFunctionCallArguments(functionCallStatement, functionCallStatement.definingScope())
|
||||
return super.visit(functionCallStatement)
|
||||
}
|
||||
|
||||
override fun visit(functionCall: FunctionCall): IExpression {
|
||||
override fun visit(functionCall: FunctionCall): Expression {
|
||||
checkFunctionCallArguments(functionCall, functionCall.definingScope())
|
||||
return super.visit(functionCall)
|
||||
}
|
||||
@@ -215,7 +298,6 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
|
||||
}
|
||||
}
|
||||
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
|
||||
@@ -241,29 +323,7 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
|
||||
}
|
||||
}
|
||||
|
||||
private fun sortConstantAssignmentSequence(first: Assignment, stmtIter: MutableIterator<IStatement>): Pair<List<Assignment>, IStatement?> {
|
||||
val sequence= mutableListOf(first)
|
||||
var trailing: IStatement? = null
|
||||
while(stmtIter.hasNext()) {
|
||||
val next = stmtIter.next()
|
||||
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 {
|
||||
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)
|
||||
@@ -271,31 +331,11 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
|
||||
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)
|
||||
whenStatement.choices
|
||||
.sortWith(compareBy<WhenChoice, Int?>(nullsLast(), {it.values?.single()?.constValue(program)?.asIntegerValue}))
|
||||
return super.visit(whenStatement)
|
||||
}
|
||||
|
||||
override fun visit(memread: DirectMemoryRead): IExpression {
|
||||
override fun visit(memread: DirectMemoryRead): Expression {
|
||||
// make sure the memory address is an uword
|
||||
val dt = memread.addressExpression.inferType(program)
|
||||
if(dt!=DataType.UWORD) {
|
||||
val literaladdr = memread.addressExpression as? LiteralValue
|
||||
val literaladdr = memread.addressExpression as? NumericLiteralValue
|
||||
if(literaladdr!=null) {
|
||||
memread.addressExpression = literaladdr.cast(DataType.UWORD)!!
|
||||
} else {
|
||||
@@ -309,7 +349,7 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
|
||||
override fun visit(memwrite: DirectMemoryWrite) {
|
||||
val dt = memwrite.addressExpression.inferType(program)
|
||||
if(dt!=DataType.UWORD) {
|
||||
val literaladdr = memwrite.addressExpression as? LiteralValue
|
||||
val literaladdr = memwrite.addressExpression as? NumericLiteralValue
|
||||
if(literaladdr!=null) {
|
||||
memwrite.addressExpression = literaladdr.cast(DataType.UWORD)!!
|
||||
} else {
|
||||
@@ -319,4 +359,42 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
|
||||
}
|
||||
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 != memberDt)
|
||||
TypecastExpression(it.second, memberDt, true, it.second.position)
|
||||
else
|
||||
it.second
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,18 @@
|
||||
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.autoHeapValuePrefix
|
||||
import prog8.ast.expressions.AddressOf
|
||||
import prog8.ast.expressions.FunctionCall
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.LiteralValue
|
||||
import prog8.ast.expressions.*
|
||||
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:
|
||||
// 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.
|
||||
@@ -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).
|
||||
|
||||
private val vardeclsToAdd = mutableMapOf<INameScope, MutableMap<String, VarDecl>>()
|
||||
private val vardeclsToAdd = mutableMapOf<INameScope, MutableList<VarDecl>>()
|
||||
|
||||
override fun visit(module: Module) {
|
||||
vardeclsToAdd.clear()
|
||||
super.visit(module)
|
||||
|
||||
// add any new vardecls to the various scopes
|
||||
for(decl in vardeclsToAdd)
|
||||
for(d in decl.value) {
|
||||
d.value.linkParents(decl.key as Node)
|
||||
decl.key.statements.add(0, d.value)
|
||||
}
|
||||
for((where, decls) in vardeclsToAdd) {
|
||||
where.statements.addAll(0, decls)
|
||||
decls.forEach { it.linkParents(where as Node) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(decl: VarDecl): IStatement {
|
||||
override fun visit(decl: VarDecl): Statement {
|
||||
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 = ReferenceLiteralValue(decl.datatype, null,
|
||||
Array(arraysize) { NumericLiteralValue.optimalInteger(0, decl.position) },
|
||||
null, decl.position)
|
||||
array.addToHeap(program.heap)
|
||||
decl.value = array
|
||||
}
|
||||
|
||||
if(decl.type!= VarDeclType.VAR || decl.value==null)
|
||||
return decl
|
||||
|
||||
@@ -45,13 +57,13 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope
|
||||
addVarDecl(scope, decl.asDefaultValueDecl(null))
|
||||
val declvalue = decl.value!!
|
||||
val value =
|
||||
if(declvalue is LiteralValue) {
|
||||
if(declvalue is NumericLiteralValue) {
|
||||
val converted = declvalue.cast(decl.datatype)
|
||||
converted ?: declvalue
|
||||
}
|
||||
else
|
||||
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(
|
||||
AssignTarget(null, IdentifierReference(identifierName, decl.position), null, null, decl.position),
|
||||
null,
|
||||
@@ -59,37 +71,47 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope
|
||||
decl.position
|
||||
)
|
||||
}
|
||||
|
||||
return decl
|
||||
}
|
||||
|
||||
override fun visit(functionCall: FunctionCall): IExpression {
|
||||
val targetStatement = functionCall.target.targetSubroutine(namespace)
|
||||
override fun visit(functionCall: FunctionCall): Expression {
|
||||
var parentStatement: Node = functionCall
|
||||
while(parentStatement !is Statement)
|
||||
parentStatement = parentStatement.parent
|
||||
val targetStatement = functionCall.target.targetSubroutine(program.namespace)
|
||||
if(targetStatement!=null) {
|
||||
var node: Node = functionCall
|
||||
while(node !is IStatement)
|
||||
node=node.parent
|
||||
addAddressOfExprIfNeeded(targetStatement, functionCall.arglist, node)
|
||||
addAddressOfExprIfNeeded(targetStatement, functionCall.arglist, parentStatement)
|
||||
} else {
|
||||
val builtinFunc = BuiltinFunctions[functionCall.target.nameInSource.joinToString (".")]
|
||||
if(builtinFunc!=null)
|
||||
addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCall.arglist, parentStatement)
|
||||
}
|
||||
return functionCall
|
||||
}
|
||||
|
||||
override fun visit(functionCallStatement: FunctionCallStatement): IStatement {
|
||||
val targetStatement = functionCallStatement.target.targetSubroutine(namespace)
|
||||
if(targetStatement!=null)
|
||||
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
|
||||
val targetStatement = functionCallStatement.target.targetSubroutine(program.namespace)
|
||||
if(targetStatement!=null) {
|
||||
addAddressOfExprIfNeeded(targetStatement, functionCallStatement.arglist, functionCallStatement)
|
||||
} else {
|
||||
val builtinFunc = BuiltinFunctions[functionCallStatement.target.nameInSource.joinToString (".")]
|
||||
if(builtinFunc!=null)
|
||||
addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCallStatement.arglist, 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.
|
||||
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)
|
||||
continue
|
||||
val idref = argparam.second as? IdentifierReference
|
||||
val strvalue = argparam.second as? LiteralValue
|
||||
val strvalue = argparam.second as? ReferenceLiteralValue
|
||||
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)) {
|
||||
val pointerExpr = AddressOf(idref, idref.position)
|
||||
pointerExpr.scopedname = parent.makeScopedName(idref.nameInSource.single())
|
||||
@@ -99,27 +121,43 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope
|
||||
}
|
||||
else if(strvalue!=null) {
|
||||
if(strvalue.isString) {
|
||||
// add a vardecl so that the autovar can be resolved in later lookups
|
||||
val variable = VarDecl.createAuto(strvalue, program.heap)
|
||||
addVarDecl(strvalue.definingScope(), variable)
|
||||
// replace the argument with &autovar
|
||||
val autoVarName = "$autoHeapValuePrefix${strvalue.heapId}"
|
||||
val autoHeapvarRef = IdentifierReference(listOf(autoVarName), strvalue.position)
|
||||
val autoHeapvarRef = IdentifierReference(listOf(variable.name), strvalue.position)
|
||||
val pointerExpr = AddressOf(autoHeapvarRef, strvalue.position)
|
||||
pointerExpr.scopedname = parent.makeScopedName(autoVarName)
|
||||
pointerExpr.scopedname = parent.makeScopedName(variable.name)
|
||||
pointerExpr.linkParents(arglist[argparam.first.index].parent)
|
||||
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) {
|
||||
for(arg in args.withIndex().zip(signature.parameters)) {
|
||||
val argvalue = arg.first.value
|
||||
val argDt = argvalue.inferType(program)
|
||||
if(DataType.UWORD in arg.second.possibleDatatypes && argDt in PassByReferenceDatatypes) {
|
||||
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.scopedname = parent.makeScopedName(argvalue.nameInSource.single())
|
||||
addrOf.linkParents(parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun addVarDecl(scope: INameScope, variable: VarDecl) {
|
||||
if(scope !in vardeclsToAdd)
|
||||
vardeclsToAdd[scope] = mutableMapOf()
|
||||
vardeclsToAdd.getValue(scope)[variable.name]=variable
|
||||
vardeclsToAdd[scope] = mutableListOf()
|
||||
val declList = vardeclsToAdd.getValue(scope)
|
||||
if(declList.all{it.name!=variable.name})
|
||||
declList.add(variable)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,7 +8,38 @@ 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 fun linkParents(parent: Node) {}
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
@@ -18,14 +49,14 @@ class BuiltinFunctionStatementPlaceholder(val name: String, override val positio
|
||||
}
|
||||
|
||||
|
||||
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,
|
||||
val address: Int?,
|
||||
override var statements: MutableList<IStatement>,
|
||||
override var statements: MutableList<Statement>,
|
||||
val isInLibrary: Boolean,
|
||||
override val position: Position) : IStatement, INameScope {
|
||||
override val position: Position) : Statement(), INameScope {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline
|
||||
get() = statements.any { it.expensiveToInline }
|
||||
@@ -45,7 +76,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()
|
||||
}
|
||||
|
||||
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 val expensiveToInline = false
|
||||
|
||||
@@ -66,7 +97,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 val expensiveToInline = false
|
||||
|
||||
@@ -84,9 +115,9 @@ data class Label(val name: String, override val position: Position) : IStatement
|
||||
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 val expensiveToInline = value!=null && value !is LiteralValue
|
||||
override val expensiveToInline = value!=null && value !is NumericLiteralValue
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@@ -110,7 +141,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 val expensiveToInline = false
|
||||
|
||||
@@ -122,7 +153,7 @@ class Continue(override val position: Position) : IStatement {
|
||||
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 val expensiveToInline = false
|
||||
|
||||
@@ -134,18 +165,54 @@ class Break(override val position: Position) : IStatement {
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
}
|
||||
|
||||
|
||||
enum class ZeropageWish {
|
||||
REQUIRE_ZEROPAGE,
|
||||
PREFER_ZEROPAGE,
|
||||
DONTCARE,
|
||||
NOT_IN_ZEROPAGE
|
||||
}
|
||||
|
||||
class VarDecl(val type: VarDeclType,
|
||||
private val declaredDatatype: DataType,
|
||||
val zeropage: Boolean,
|
||||
val zeropage: ZeropageWish,
|
||||
var arraysize: ArrayIndex?,
|
||||
val name: String,
|
||||
var value: IExpression?,
|
||||
private val structName: String?,
|
||||
var value: Expression?,
|
||||
val isArray: Boolean,
|
||||
val autoGenerated: Boolean,
|
||||
override val position: Position) : IStatement {
|
||||
val autogeneratedDontRemove: Boolean,
|
||||
override val position: Position) : Statement() {
|
||||
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
|
||||
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(refLv: ReferenceLiteralValue, heap: HeapValues): VarDecl {
|
||||
if(refLv.heapId==null)
|
||||
throw FatalAstException("can only create autovar for a ref lv that has a heapid $refLv")
|
||||
|
||||
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
|
||||
return if(refLv.isArray) {
|
||||
val declaredType = ArrayElementTypes.getValue(refLv.type)
|
||||
val arraysize = ArrayIndex.forArray(refLv, heap)
|
||||
VarDecl(VarDeclType.VAR, declaredType, ZeropageWish.NOT_IN_ZEROPAGE, arraysize, autoVarName, null, refLv,
|
||||
isArray = true, autogeneratedDontRemove = true, position = refLv.position)
|
||||
} else {
|
||||
VarDecl(VarDeclType.VAR, refLv.type, ZeropageWish.NOT_IN_ZEROPAGE, null, autoVarName, null, refLv,
|
||||
isArray = false, autogeneratedDontRemove = true, position = refLv.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val datatypeErrors = mutableListOf<SyntaxError>() // don't crash at init time, report them in the AstChecker
|
||||
val datatype =
|
||||
@@ -158,7 +225,7 @@ class VarDecl(val type: VarDeclType,
|
||||
DataType.FLOAT -> DataType.ARRAY_F
|
||||
else -> {
|
||||
datatypeErrors.add(SyntaxError("array can only contain bytes/words/floats", position))
|
||||
DataType.UBYTE
|
||||
DataType.ARRAY_UB
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,6 +233,11 @@ class VarDecl(val type: VarDeclType,
|
||||
this.parent = parent
|
||||
arraysize?.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)
|
||||
@@ -174,26 +246,53 @@ class VarDecl(val type: VarDeclType,
|
||||
val scopedname: String by lazy { makeScopedName(name) }
|
||||
|
||||
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 {
|
||||
val constValue = when(declaredDatatype) {
|
||||
DataType.UBYTE -> LiteralValue(DataType.UBYTE, 0, position = position)
|
||||
DataType.BYTE -> LiteralValue(DataType.BYTE, 0, position = position)
|
||||
DataType.UWORD -> LiteralValue(DataType.UWORD, wordvalue = 0, position = position)
|
||||
DataType.WORD -> LiteralValue(DataType.WORD, wordvalue = 0, position = position)
|
||||
DataType.FLOAT -> LiteralValue(DataType.FLOAT, floatvalue = 0.0, position = position)
|
||||
DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, 0, position)
|
||||
DataType.BYTE -> NumericLiteralValue(DataType.BYTE, 0, position)
|
||||
DataType.UWORD -> NumericLiteralValue(DataType.UWORD, 0, position)
|
||||
DataType.WORD -> NumericLiteralValue(DataType.WORD, 0, position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, 0.0, position)
|
||||
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)
|
||||
decl.linkParents(parent)
|
||||
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
|
||||
}
|
||||
|
||||
fun withPrefixedName(nameprefix: String): Statement {
|
||||
val new = VarDecl(type, declaredDatatype, zeropage, arraysize, nameprefix+name, structName, value, isArray, autogeneratedDontRemove, position)
|
||||
new.parent = parent
|
||||
return new
|
||||
}
|
||||
}
|
||||
|
||||
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 fun linkParents(parent: Node) {
|
||||
@@ -202,15 +301,16 @@ class ArrayIndex(var index: IExpression, override val position: Position) : Node
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun forArray(v: LiteralValue, heap: HeapValues): ArrayIndex {
|
||||
val arraySize = v.arrayvalue?.size ?: heap.get(v.heapId!!).arraysize
|
||||
return ArrayIndex(LiteralValue.optimalNumeric(arraySize, v.position), v.position)
|
||||
fun forArray(v: ReferenceLiteralValue, heap: HeapValues): ArrayIndex {
|
||||
val arraySize = v.array?.size ?: heap.get(v.heapId!!).arraysize
|
||||
return ArrayIndex(NumericLiteralValue.optimalNumeric(arraySize, v.position), v.position)
|
||||
}
|
||||
}
|
||||
|
||||
fun accept(visitor: IAstModifyingVisitor) {
|
||||
index = index.accept(visitor)
|
||||
}
|
||||
|
||||
fun accept(visitor: IAstVisitor) {
|
||||
index.accept(visitor)
|
||||
}
|
||||
@@ -219,13 +319,13 @@ class ArrayIndex(var index: IExpression, override val position: Position) : Node
|
||||
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 val expensiveToInline
|
||||
get() = value !is LiteralValue
|
||||
get() = value !is NumericLiteralValue
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@@ -243,13 +343,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,
|
||||
// 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)
|
||||
|
||||
data class AssignTarget(val register: Register?,
|
||||
val identifier: IdentifierReference?,
|
||||
val arrayindexed: ArrayIndexedExpression?,
|
||||
var memoryAddress: DirectMemoryWrite?,
|
||||
var identifier: IdentifierReference?,
|
||||
var arrayindexed: ArrayIndexedExpression?,
|
||||
val memoryAddress: DirectMemoryWrite?,
|
||||
override val position: Position) : Node {
|
||||
override lateinit var parent: Node
|
||||
|
||||
@@ -264,7 +364,7 @@ data class AssignTarget(val register: Register?,
|
||||
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
|
||||
companion object {
|
||||
fun fromExpr(expr: IExpression): AssignTarget {
|
||||
fun fromExpr(expr: Expression): AssignTarget {
|
||||
return when (expr) {
|
||||
is RegisterExpr -> AssignTarget(expr.register, null, null, null, expr.position)
|
||||
is IdentifierReference -> AssignTarget(null, expr, null, null, expr.position)
|
||||
@@ -275,17 +375,17 @@ data class AssignTarget(val register: Register?,
|
||||
}
|
||||
}
|
||||
|
||||
fun inferType(program: Program, stmt: IStatement): DataType? {
|
||||
fun inferType(program: Program, stmt: Statement): DataType? {
|
||||
if(register!=null)
|
||||
return DataType.UBYTE
|
||||
|
||||
if(identifier!=null) {
|
||||
val symbol = program.namespace.lookup(identifier.nameInSource, stmt) ?: return null
|
||||
val symbol = program.namespace.lookup(identifier!!.nameInSource, stmt) ?: return null
|
||||
if (symbol is VarDecl) return symbol.datatype
|
||||
}
|
||||
|
||||
if(arrayindexed!=null) {
|
||||
val dt = arrayindexed.inferType(program)
|
||||
val dt = arrayindexed!!.inferType(program)
|
||||
if(dt!=null)
|
||||
return dt
|
||||
}
|
||||
@@ -296,32 +396,16 @@ data class AssignTarget(val register: Register?,
|
||||
return null
|
||||
}
|
||||
|
||||
fun shortString(withTypePrefix: Boolean=false): String {
|
||||
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 {
|
||||
infix fun isSameAs(value: Expression): Boolean {
|
||||
return when {
|
||||
this.memoryAddress!=null -> false
|
||||
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 &&
|
||||
value.identifier.nameInSource==arrayindexed.identifier.nameInSource &&
|
||||
value.identifier.nameInSource==arrayindexed!!.identifier.nameInSource &&
|
||||
value.arrayspec.size()!=null &&
|
||||
arrayindexed.arrayspec.size()!=null &&
|
||||
value.arrayspec.size()==arrayindexed.arrayspec.size()
|
||||
arrayindexed!!.arrayspec.size()!=null &&
|
||||
value.arrayspec.size()==arrayindexed!!.arrayspec.size()
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
@@ -332,16 +416,16 @@ data class AssignTarget(val register: Register?,
|
||||
if(this.register!=null && other.register!=null)
|
||||
return this.register==other.register
|
||||
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) {
|
||||
val addr1 = this.memoryAddress!!.addressExpression.constValue(program)
|
||||
val addr2 = other.memoryAddress!!.addressExpression.constValue(program)
|
||||
val addr1 = this.memoryAddress.addressExpression.constValue(program)
|
||||
val addr2 = other.memoryAddress.addressExpression.constValue(program)
|
||||
return addr1!=null && addr2!=null && addr1==addr2
|
||||
}
|
||||
if(this.arrayindexed!=null && other.arrayindexed!=null) {
|
||||
if(this.arrayindexed.identifier.nameInSource == other.arrayindexed.identifier.nameInSource) {
|
||||
val x1 = this.arrayindexed.arrayspec.index.constValue(program)
|
||||
val x2 = other.arrayindexed.arrayspec.index.constValue(program)
|
||||
if(this.arrayindexed!!.identifier.nameInSource == other.arrayindexed!!.identifier.nameInSource) {
|
||||
val x1 = this.arrayindexed!!.arrayspec.index.constValue(program)
|
||||
val x2 = other.arrayindexed!!.arrayspec.index.constValue(program)
|
||||
return x1!=null && x2!=null && x1==x2
|
||||
}
|
||||
}
|
||||
@@ -354,12 +438,12 @@ data class AssignTarget(val register: Register?,
|
||||
if(this.memoryAddress!=null)
|
||||
return false
|
||||
if(this.arrayindexed!=null) {
|
||||
val targetStmt = this.arrayindexed.identifier.targetVarDecl(namespace)
|
||||
val targetStmt = this.arrayindexed!!.identifier.targetVarDecl(namespace)
|
||||
if(targetStmt!=null)
|
||||
return targetStmt.type!= VarDeclType.MEMORY
|
||||
}
|
||||
if(this.identifier!=null) {
|
||||
val targetStmt = this.identifier.targetVarDecl(namespace)
|
||||
val targetStmt = this.identifier!!.targetVarDecl(namespace)
|
||||
if(targetStmt!=null)
|
||||
return targetStmt.type!= VarDeclType.MEMORY
|
||||
}
|
||||
@@ -367,7 +451,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 val expensiveToInline = false
|
||||
|
||||
@@ -387,7 +471,7 @@ class PostIncrDecr(var target: AssignTarget, val operator: String, override val
|
||||
class Jump(val address: Int?,
|
||||
val identifier: IdentifierReference?,
|
||||
val generatedLabel: String?, // used in code generation scenarios
|
||||
override val position: Position) : IStatement {
|
||||
override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline = false
|
||||
|
||||
@@ -405,11 +489,11 @@ class Jump(val address: Int?,
|
||||
}
|
||||
|
||||
class FunctionCallStatement(override var target: IdentifierReference,
|
||||
override var arglist: MutableList<IExpression>,
|
||||
override val position: Position) : IStatement, IFunctionCall {
|
||||
override var arglist: MutableList<Expression>,
|
||||
override val position: Position) : Statement(), IFunctionCall {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline
|
||||
get() = arglist.any { it !is LiteralValue }
|
||||
get() = arglist.any { it !is NumericLiteralValue }
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@@ -425,7 +509,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 val expensiveToInline = true
|
||||
|
||||
@@ -437,22 +521,22 @@ class InlineAssembly(val assembly: String, override val position: Position) : IS
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
}
|
||||
|
||||
class AnonymousScope(override var statements: MutableList<IStatement>,
|
||||
override val position: Position) : INameScope, IStatement {
|
||||
class AnonymousScope(override var statements: MutableList<Statement>,
|
||||
override val position: Position) : INameScope, Statement() {
|
||||
override val name: String
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline
|
||||
get() = statements.any { it.expensiveToInline }
|
||||
|
||||
companion object {
|
||||
private var sequenceNumber = 1
|
||||
}
|
||||
|
||||
init {
|
||||
name = "<anon-$sequenceNumber>" // make sure it's an invalid soruce code identifier so user source code can never produce it
|
||||
sequenceNumber++
|
||||
}
|
||||
|
||||
companion object {
|
||||
private var sequenceNumber = 1
|
||||
}
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
statements.forEach { it.linkParents(this) }
|
||||
@@ -462,7 +546,7 @@ class AnonymousScope(override var statements: MutableList<IStatement>,
|
||||
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 val expensiveToInline = false
|
||||
|
||||
@@ -474,7 +558,7 @@ class NopStatement(override val position: Position): IStatement {
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
|
||||
companion object {
|
||||
fun insteadOf(stmt: IStatement): NopStatement {
|
||||
fun insteadOf(stmt: Statement): NopStatement {
|
||||
val nop = NopStatement(stmt.position)
|
||||
nop.parent = stmt.parent
|
||||
return nop
|
||||
@@ -493,8 +577,8 @@ class Subroutine(override val name: String,
|
||||
val asmClobbers: Set<Register>,
|
||||
val asmAddress: Int?,
|
||||
val isAsmSubroutine: Boolean,
|
||||
override var statements: MutableList<IStatement>,
|
||||
override val position: Position) : IStatement, INameScope {
|
||||
override var statements: MutableList<Statement>,
|
||||
override val position: Position) : Statement(), INameScope {
|
||||
|
||||
var keepAlways: Boolean = false
|
||||
override val expensiveToInline
|
||||
@@ -570,10 +654,10 @@ open class SubroutineParameter(val name: String,
|
||||
}
|
||||
}
|
||||
|
||||
class IfStatement(var condition: IExpression,
|
||||
class IfStatement(var condition: Expression,
|
||||
var truepart: AnonymousScope,
|
||||
var elsepart: AnonymousScope,
|
||||
override val position: Position) : IStatement {
|
||||
override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline: Boolean
|
||||
get() = truepart.expensiveToInline || elsepart.expensiveToInline
|
||||
@@ -592,7 +676,7 @@ class IfStatement(var condition: IExpression,
|
||||
class BranchStatement(var condition: BranchCondition,
|
||||
var truepart: AnonymousScope,
|
||||
var elsepart: AnonymousScope,
|
||||
override val position: Position) : IStatement {
|
||||
override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline: Boolean
|
||||
get() = truepart.expensiveToInline || elsepart.expensiveToInline
|
||||
@@ -609,11 +693,11 @@ class BranchStatement(var condition: BranchCondition,
|
||||
|
||||
class ForLoop(val loopRegister: Register?,
|
||||
val decltype: DataType?,
|
||||
val zeropage: Boolean,
|
||||
val loopVar: IdentifierReference?,
|
||||
var iterable: IExpression,
|
||||
val zeropage: ZeropageWish,
|
||||
var loopVar: IdentifierReference?,
|
||||
var iterable: Expression,
|
||||
var body: AnonymousScope,
|
||||
override val position: Position) : IStatement {
|
||||
override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline = true
|
||||
|
||||
@@ -630,15 +714,11 @@ class ForLoop(val loopRegister: Register?,
|
||||
override fun toString(): String {
|
||||
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,
|
||||
override val position: Position) : IStatement {
|
||||
override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline = true
|
||||
|
||||
@@ -653,8 +733,8 @@ class WhileLoop(var condition: IExpression,
|
||||
}
|
||||
|
||||
class RepeatLoop(var body: AnonymousScope,
|
||||
var untilCondition: IExpression,
|
||||
override val position: Position) : IStatement {
|
||||
var untilCondition: Expression,
|
||||
override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline = true
|
||||
|
||||
@@ -668,9 +748,9 @@ class RepeatLoop(var body: AnonymousScope,
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
}
|
||||
|
||||
class WhenStatement(val condition: IExpression,
|
||||
val choices: MutableList<WhenChoice>,
|
||||
override val position: Position): IStatement {
|
||||
class WhenStatement(var condition: Expression,
|
||||
var choices: MutableList<WhenChoice>,
|
||||
override val position: Position): Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline: Boolean = true
|
||||
|
||||
@@ -687,7 +767,7 @@ class WhenStatement(val condition: IExpression,
|
||||
if(choice.values==null)
|
||||
result.add(null to choice)
|
||||
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))
|
||||
result.add(null to choice)
|
||||
else
|
||||
@@ -701,8 +781,8 @@ class WhenStatement(val condition: IExpression,
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
}
|
||||
|
||||
class WhenChoice(val values: List<IExpression>?, // if null, this is the 'else' part
|
||||
val statements: AnonymousScope,
|
||||
class WhenChoice(var values: List<Expression>?, // if null, this is the 'else' part
|
||||
var statements: AnonymousScope,
|
||||
override val position: Position) : Node {
|
||||
override lateinit var parent: Node
|
||||
|
||||
@@ -721,7 +801,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 fun linkParents(parent: Node) {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,13 +1,11 @@
|
||||
package prog8.compiler
|
||||
|
||||
import prog8.ast.AstToSourceCode
|
||||
import prog8.ast.Program
|
||||
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.compiler.target.c64.AsmGen
|
||||
import prog8.compiler.target.c64.C64Zeropage
|
||||
import prog8.compiler.target.c64.MachineDefinition
|
||||
import prog8.compiler.target.c64.codegen2.AsmGen2
|
||||
import prog8.optimizer.constantFold
|
||||
import prog8.optimizer.optimizeStatements
|
||||
import prog8.optimizer.simplifyExpressions
|
||||
@@ -15,20 +13,25 @@ import prog8.parser.ParsingFailedError
|
||||
import prog8.parser.importLibraryModule
|
||||
import prog8.parser.importModule
|
||||
import prog8.parser.moduleName
|
||||
import java.io.File
|
||||
import java.io.PrintStream
|
||||
import java.lang.Exception
|
||||
import java.nio.file.Path
|
||||
import kotlin.system.exitProcess
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
|
||||
class CompilationResult(val success: Boolean,
|
||||
val programAst: Program,
|
||||
val programName: String,
|
||||
val importedFiles: List<Path>)
|
||||
|
||||
|
||||
fun compileProgram(filepath: Path,
|
||||
optimize: Boolean, optimizeInlining: Boolean,
|
||||
generateVmCode: Boolean, writeVmCode: Boolean,
|
||||
writeAssembly: Boolean): Pair<Program, String?> {
|
||||
writeAssembly: Boolean): CompilationResult {
|
||||
lateinit var programAst: Program
|
||||
var programName: String? = null
|
||||
|
||||
var importedFiles: List<Path> = emptyList()
|
||||
var success=false
|
||||
|
||||
try {
|
||||
val totalTime = measureTimeMillis {
|
||||
// import main module and everything it needs
|
||||
@@ -36,6 +39,8 @@ fun compileProgram(filepath: Path,
|
||||
programAst = Program(moduleName(filepath.fileName), mutableListOf())
|
||||
importModule(programAst, filepath)
|
||||
|
||||
importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map{ it.source }
|
||||
|
||||
val compilerOptions = determineCompilationOptions(programAst)
|
||||
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
|
||||
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
|
||||
@@ -63,6 +68,7 @@ fun compileProgram(filepath: Path,
|
||||
}
|
||||
//println(" time2: $time2")
|
||||
val time3 = measureTimeMillis {
|
||||
programAst.removeNopsFlattenAnonScopes()
|
||||
programAst.reorderStatements() // reorder statements and add type casts, to please the compiler later
|
||||
}
|
||||
//println(" time3: $time3")
|
||||
@@ -84,36 +90,21 @@ fun compileProgram(filepath: Path,
|
||||
}
|
||||
}
|
||||
|
||||
programAst.removeNops()
|
||||
programAst.removeNopsFlattenAnonScopes()
|
||||
programAst.checkValid(compilerOptions) // check if final tree is valid
|
||||
programAst.checkRecursion() // check if there are recursive subroutine calls
|
||||
|
||||
// printAst(programAst)
|
||||
// namespace.debugPrint()
|
||||
printAst(programAst)
|
||||
|
||||
if(generateVmCode) {
|
||||
// compile the syntax tree into stackvmProg form, and optimize that
|
||||
val compiler = Compiler(programAst)
|
||||
val intermediate = compiler.compile(compilerOptions)
|
||||
if (optimize)
|
||||
intermediate.optimize()
|
||||
|
||||
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
|
||||
}
|
||||
if(writeAssembly) {
|
||||
// asm generation directly from the Ast, no need for intermediate code
|
||||
val zeropage = MachineDefinition.C64Zeropage(compilerOptions)
|
||||
programAst.anonscopeVarsCleanup()
|
||||
val assembly = AsmGen2(programAst, compilerOptions, zeropage).compileToAssembly(optimize)
|
||||
assembly.assemble(compilerOptions)
|
||||
programName = assembly.name
|
||||
}
|
||||
success = true
|
||||
}
|
||||
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
|
||||
|
||||
@@ -121,12 +112,10 @@ fun compileProgram(filepath: Path,
|
||||
System.err.print("\u001b[91m") // bright red
|
||||
System.err.println(px.message)
|
||||
System.err.print("\u001b[0m") // reset
|
||||
exitProcess(1)
|
||||
} catch (ax: AstException) {
|
||||
System.err.print("\u001b[91m") // bright red
|
||||
System.err.println(ax.toString())
|
||||
System.err.print("\u001b[0m") // reset
|
||||
exitProcess(1)
|
||||
} catch (x: Exception) {
|
||||
print("\u001b[91m") // bright red
|
||||
println("\n* internal error *")
|
||||
@@ -140,12 +129,12 @@ fun compileProgram(filepath: Path,
|
||||
System.out.flush()
|
||||
throw x
|
||||
}
|
||||
return Pair(programAst, programName)
|
||||
return CompilationResult(success, programAst, programName ?: "", importedFiles)
|
||||
}
|
||||
|
||||
fun printAst(programAst: Program) {
|
||||
println()
|
||||
val printer = AstToSourceCode(::print)
|
||||
val printer = AstToSourceCode(::print, programAst)
|
||||
printer.visit(programAst)
|
||||
println()
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package prog8.compiler
|
||||
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.base.printWarning
|
||||
|
||||
|
||||
class ZeropageDepletedError(message: String) : Exception(message)
|
||||
@@ -19,6 +18,9 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
||||
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"}
|
||||
|
||||
if(options.zeropage==ZeropageType.DONTUSE)
|
||||
throw CompilerException("zero page usage has been disabled")
|
||||
|
||||
val size =
|
||||
when (datatype) {
|
||||
in ByteDatatypes -> 1
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -9,10 +9,25 @@ class AssemblyProgram(val name: String) {
|
||||
private val assemblyFile = "$name.asm"
|
||||
private val viceMonListFile = "$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) {
|
||||
// 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",
|
||||
"-Werror", "-Wno-error=long-branch", "--dump-labels", "--vice-labels", "-l", viceMonListFile, "--no-monitor")
|
||||
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
|
||||
"-Wall", "-Wno-strict-bool", "-Wno-shadow", "-Werror", "-Wno-error=long-branch",
|
||||
"--dump-labels", "--vice-labels", "-l", viceMonListFile, "--no-monitor")
|
||||
|
||||
val outFile = when(options.output) {
|
||||
OutputType.PRG -> {
|
||||
@@ -41,7 +56,7 @@ class AssemblyProgram(val name: String) {
|
||||
private fun generateBreakpointList() {
|
||||
// builds list of breakpoints, appends to monitor list file
|
||||
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()) {
|
||||
val match = pattern.matchEntire(line)
|
||||
if(match!=null)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
@@ -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.codegen2
|
||||
|
||||
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>().associate { it.name to it }
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+7
-7
@@ -1,6 +1,7 @@
|
||||
package prog8.compiler.target.c64
|
||||
package prog8.compiler.target.c64.codegen2
|
||||
|
||||
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
|
||||
@@ -69,10 +70,10 @@ fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Int
|
||||
// the repeated lda can be removed
|
||||
val removeLines = mutableListOf<Int>()
|
||||
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[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
|
||||
}
|
||||
}
|
||||
@@ -84,11 +85,10 @@ fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<String>>>
|
||||
// this is a lot harder for word values because the instruction sequence varies.
|
||||
val removeLines = mutableListOf<Int>()
|
||||
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[2].value.trim()=="inx" &&
|
||||
lines[3].value.trim()=="lda ${ESTACK_LO.toHex()},x") {
|
||||
removeLines.add(lines[0].index)
|
||||
lines[3].value.trim()=="lda $ESTACK_LO_HEX,x") {
|
||||
removeLines.add(lines[1].index)
|
||||
removeLines.add(lines[2].index)
|
||||
removeLines.add(lines[3].index)
|
||||
@@ -0,0 +1,252 @@
|
||||
package prog8.compiler.target.c64.codegen2
|
||||
|
||||
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.CompilationOptions
|
||||
import prog8.compiler.Zeropage
|
||||
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 options: CompilationOptions,
|
||||
private val zeropage: Zeropage,
|
||||
private val asmgen: AsmGen2) {
|
||||
|
||||
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) !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) {
|
||||
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)
|
||||
}
|
||||
// TODO: any(f), all(f), max(f), min(f), sum(f)
|
||||
"sin", "cos", "tan", "atan",
|
||||
"ln", "log2", "sqrt", "rad",
|
||||
"deg", "round", "floor", "ceil",
|
||||
"rdnf" -> {
|
||||
translateFunctionArguments(fcall.arglist, func)
|
||||
asmgen.out(" jsr c64flt.func_$functionName")
|
||||
}
|
||||
/*
|
||||
TODO this was the old code for bit rotations:
|
||||
Opcode.SHL_BYTE -> AsmFragment(" asl $variable+$index", 8)
|
||||
Opcode.SHR_UBYTE -> AsmFragment(" lsr $variable+$index", 8)
|
||||
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.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.ROL_BYTE -> AsmFragment(" rol $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.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.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.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)
|
||||
|
||||
*/
|
||||
"lsl" -> {
|
||||
// in-place
|
||||
val what = fcall.arglist.single()
|
||||
val dt = what.inferType(program)!!
|
||||
when(dt) {
|
||||
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) {
|
||||
asmgen.out(" asl ${(what.addressExpression as NumericLiteralValue).number.toHex()}")
|
||||
} else {
|
||||
TODO("lsl memory byte $what")
|
||||
}
|
||||
}
|
||||
is ArrayIndexedExpression -> {
|
||||
TODO("lsl byte array $what")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
TODO("lsl word $what")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
"lsr" -> {
|
||||
// in-place
|
||||
val what = fcall.arglist.single()
|
||||
val dt = what.inferType(program)!!
|
||||
when(dt) {
|
||||
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) {
|
||||
asmgen.out(" lsr ${(what.addressExpression as NumericLiteralValue).number.toHex()}")
|
||||
} else {
|
||||
TODO("lsr memory byte $what")
|
||||
}
|
||||
}
|
||||
is ArrayIndexedExpression -> {
|
||||
TODO("lsr byte array $what")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
TODO("lsr sbyte $what")
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
TODO("lsr sword $what")
|
||||
}
|
||||
DataType.WORD -> {
|
||||
TODO("lsr word $what")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
"rol" -> {
|
||||
// in-place
|
||||
val what = fcall.arglist.single()
|
||||
val dt = what.inferType(program)!!
|
||||
when(dt) {
|
||||
DataType.UBYTE -> {
|
||||
TODO("rol ubyte")
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
TODO("rol uword")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
"rol2" -> {
|
||||
// in-place
|
||||
val what = fcall.arglist.single()
|
||||
val dt = what.inferType(program)!!
|
||||
when(dt) {
|
||||
DataType.UBYTE -> {
|
||||
TODO("rol2 ubyte")
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
TODO("rol2 uword")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
"ror" -> {
|
||||
// in-place
|
||||
val what = fcall.arglist.single()
|
||||
val dt = what.inferType(program)!!
|
||||
when(dt) {
|
||||
DataType.UBYTE -> {
|
||||
TODO("ror ubyte")
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
TODO("ror uword")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
"ror2" -> {
|
||||
// in-place
|
||||
val what = fcall.arglist.single()
|
||||
val dt = what.inferType(program)!!
|
||||
when(dt) {
|
||||
DataType.UBYTE -> {
|
||||
TODO("ror2 ubyte")
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
TODO("ror2 uword")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
translateFunctionArguments(fcall.arglist, func)
|
||||
asmgen.out(" jsr prog8_lib.func_$functionName")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateFunctionArguments(args: MutableList<Expression>, signature: FunctionSignature) {
|
||||
args.forEach {
|
||||
asmgen.translateExpression(it)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
package prog8.functions
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.DirectMemoryRead
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.LiteralValue
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.compiler.CompilerException
|
||||
import kotlin.math.*
|
||||
|
||||
|
||||
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?
|
||||
val parameters: List<BuiltinFunctionParam>,
|
||||
val returntype: DataType?,
|
||||
val constExpressionFunc: ((args: List<IExpression>, position: Position, program: Program) -> LiteralValue)? = null)
|
||||
val constExpressionFunc: ConstExpressionCaller? = null)
|
||||
|
||||
|
||||
val BuiltinFunctions = mapOf(
|
||||
@@ -27,9 +28,9 @@ val BuiltinFunctions = mapOf(
|
||||
"lsl" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null),
|
||||
"lsr" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null),
|
||||
// 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
|
||||
"min" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArgOutputNumber(a, p, prg) { it.min()!! }}, // 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
|
||||
"max" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, _ -> collectionArgNeverConst(a, p) }, // type depends on args
|
||||
"min" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, _ -> collectionArgNeverConst(a, p) }, // type depends on args
|
||||
"sum" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, _ -> collectionArgNeverConst(a, p) }, // type depends on args
|
||||
"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
|
||||
// normal functions follow:
|
||||
@@ -51,12 +52,12 @@ val BuiltinFunctions = mapOf(
|
||||
"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) },
|
||||
"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),
|
||||
"avg" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.FLOAT) { a, p, _ -> collectionArgNeverConst(a, p) },
|
||||
"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) },
|
||||
"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} }},
|
||||
"all" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArgOutputBoolean(a, p, prg) { it.all { v -> v != 0.0} }},
|
||||
"any" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, _ -> collectionArgNeverConst(a, p) },
|
||||
"all" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, _ -> collectionArgNeverConst(a, p) },
|
||||
"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}},
|
||||
"mkword" to FunctionSignature(true, listOf(
|
||||
@@ -112,12 +113,12 @@ val BuiltinFunctions = mapOf(
|
||||
)
|
||||
|
||||
|
||||
fun builtinFunctionReturnType(function: String, args: List<IExpression>, program: Program): DataType? {
|
||||
fun builtinFunctionReturnType(function: String, args: List<Expression>, program: Program): DataType? {
|
||||
|
||||
fun datatypeFromIterableArg(arglist: IExpression): DataType {
|
||||
if(arglist is LiteralValue) {
|
||||
fun datatypeFromIterableArg(arglist: Expression): DataType {
|
||||
if(arglist is ReferenceLiteralValue) {
|
||||
if(arglist.type== DataType.ARRAY_UB || arglist.type== DataType.ARRAY_UW || arglist.type== DataType.ARRAY_F) {
|
||||
val dt = arglist.arrayvalue!!.map {it.inferType(program)}
|
||||
val dt = arglist.array!!.map {it.inferType(program)}
|
||||
if(dt.any { it!= DataType.UBYTE && it!= DataType.UWORD && it!= DataType.FLOAT}) {
|
||||
throw FatalAstException("fuction $function only accepts arraysize of numeric values")
|
||||
}
|
||||
@@ -144,12 +145,11 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, program
|
||||
|
||||
return when (function) {
|
||||
"abs" -> {
|
||||
when(val dt = args.single().inferType(program)) {
|
||||
in ByteDatatypes -> DataType.UBYTE
|
||||
in WordDatatypes -> DataType.UWORD
|
||||
DataType.FLOAT -> DataType.FLOAT
|
||||
else -> throw FatalAstException("weird datatype passed to abs $dt")
|
||||
}
|
||||
val dt = args.single().inferType(program)
|
||||
if(dt in NumericDatatypes)
|
||||
return dt
|
||||
else
|
||||
throw FatalAstException("weird datatype passed to abs $dt")
|
||||
}
|
||||
"max", "min" -> {
|
||||
when(val dt = datatypeFromIterableArg(args.single())) {
|
||||
@@ -184,181 +184,98 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, program
|
||||
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)
|
||||
throw SyntaxError("built-in function requires one floating point argument", position)
|
||||
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)
|
||||
}
|
||||
|
||||
private fun getFloatArg(v: LiteralValue, position: Position): Double {
|
||||
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 {
|
||||
private fun oneDoubleArgOutputWord(args: List<Expression>, position: Position, program: Program, function: (arg: Double)->Number): NumericLiteralValue {
|
||||
if(args.size!=1)
|
||||
throw SyntaxError("built-in function requires one floating point argument", position)
|
||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
val float = getFloatArg(constval, args[0].position)
|
||||
return LiteralValue(DataType.WORD, wordvalue = function(float).toInt(), position = args[0].position)
|
||||
val float = constval.number.toDouble()
|
||||
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)
|
||||
throw SyntaxError("built-in function requires one integer argument", position)
|
||||
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)
|
||||
|
||||
val integer = constval.asNumericValue?.toInt()!!
|
||||
val integer = constval.number.toInt()
|
||||
return numericLiteral(function(integer).toInt(), args[0].position)
|
||||
}
|
||||
|
||||
private fun collectionArgOutputNumber(args: List<IExpression>, position: Position,
|
||||
program: Program,
|
||||
function: (arg: Collection<Double>)->Number): LiteralValue {
|
||||
private fun collectionArgNeverConst(args: List<Expression>, position: Position): NumericLiteralValue {
|
||||
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() }).toDouble()
|
||||
} else {
|
||||
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)
|
||||
// max/min/sum etc only work on arrays and these are never considered to be const for these functions
|
||||
throw NotConstArgumentException()
|
||||
}
|
||||
|
||||
private fun collectionArgOutputBoolean(args: List<IExpression>, position: Position,
|
||||
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 {
|
||||
private fun builtinAbs(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||
// 1 arg, type = float or int, result type= isSameAs as argument type
|
||||
if(args.size!=1)
|
||||
throw SyntaxError("abs requires one numeric argument", position)
|
||||
|
||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
return when (val number = constval.asNumericValue) {
|
||||
is Int, is Byte, is Short -> numericLiteral(abs(number.toInt()), args[0].position)
|
||||
is Double -> numericLiteral(abs(number.toDouble()), args[0].position)
|
||||
return when (constval.type) {
|
||||
in IntegerDatatypes -> numericLiteral(abs(constval.number.toInt()), args[0].position)
|
||||
DataType.FLOAT -> numericLiteral(abs(constval.number.toDouble()), args[0].position)
|
||||
else -> throw SyntaxError("abs requires one numeric argument", position)
|
||||
}
|
||||
}
|
||||
|
||||
private fun builtinAvg(args: List<IExpression>, position: Position, program: Program): LiteralValue {
|
||||
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 {
|
||||
private fun builtinStrlen(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||
if (args.size != 1)
|
||||
throw SyntaxError("strlen requires one argument", position)
|
||||
val argument = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
if(argument.type !in StringDatatypes)
|
||||
throw SyntaxError("strlen must have string argument", position)
|
||||
val string = argument.strvalue!!
|
||||
val zeroIdx = string.indexOf('\u0000')
|
||||
return if(zeroIdx>=0)
|
||||
LiteralValue.optimalInteger(zeroIdx, position=position)
|
||||
else
|
||||
LiteralValue.optimalInteger(string.length, position=position)
|
||||
|
||||
throw NotConstArgumentException() // this function is not considering the string argument a constant
|
||||
}
|
||||
|
||||
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.
|
||||
if(args.size!=1)
|
||||
throw SyntaxError("len requires one argument", position)
|
||||
var argument = args[0].constValue(program)
|
||||
if(argument==null) {
|
||||
val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program.namespace)
|
||||
val arraySize = directMemVar?.arraysize?.size()
|
||||
if(arraySize != null)
|
||||
return LiteralValue.optimalInteger(arraySize, position)
|
||||
if(args[0] !is IdentifierReference)
|
||||
throw SyntaxError("len argument should be an identifier, but is ${args[0]}", position)
|
||||
val target = (args[0] as IdentifierReference).targetStatement(program.namespace)
|
||||
val argValue = (target as? VarDecl)?.value
|
||||
argument = argValue?.constValue(program)
|
||||
?: throw NotConstArgumentException()
|
||||
}
|
||||
return when(argument.type) {
|
||||
val constArg = args[0].constValue(program)
|
||||
if(constArg!=null)
|
||||
throw SyntaxError("len of weird argument ${args[0]}", position)
|
||||
|
||||
val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program.namespace)
|
||||
var arraySize = directMemVar?.arraysize?.size()
|
||||
if(arraySize != null)
|
||||
return NumericLiteralValue.optimalInteger(arraySize, position)
|
||||
if(args[0] !is IdentifierReference)
|
||||
throw SyntaxError("len argument should be an identifier, but is ${args[0]}", position)
|
||||
val target = (args[0] as IdentifierReference).targetVarDecl(program.namespace)!!
|
||||
|
||||
return when(target.datatype) {
|
||||
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)
|
||||
throw CompilerException("array length exceeds byte limit ${argument.position}")
|
||||
LiteralValue.optimalInteger(arraySize, args[0].position)
|
||||
throw CompilerException("array length exceeds byte limit ${target.position}")
|
||||
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
val arraySize = argument.arrayvalue?.size ?: program.heap.get(argument.heapId!!).arraysize
|
||||
arraySize = target.arraysize!!.size()!!
|
||||
if(arraySize>256)
|
||||
throw CompilerException("array length exceeds byte limit ${argument.position}")
|
||||
LiteralValue.optimalInteger(arraySize, args[0].position)
|
||||
throw CompilerException("array length exceeds byte limit ${target.position}")
|
||||
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
|
||||
}
|
||||
in StringDatatypes -> {
|
||||
val str = argument.strvalue!!
|
||||
if(str.length>255)
|
||||
throw CompilerException("string length exceeds byte limit ${argument.position}")
|
||||
LiteralValue.optimalInteger(str.length, args[0].position)
|
||||
val refLv = target.value as ReferenceLiteralValue
|
||||
if(refLv.str!!.length>255)
|
||||
throw CompilerException("string length exceeds byte limit ${refLv.position}")
|
||||
NumericLiteralValue.optimalInteger(refLv.str.length, args[0].position)
|
||||
}
|
||||
in NumericDatatypes -> throw SyntaxError("len of weird argument ${args[0]}", position)
|
||||
else -> throw CompilerException("weird datatype")
|
||||
@@ -366,80 +283,80 @@ 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)
|
||||
throw SyntaxError("mkword requires lsb and msb arguments", position)
|
||||
val constLsb = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
val constMsb = args[1].constValue(program) ?: throw NotConstArgumentException()
|
||||
val result = (constMsb.asIntegerValue!! shl 8) or constLsb.asIntegerValue!!
|
||||
return LiteralValue(DataType.UWORD, wordvalue = result, position = position)
|
||||
val result = (constMsb.number.toInt() shl 8) or constLsb.number.toInt()
|
||||
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)
|
||||
throw SyntaxError("sin8 requires one argument", position)
|
||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
|
||||
return LiteralValue(DataType.BYTE, bytevalue = (127.0 * sin(rad)).toShort(), position = position)
|
||||
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||
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)
|
||||
throw SyntaxError("sin8u requires one argument", position)
|
||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
|
||||
return LiteralValue(DataType.UBYTE, bytevalue = (128.0 + 127.5 * sin(rad)).toShort(), position = position)
|
||||
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||
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)
|
||||
throw SyntaxError("cos8 requires one argument", position)
|
||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
|
||||
return LiteralValue(DataType.BYTE, bytevalue = (127.0 * cos(rad)).toShort(), position = position)
|
||||
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||
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)
|
||||
throw SyntaxError("cos8u requires one argument", position)
|
||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
|
||||
return LiteralValue(DataType.UBYTE, bytevalue = (128.0 + 127.5 * cos(rad)).toShort(), position = position)
|
||||
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||
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)
|
||||
throw SyntaxError("sin16 requires one argument", position)
|
||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
|
||||
return LiteralValue(DataType.WORD, wordvalue = (32767.0 * sin(rad)).toInt(), position = position)
|
||||
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||
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)
|
||||
throw SyntaxError("sin16u requires one argument", position)
|
||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
|
||||
return LiteralValue(DataType.UWORD, wordvalue = (32768.0 + 32767.5 * sin(rad)).toInt(), position = position)
|
||||
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||
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)
|
||||
throw SyntaxError("cos16 requires one argument", position)
|
||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
|
||||
return LiteralValue(DataType.WORD, wordvalue = (32767.0 * cos(rad)).toInt(), position = position)
|
||||
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||
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)
|
||||
throw SyntaxError("cos16u requires one argument", position)
|
||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
|
||||
return LiteralValue(DataType.UWORD, wordvalue = (32768.0 + 32767.5 * cos(rad)).toInt(), position = position)
|
||||
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||
return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * cos(rad)).toInt(), position)
|
||||
}
|
||||
|
||||
private fun numericLiteral(value: Number, position: Position): LiteralValue {
|
||||
private fun numericLiteral(value: Number, position: Position): NumericLiteralValue {
|
||||
val floatNum=value.toDouble()
|
||||
val tweakedValue: Number =
|
||||
if(floatNum== floor(floatNum) && (floatNum>=-32768 && floatNum<=65535))
|
||||
@@ -448,11 +365,11 @@ private fun numericLiteral(value: Number, position: Position): LiteralValue {
|
||||
floatNum
|
||||
|
||||
return when(tweakedValue) {
|
||||
is Int -> LiteralValue.optimalNumeric(value.toInt(), position)
|
||||
is Short -> LiteralValue.optimalNumeric(value.toInt(), position)
|
||||
is Byte -> LiteralValue(DataType.UBYTE, bytevalue = value.toShort(), position = position)
|
||||
is Double -> LiteralValue(DataType.FLOAT, floatvalue = value.toDouble(), position = position)
|
||||
is Float -> LiteralValue(DataType.FLOAT, floatvalue = value.toDouble(), position = position)
|
||||
is Int -> NumericLiteralValue.optimalNumeric(value.toInt(), position)
|
||||
is Short -> NumericLiteralValue.optimalNumeric(value.toInt(), position)
|
||||
is Byte -> NumericLiteralValue(DataType.UBYTE, value.toShort(), position)
|
||||
is Double -> NumericLiteralValue(DataType.FLOAT, value.toDouble(), position)
|
||||
is Float -> NumericLiteralValue(DataType.FLOAT, value.toDouble(), position)
|
||||
else -> throw FatalAstException("invalid number type ${value::class}")
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
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.VarDeclType
|
||||
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 subroutinesCalling = mutableMapOf<INameScope, List<Subroutine>>().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 {
|
||||
visit(program)
|
||||
@@ -92,11 +97,11 @@ class CallGraph(private val program: Program): IAstVisitor {
|
||||
super.visit(identifier)
|
||||
}
|
||||
|
||||
private fun addNodeAndParentScopes(stmt: IStatement) {
|
||||
private fun addNodeAndParentScopes(stmt: Statement) {
|
||||
usedSymbols.add(stmt)
|
||||
var node: Node=stmt
|
||||
do {
|
||||
if(node is INameScope && node is IStatement) {
|
||||
if(node is INameScope && node is Statement) {
|
||||
usedSymbols.add(node)
|
||||
}
|
||||
node=node.parent
|
||||
@@ -106,7 +111,8 @@ class CallGraph(private val program: Program): IAstVisitor {
|
||||
override fun visit(subroutine: Subroutine) {
|
||||
val alwaysKeepSubroutines = setOf(
|
||||
Pair("main", "start"),
|
||||
Pair("irq", "irq")
|
||||
Pair("irq", "irq"),
|
||||
Pair("prog8_lib", "init_system")
|
||||
)
|
||||
|
||||
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) {
|
||||
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
|
||||
addNodeAndParentScopes(decl)
|
||||
}
|
||||
|
||||
if(decl.datatype==DataType.STRUCT)
|
||||
addNodeAndParentScopes(decl)
|
||||
|
||||
super.visit(decl)
|
||||
}
|
||||
|
||||
@@ -158,6 +168,11 @@ class CallGraph(private val program: Program): IAstVisitor {
|
||||
super.visit(jump)
|
||||
}
|
||||
|
||||
override fun visit(structDecl: StructDecl) {
|
||||
usedSymbols.add(structDecl)
|
||||
usedSymbols.addAll(structDecl.statements)
|
||||
}
|
||||
|
||||
override fun visit(inlineAssembly: InlineAssembly) {
|
||||
// parse inline asm for subroutine calls (jmp, jsr)
|
||||
val scope = inlineAssembly.definingScope()
|
||||
@@ -165,7 +180,7 @@ class CallGraph(private val program: Program): IAstVisitor {
|
||||
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 asmRefRx = Regex("""[\-+a-zA-Z0-9_ \t]+(...)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
|
||||
asm.lines().forEach { line ->
|
||||
@@ -191,10 +206,12 @@ class CallGraph(private val program: Program): IAstVisitor {
|
||||
if (matches2 != null) {
|
||||
val target= matches2.groups[2]?.value
|
||||
if (target != null && (target[0].isLetter() || target[0] == '_')) {
|
||||
val node = program.namespace.lookup(listOf(target.substringBefore('.')), context)
|
||||
if (node is Subroutine) {
|
||||
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node)
|
||||
subroutinesCalledBy[node] = subroutinesCalledBy.getValue(node).plus(context)
|
||||
if(target.contains('.')) {
|
||||
val node = program.namespace.lookup(listOf(target.substringBefore('.')), context)
|
||||
if (node is Subroutine) {
|
||||
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node)
|
||||
subroutinesCalledBy[node] = subroutinesCalledBy.getValue(node).plus(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.ExpressionError
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.LiteralValue
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import kotlin.math.pow
|
||||
|
||||
|
||||
class ConstExprEvaluator {
|
||||
|
||||
fun evaluate(left: LiteralValue, operator: String, right: LiteralValue): IExpression {
|
||||
fun evaluate(left: NumericLiteralValue, operator: String, right: NumericLiteralValue): Expression {
|
||||
return when(operator) {
|
||||
"+" -> plus(left, right)
|
||||
"-" -> minus(left, right)
|
||||
@@ -25,200 +22,188 @@ class ConstExprEvaluator {
|
||||
"and" -> logicaland(left, right)
|
||||
"or" -> logicalor(left, right)
|
||||
"xor" -> logicalxor(left, right)
|
||||
"<" -> LiteralValue.fromBoolean(left < right, left.position)
|
||||
">" -> LiteralValue.fromBoolean(left > right, left.position)
|
||||
"<=" -> LiteralValue.fromBoolean(left <= right, left.position)
|
||||
">=" -> LiteralValue.fromBoolean(left >= right, left.position)
|
||||
"==" -> LiteralValue.fromBoolean(left == right, left.position)
|
||||
"!=" -> LiteralValue.fromBoolean(left != right, left.position)
|
||||
"<" -> NumericLiteralValue.fromBoolean(left < right, left.position)
|
||||
">" -> NumericLiteralValue.fromBoolean(left > right, left.position)
|
||||
"<=" -> NumericLiteralValue.fromBoolean(left <= right, left.position)
|
||||
">=" -> NumericLiteralValue.fromBoolean(left >= right, left.position)
|
||||
"==" -> NumericLiteralValue.fromBoolean(left == right, left.position)
|
||||
"!=" -> NumericLiteralValue.fromBoolean(left != right, left.position)
|
||||
"<<" -> shiftedleft(left, right)
|
||||
">>" -> shiftedright(left, right)
|
||||
else -> throw FatalAstException("const evaluation for invalid operator $operator")
|
||||
}
|
||||
}
|
||||
|
||||
private fun shiftedright(left: LiteralValue, amount: LiteralValue): IExpression {
|
||||
if(left.asIntegerValue==null || amount.asIntegerValue==null)
|
||||
private fun shiftedright(left: NumericLiteralValue, amount: NumericLiteralValue): Expression {
|
||||
if(left.type !in IntegerDatatypes || amount.type !in IntegerDatatypes)
|
||||
throw ExpressionError("cannot compute $left >> $amount", left.position)
|
||||
val result =
|
||||
if(left.type== DataType.UBYTE || left.type== DataType.UWORD)
|
||||
left.asIntegerValue.ushr(amount.asIntegerValue)
|
||||
left.number.toInt().ushr(amount.number.toInt())
|
||||
else
|
||||
left.asIntegerValue.shr(amount.asIntegerValue)
|
||||
return LiteralValue.fromNumber(result, left.type, left.position)
|
||||
left.number.toInt().shr(amount.number.toInt())
|
||||
return NumericLiteralValue(left.type, result, left.position)
|
||||
}
|
||||
|
||||
private fun shiftedleft(left: LiteralValue, amount: LiteralValue): IExpression {
|
||||
if(left.asIntegerValue==null || amount.asIntegerValue==null)
|
||||
private fun shiftedleft(left: NumericLiteralValue, amount: NumericLiteralValue): Expression {
|
||||
if(left.type !in IntegerDatatypes || amount.type !in IntegerDatatypes)
|
||||
throw ExpressionError("cannot compute $left << $amount", left.position)
|
||||
val result = left.asIntegerValue.shl(amount.asIntegerValue)
|
||||
return LiteralValue.fromNumber(result, left.type, left.position)
|
||||
val result = left.number.toInt().shl(amount.number.toInt())
|
||||
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"
|
||||
return when {
|
||||
left.asIntegerValue!=null -> when {
|
||||
right.asIntegerValue!=null -> LiteralValue.fromBoolean((left.asIntegerValue != 0) xor (right.asIntegerValue != 0), left.position)
|
||||
right.floatvalue!=null -> LiteralValue.fromBoolean((left.asIntegerValue != 0) xor (right.floatvalue != 0.0), left.position)
|
||||
left.type in IntegerDatatypes -> when {
|
||||
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number.toInt() != 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)
|
||||
}
|
||||
left.floatvalue!=null -> when {
|
||||
right.asIntegerValue!=null -> LiteralValue.fromBoolean((left.floatvalue != 0.0) xor (right.asIntegerValue != 0), left.position)
|
||||
right.floatvalue!=null -> LiteralValue.fromBoolean((left.floatvalue != 0.0) xor (right.floatvalue != 0.0), left.position)
|
||||
left.type == DataType.FLOAT -> when {
|
||||
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toDouble() != 0.0) xor (right.number.toInt() != 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)
|
||||
}
|
||||
}
|
||||
|
||||
private fun logicalor(left: LiteralValue, right: LiteralValue): LiteralValue {
|
||||
private fun logicalor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||
val error = "cannot compute $left locical-or $right"
|
||||
return when {
|
||||
left.asIntegerValue!=null -> when {
|
||||
right.asIntegerValue!=null -> LiteralValue.fromBoolean(left.asIntegerValue != 0 || right.asIntegerValue != 0, left.position)
|
||||
right.floatvalue!=null -> LiteralValue.fromBoolean(left.asIntegerValue != 0 || right.floatvalue != 0.0, left.position)
|
||||
left.type in IntegerDatatypes -> when {
|
||||
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number.toInt() != 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)
|
||||
}
|
||||
left.floatvalue!=null -> when {
|
||||
right.asIntegerValue!=null -> LiteralValue.fromBoolean(left.floatvalue != 0.0 || right.asIntegerValue != 0, left.position)
|
||||
right.floatvalue!=null -> LiteralValue.fromBoolean(left.floatvalue != 0.0 || right.floatvalue != 0.0, left.position)
|
||||
left.type == DataType.FLOAT -> when {
|
||||
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 || right.number.toInt() != 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)
|
||||
}
|
||||
}
|
||||
|
||||
private fun logicaland(left: LiteralValue, right: LiteralValue): LiteralValue {
|
||||
private fun logicaland(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||
val error = "cannot compute $left locical-and $right"
|
||||
return when {
|
||||
left.asIntegerValue!=null -> when {
|
||||
right.asIntegerValue!=null -> LiteralValue.fromBoolean(left.asIntegerValue != 0 && right.asIntegerValue != 0, left.position)
|
||||
right.floatvalue!=null -> LiteralValue.fromBoolean(left.asIntegerValue != 0 && right.floatvalue != 0.0, left.position)
|
||||
left.type in IntegerDatatypes -> when {
|
||||
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number.toInt() != 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)
|
||||
}
|
||||
left.floatvalue!=null -> when {
|
||||
right.asIntegerValue!=null -> LiteralValue.fromBoolean(left.floatvalue != 0.0 && right.asIntegerValue != 0, left.position)
|
||||
right.floatvalue!=null -> LiteralValue.fromBoolean(left.floatvalue != 0.0 && right.floatvalue != 0.0, left.position)
|
||||
left.type == DataType.FLOAT -> when {
|
||||
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 && right.number.toInt() != 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)
|
||||
}
|
||||
}
|
||||
|
||||
private fun bitwisexor(left: LiteralValue, right: LiteralValue): LiteralValue {
|
||||
private fun bitwisexor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||
if(left.type== DataType.UBYTE) {
|
||||
if(right.asIntegerValue!=null) {
|
||||
return LiteralValue(DataType.UBYTE, bytevalue = (left.bytevalue!!.toInt() xor (right.asIntegerValue and 255)).toShort(), position = left.position)
|
||||
if(right.type in IntegerDatatypes) {
|
||||
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() xor (right.number.toInt() and 255)).toShort(), left.position)
|
||||
}
|
||||
} else if(left.type== DataType.UWORD) {
|
||||
if(right.asIntegerValue!=null) {
|
||||
return LiteralValue(DataType.UWORD, wordvalue = left.wordvalue!! xor right.asIntegerValue, position = left.position)
|
||||
if(right.type in IntegerDatatypes) {
|
||||
return NumericLiteralValue(DataType.UWORD, left.number.toInt() xor right.number.toInt(), 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(right.asIntegerValue!=null) {
|
||||
return LiteralValue(DataType.UBYTE, bytevalue = (left.bytevalue!!.toInt() or (right.asIntegerValue and 255)).toShort(), position = left.position)
|
||||
if(right.type in IntegerDatatypes) {
|
||||
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() or (right.number.toInt() and 255)).toShort(), left.position)
|
||||
}
|
||||
} else if(left.type== DataType.UWORD) {
|
||||
if(right.asIntegerValue!=null) {
|
||||
return LiteralValue(DataType.UWORD, wordvalue = left.wordvalue!! or right.asIntegerValue, position = left.position)
|
||||
if(right.type in IntegerDatatypes) {
|
||||
return NumericLiteralValue(DataType.UWORD, left.number.toInt() or right.number.toInt(), 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(right.asIntegerValue!=null) {
|
||||
return LiteralValue(DataType.UBYTE, bytevalue = (left.bytevalue!!.toInt() or (right.asIntegerValue and 255)).toShort(), position = left.position)
|
||||
if(right.type in IntegerDatatypes) {
|
||||
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() or (right.number.toInt() and 255)).toShort(), left.position)
|
||||
}
|
||||
} else if(left.type== DataType.UWORD) {
|
||||
if(right.asIntegerValue!=null) {
|
||||
return LiteralValue(DataType.UWORD, wordvalue = left.wordvalue!! or right.asIntegerValue, position = left.position)
|
||||
if(right.type in IntegerDatatypes) {
|
||||
return NumericLiteralValue(DataType.UWORD, left.number.toInt() or right.number.toInt(), 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"
|
||||
return when {
|
||||
left.asIntegerValue!=null -> when {
|
||||
right.asIntegerValue!=null -> LiteralValue.optimalNumeric(left.asIntegerValue.toDouble().pow(right.asIntegerValue), left.position)
|
||||
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.asIntegerValue.toDouble().pow(right.floatvalue), position = left.position)
|
||||
left.type in IntegerDatatypes -> when {
|
||||
right.type in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble().pow(right.number.toInt()), 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)
|
||||
}
|
||||
left.floatvalue!=null -> when {
|
||||
right.asIntegerValue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue.pow(right.asIntegerValue), position = left.position)
|
||||
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue.pow(right.floatvalue), position = left.position)
|
||||
left.type == DataType.FLOAT -> when {
|
||||
right.type in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble().pow(right.number.toInt()), 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)
|
||||
}
|
||||
}
|
||||
|
||||
private fun plus(left: LiteralValue, right: LiteralValue): LiteralValue {
|
||||
private fun plus(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||
val error = "cannot add $left and $right"
|
||||
return when {
|
||||
left.asIntegerValue!=null -> when {
|
||||
right.asIntegerValue!=null -> LiteralValue.optimalNumeric(left.asIntegerValue + right.asIntegerValue, left.position)
|
||||
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.asIntegerValue + right.floatvalue, position = left.position)
|
||||
left.type in IntegerDatatypes -> when {
|
||||
right.type in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() + right.number.toInt(), left.position)
|
||||
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() + right.number.toDouble(), left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
left.floatvalue!=null -> when {
|
||||
right.asIntegerValue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue + right.asIntegerValue, position = left.position)
|
||||
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue + right.floatvalue, position = 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)
|
||||
}
|
||||
left.type == DataType.FLOAT -> when {
|
||||
right.type in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() + right.number.toInt(), 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)
|
||||
}
|
||||
}
|
||||
|
||||
private fun minus(left: LiteralValue, right: LiteralValue): LiteralValue {
|
||||
private fun minus(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||
val error = "cannot subtract $left and $right"
|
||||
return when {
|
||||
left.asIntegerValue!=null -> when {
|
||||
right.asIntegerValue!=null -> LiteralValue.optimalNumeric(left.asIntegerValue - right.asIntegerValue, left.position)
|
||||
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.asIntegerValue - right.floatvalue, position = left.position)
|
||||
left.type in IntegerDatatypes -> when {
|
||||
right.type in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() - right.number.toInt(), left.position)
|
||||
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() - right.number.toDouble(), left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
left.floatvalue!=null -> when {
|
||||
right.asIntegerValue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue - right.asIntegerValue, position = left.position)
|
||||
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue - right.floatvalue, position = left.position)
|
||||
left.type == DataType.FLOAT -> when {
|
||||
right.type in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() - right.number.toInt(), 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)
|
||||
}
|
||||
}
|
||||
|
||||
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}"
|
||||
return when {
|
||||
left.asIntegerValue!=null -> when {
|
||||
right.asIntegerValue!=null -> LiteralValue.optimalNumeric(left.asIntegerValue * right.asIntegerValue, left.position)
|
||||
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.asIntegerValue * right.floatvalue, position = 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)
|
||||
}
|
||||
left.type in IntegerDatatypes -> when {
|
||||
right.type in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() * right.number.toInt(), left.position)
|
||||
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() * right.number.toDouble(), left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
left.floatvalue!=null -> when {
|
||||
right.asIntegerValue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue * right.asIntegerValue, position = left.position)
|
||||
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue * right.floatvalue, position = left.position)
|
||||
left.type == DataType.FLOAT -> when {
|
||||
right.type in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() * right.number.toInt(), 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)
|
||||
@@ -228,29 +213,29 @@ class ConstExprEvaluator {
|
||||
private fun divideByZeroError(pos: Position): Unit =
|
||||
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"
|
||||
return when {
|
||||
left.asIntegerValue!=null -> when {
|
||||
right.asIntegerValue!=null -> {
|
||||
if(right.asIntegerValue==0) divideByZeroError(right.position)
|
||||
val result: Int = left.asIntegerValue / right.asIntegerValue
|
||||
LiteralValue.optimalNumeric(result, left.position)
|
||||
left.type in IntegerDatatypes -> when {
|
||||
right.type in IntegerDatatypes -> {
|
||||
if(right.number.toInt()==0) divideByZeroError(right.position)
|
||||
val result: Int = left.number.toInt() / right.number.toInt()
|
||||
NumericLiteralValue.optimalNumeric(result, left.position)
|
||||
}
|
||||
right.floatvalue!=null -> {
|
||||
if(right.floatvalue==0.0) divideByZeroError(right.position)
|
||||
LiteralValue(DataType.FLOAT, floatvalue = left.asIntegerValue / right.floatvalue, position = left.position)
|
||||
right.type == DataType.FLOAT -> {
|
||||
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number.toInt() / right.number.toDouble(), left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
left.floatvalue!=null -> when {
|
||||
right.asIntegerValue!=null -> {
|
||||
if(right.asIntegerValue==0) divideByZeroError(right.position)
|
||||
LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue / right.asIntegerValue, position = left.position)
|
||||
left.type == DataType.FLOAT -> when {
|
||||
right.type in IntegerDatatypes -> {
|
||||
if(right.number.toInt()==0) divideByZeroError(right.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() / right.number.toInt(), left.position)
|
||||
}
|
||||
right.floatvalue!=null -> {
|
||||
if(right.floatvalue==0.0) divideByZeroError(right.position)
|
||||
LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue / right.floatvalue, position = left.position)
|
||||
right.type == DataType.FLOAT -> {
|
||||
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() / right.number.toDouble(), 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"
|
||||
return when {
|
||||
left.asIntegerValue!=null -> when {
|
||||
right.asIntegerValue!=null -> {
|
||||
if(right.asIntegerValue==0) divideByZeroError(right.position)
|
||||
LiteralValue.optimalNumeric(left.asIntegerValue.toDouble() % right.asIntegerValue.toDouble(), left.position)
|
||||
left.type in IntegerDatatypes -> when {
|
||||
right.type in IntegerDatatypes -> {
|
||||
if(right.number.toInt()==0) divideByZeroError(right.position)
|
||||
NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble() % right.number.toInt().toDouble(), left.position)
|
||||
}
|
||||
right.floatvalue!=null -> {
|
||||
if(right.floatvalue==0.0) divideByZeroError(right.position)
|
||||
LiteralValue(DataType.FLOAT, floatvalue = left.asIntegerValue % right.floatvalue, position = left.position)
|
||||
right.type == DataType.FLOAT -> {
|
||||
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number.toInt() % right.number.toDouble(), left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
left.floatvalue!=null -> when {
|
||||
right.asIntegerValue!=null -> {
|
||||
if(right.asIntegerValue==0) divideByZeroError(right.position)
|
||||
LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue % right.asIntegerValue, position = left.position)
|
||||
left.type == DataType.FLOAT -> when {
|
||||
right.type in IntegerDatatypes -> {
|
||||
if(right.number.toInt()==0) divideByZeroError(right.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() % right.number.toInt(), left.position)
|
||||
}
|
||||
right.floatvalue!=null -> {
|
||||
if(right.floatvalue==0.0) divideByZeroError(right.position)
|
||||
LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue % right.floatvalue, position = left.position)
|
||||
right.type == DataType.FLOAT -> {
|
||||
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() % right.number.toDouble(), left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.processing.IAstModifyingVisitor
|
||||
import prog8.ast.processing.fixupArrayDatatype
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.HeapValues
|
||||
import prog8.compiler.IntegerOrAddressOf
|
||||
import prog8.compiler.target.c64.FLOAT_MAX_NEGATIVE
|
||||
import prog8.compiler.target.c64.FLOAT_MAX_POSITIVE
|
||||
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_NEGATIVE
|
||||
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_POSITIVE
|
||||
import prog8.functions.BuiltinFunctions
|
||||
import kotlin.math.floor
|
||||
|
||||
|
||||
class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
|
||||
var optimizationsDone: Int = 0
|
||||
var errors : MutableList<AstException> = mutableListOf()
|
||||
|
||||
private val reportedErrorMessages = mutableSetOf<String>()
|
||||
|
||||
fun addError(x: AstException) {
|
||||
@@ -26,30 +26,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)
|
||||
// TODO: use call tree for this?
|
||||
if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true) {
|
||||
errors.add(ExpressionError("recursive var declaration", decl.position))
|
||||
return decl
|
||||
}
|
||||
|
||||
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){
|
||||
// for arrays that have no size specifier (or a non-constant one) attempt to deduce the size
|
||||
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? ReferenceLiteralValue)?.array
|
||||
if(arrayval!=null) {
|
||||
decl.arraysize = ArrayIndex(LiteralValue.optimalInteger(arrayval.size, decl.position), decl.position)
|
||||
decl.arraysize = ArrayIndex(NumericLiteralValue.optimalInteger(arrayval.size, decl.position), decl.position)
|
||||
optimizationsDone++
|
||||
}
|
||||
}
|
||||
else if(decl.arraysize?.size()==null) {
|
||||
val size = decl.arraysize!!.index.accept(this)
|
||||
if(size is LiteralValue) {
|
||||
if(size is NumericLiteralValue) {
|
||||
decl.arraysize = ArrayIndex(size, decl.position)
|
||||
optimizationsDone++
|
||||
}
|
||||
@@ -59,17 +56,25 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
|
||||
when(decl.datatype) {
|
||||
DataType.FLOAT -> {
|
||||
// vardecl: for scalar float vars, promote constant integer initialization values to floats
|
||||
if (litval != null && litval.type in IntegerDatatypes) {
|
||||
val newValue = LiteralValue(DataType.FLOAT, floatvalue = litval.asNumericValue!!.toDouble(), position = litval.position)
|
||||
val litval = decl.value as? NumericLiteralValue
|
||||
if (litval!=null && litval.type in IntegerDatatypes) {
|
||||
val newValue = NumericLiteralValue(DataType.FLOAT, litval.number.toDouble(), litval.position)
|
||||
decl.value = newValue
|
||||
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 -> {
|
||||
val numericLv = decl.value as? NumericLiteralValue
|
||||
val rangeExpr = decl.value as? RangeExpr
|
||||
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()
|
||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||
errors.add(ExpressionError("range expression size doesn't match declared array size", decl.value?.position!!))
|
||||
@@ -77,69 +82,74 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
|
||||
if(constRange!=null) {
|
||||
val eltType = rangeExpr.inferType(program)!!
|
||||
if(eltType in ByteDatatypes) {
|
||||
decl.value = LiteralValue(decl.datatype,
|
||||
arrayvalue = constRange.map { LiteralValue(eltType, bytevalue = it.toShort(), position = decl.value!!.position) }
|
||||
decl.value = ReferenceLiteralValue(decl.datatype,
|
||||
array = constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) }
|
||||
.toTypedArray(), position = decl.value!!.position)
|
||||
} else {
|
||||
decl.value = LiteralValue(decl.datatype,
|
||||
arrayvalue = constRange.map { LiteralValue(eltType, wordvalue = it, position = decl.value!!.position) }
|
||||
decl.value = ReferenceLiteralValue(decl.datatype,
|
||||
array = constRange.map { NumericLiteralValue(eltType, it, decl.value!!.position) }
|
||||
.toTypedArray(), position = decl.value!!.position)
|
||||
}
|
||||
decl.value!!.linkParents(decl)
|
||||
optimizationsDone++
|
||||
return decl
|
||||
return super.visit(decl)
|
||||
}
|
||||
}
|
||||
if(litval?.type== DataType.FLOAT)
|
||||
errors.add(ExpressionError("arraysize requires only integers here", litval.position))
|
||||
if(numericLv!=null && numericLv.type== DataType.FLOAT)
|
||||
errors.add(ExpressionError("arraysize requires only integers here", numericLv.position))
|
||||
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.
|
||||
val fillvalue = if (litval == null) 0 else litval.asIntegerValue ?: 0
|
||||
val fillvalue = numericLv.number.toInt()
|
||||
when(decl.datatype){
|
||||
DataType.ARRAY_UB -> {
|
||||
if(fillvalue !in 0..255)
|
||||
errors.add(ExpressionError("ubyte value overflow", litval?.position
|
||||
?: decl.position))
|
||||
errors.add(ExpressionError("ubyte value overflow", numericLv.position))
|
||||
}
|
||||
DataType.ARRAY_B -> {
|
||||
if(fillvalue !in -128..127)
|
||||
errors.add(ExpressionError("byte value overflow", litval?.position
|
||||
?: decl.position))
|
||||
errors.add(ExpressionError("byte value overflow", numericLv.position))
|
||||
}
|
||||
DataType.ARRAY_UW -> {
|
||||
if(fillvalue !in 0..65535)
|
||||
errors.add(ExpressionError("uword value overflow", litval?.position
|
||||
?: decl.position))
|
||||
errors.add(ExpressionError("uword value overflow", numericLv.position))
|
||||
}
|
||||
DataType.ARRAY_W -> {
|
||||
if(fillvalue !in -32768..32767)
|
||||
errors.add(ExpressionError("word value overflow", litval?.position
|
||||
?: decl.position))
|
||||
errors.add(ExpressionError("word value overflow", numericLv.position))
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
val heapId = program.heap.addIntegerArray(decl.datatype, Array(size) { IntegerOrAddressOf(fillvalue, null) })
|
||||
decl.value = LiteralValue(decl.datatype, initHeapId = heapId, position = litval?.position
|
||||
?: decl.position)
|
||||
// create the array itself, filled with the fillvalue.
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteralValue.optimalInteger(it, numericLv.position) as Expression}.toTypedArray()
|
||||
val refValue = ReferenceLiteralValue(decl.datatype, array = array, position = numericLv.position)
|
||||
refValue.addToHeap(program.heap)
|
||||
decl.value = refValue
|
||||
refValue.parent=decl
|
||||
optimizationsDone++
|
||||
return decl
|
||||
return super.visit(decl)
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
val size = decl.arraysize?.size() ?: return decl
|
||||
if (litval==null || !litval.isArray) {
|
||||
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
|
||||
val fillvalue = if (litval == null) 0.0 else litval.asNumericValue?.toDouble() ?: 0.0
|
||||
if(fillvalue< FLOAT_MAX_NEGATIVE || fillvalue> FLOAT_MAX_POSITIVE)
|
||||
errors.add(ExpressionError("float value overflow", litval?.position
|
||||
?: decl.position))
|
||||
val litval = decl.value as? NumericLiteralValue
|
||||
if(litval==null) {
|
||||
// there's no initialization value, but the size is known, so we're ok.
|
||||
return super.visit(decl)
|
||||
} else {
|
||||
// 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 {
|
||||
val heapId = program.heap.addDoublesArray(DoubleArray(size) { fillvalue })
|
||||
decl.value = LiteralValue(DataType.ARRAY_F, initHeapId = heapId, position = litval?.position
|
||||
?: decl.position)
|
||||
// create the array itself, filled with the fillvalue.
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteralValue(DataType.FLOAT, it, litval.position) as Expression}.toTypedArray()
|
||||
val refValue = ReferenceLiteralValue(DataType.ARRAY_F, array = array, position = litval.position)
|
||||
refValue.addToHeap(program.heap)
|
||||
decl.value = refValue
|
||||
refValue.parent=decl
|
||||
optimizationsDone++
|
||||
return decl
|
||||
return super.visit(decl)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,52 +162,28 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
|
||||
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)
|
||||
*/
|
||||
override fun visit(identifier: IdentifierReference): IExpression {
|
||||
override fun visit(identifier: IdentifierReference): Expression {
|
||||
return try {
|
||||
val cval = identifier.constValue(program) ?: return identifier
|
||||
return if(cval.isNumeric) {
|
||||
val copy = LiteralValue(cval.type, cval.bytevalue, cval.wordvalue, cval.floatvalue, null, cval.arrayvalue, position = identifier.position)
|
||||
copy.parent = identifier.parent
|
||||
copy
|
||||
} else
|
||||
identifier
|
||||
return when {
|
||||
cval.type in NumericDatatypes -> {
|
||||
val copy = NumericLiteralValue(cval.type, cval.number, identifier.position)
|
||||
copy.parent = identifier.parent
|
||||
copy
|
||||
}
|
||||
cval.type in PassByReferenceDatatypes -> TODO("ref type $identifier")
|
||||
else -> identifier
|
||||
}
|
||||
} catch (ax: AstException) {
|
||||
addError(ax)
|
||||
identifier
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(functionCall: FunctionCall): IExpression {
|
||||
override fun visit(functionCall: FunctionCall): Expression {
|
||||
return try {
|
||||
super.visit(functionCall)
|
||||
typeCastConstArguments(functionCall)
|
||||
@@ -208,13 +194,32 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(functionCallStatement: FunctionCallStatement): IStatement {
|
||||
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
|
||||
super.visit(functionCallStatement)
|
||||
typeCastConstArguments(functionCallStatement)
|
||||
return functionCallStatement
|
||||
}
|
||||
|
||||
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())
|
||||
if(convertedValue!=null) {
|
||||
functionCall.arglist[arg.first.index] = convertedValue
|
||||
optimizationsDone++
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
// match the arguments of a subroutine.
|
||||
val subroutine = functionCall.target.targetSubroutine(program.namespace)
|
||||
if(subroutine!=null) {
|
||||
// if types differ, try to typecast constant arguments to the function call to the desired data type of the parameter
|
||||
@@ -232,7 +237,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(memread: DirectMemoryRead): IExpression {
|
||||
override fun visit(memread: DirectMemoryRead): Expression {
|
||||
// @( &thing ) --> thing
|
||||
val addrOf = memread.addressExpression as? AddressOf
|
||||
if(addrOf!=null)
|
||||
@@ -245,48 +250,43 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
|
||||
* 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
|
||||
*/
|
||||
override fun visit(expr: PrefixExpression): IExpression {
|
||||
override fun visit(expr: PrefixExpression): Expression {
|
||||
return try {
|
||||
super.visit(expr)
|
||||
val prefixExpr=super.visit(expr)
|
||||
if(prefixExpr !is PrefixExpression)
|
||||
return prefixExpr
|
||||
|
||||
val subexpr = expr.expression
|
||||
if (subexpr is LiteralValue) {
|
||||
val subexpr = prefixExpr.expression
|
||||
if (subexpr is NumericLiteralValue) {
|
||||
// accept prefixed literal values (such as -3, not true)
|
||||
return when {
|
||||
expr.operator == "+" -> subexpr
|
||||
expr.operator == "-" -> when {
|
||||
subexpr.asIntegerValue!= null -> {
|
||||
prefixExpr.operator == "+" -> subexpr
|
||||
prefixExpr.operator == "-" -> when {
|
||||
subexpr.type in IntegerDatatypes -> {
|
||||
optimizationsDone++
|
||||
LiteralValue.optimalNumeric(-subexpr.asIntegerValue, subexpr.position)
|
||||
NumericLiteralValue.optimalNumeric(-subexpr.number.toInt(), subexpr.position)
|
||||
}
|
||||
subexpr.floatvalue != null -> {
|
||||
subexpr.type == DataType.FLOAT -> {
|
||||
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)
|
||||
}
|
||||
expr.operator == "~" -> when {
|
||||
subexpr.asIntegerValue != null -> {
|
||||
prefixExpr.operator == "~" -> when {
|
||||
subexpr.type in IntegerDatatypes -> {
|
||||
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)
|
||||
}
|
||||
expr.operator == "not" -> when {
|
||||
subexpr.asIntegerValue != null -> {
|
||||
optimizationsDone++
|
||||
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)
|
||||
prefixExpr.operator == "not" -> {
|
||||
optimizationsDone++
|
||||
NumericLiteralValue.fromBoolean(subexpr.number.toDouble() == 0.0, subexpr.position)
|
||||
}
|
||||
else -> throw ExpressionError(expr.operator, subexpr.position)
|
||||
else -> throw ExpressionError(prefixExpr.operator, subexpr.position)
|
||||
}
|
||||
}
|
||||
return expr
|
||||
return prefixExpr
|
||||
} catch (ax: AstException) {
|
||||
addError(ax)
|
||||
expr
|
||||
@@ -310,9 +310,13 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
|
||||
* (X / c1) * c2 -> X / (c2/c1)
|
||||
* (X + c1) - c2 -> X + (c1-c2)
|
||||
*/
|
||||
override fun visit(expr: BinaryExpression): IExpression {
|
||||
override fun visit(expr: BinaryExpression): Expression {
|
||||
return try {
|
||||
super.visit(expr)
|
||||
|
||||
if(expr.left is ReferenceLiteralValue || expr.right is ReferenceLiteralValue)
|
||||
TODO("binexpr with reference litval")
|
||||
|
||||
val leftconst = expr.left.constValue(program)
|
||||
val rightconst = expr.right.constValue(program)
|
||||
|
||||
@@ -333,12 +337,13 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
|
||||
}
|
||||
|
||||
// const fold when both operands are a const
|
||||
val evaluator = ConstExprEvaluator()
|
||||
return when {
|
||||
leftconst != null && rightconst != null -> {
|
||||
optimizationsDone++
|
||||
val evaluator = ConstExprEvaluator()
|
||||
evaluator.evaluate(leftconst, expr.operator, rightconst)
|
||||
}
|
||||
|
||||
else -> expr
|
||||
}
|
||||
} catch (ax: AstException) {
|
||||
@@ -352,7 +357,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
|
||||
leftIsConst: Boolean,
|
||||
rightIsConst: Boolean,
|
||||
subleftIsConst: Boolean,
|
||||
subrightIsConst: Boolean): IExpression
|
||||
subrightIsConst: Boolean): Expression
|
||||
{
|
||||
// @todo this implements only a small set of possible reorderings for now
|
||||
if(expr.operator==subExpr.operator) {
|
||||
@@ -539,13 +544,13 @@ 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 newTo = rangeTo.cast(targetDt)
|
||||
if (newFrom != null && newTo != null) {
|
||||
val newStep: IExpression =
|
||||
val newStep: Expression =
|
||||
if (stepLiteral != null) (stepLiteral.cast(targetDt) ?: stepLiteral) else range.step
|
||||
return RangeExpr(newFrom, newTo, newStep, range.position)
|
||||
}
|
||||
@@ -555,13 +560,13 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
|
||||
// adjust the datatype of a range expression in for loops to the loop variable.
|
||||
val resultStmt = super.visit(forLoop) as ForLoop
|
||||
val iterableRange = resultStmt.iterable as? RangeExpr ?: return resultStmt
|
||||
val rangeFrom = iterableRange.from as? LiteralValue
|
||||
val rangeTo = iterableRange.to as? LiteralValue
|
||||
val rangeFrom = iterableRange.from as? NumericLiteralValue
|
||||
val rangeTo = iterableRange.to as? NumericLiteralValue
|
||||
if(rangeFrom==null || rangeTo==null) return resultStmt
|
||||
|
||||
val loopvar = resultStmt.loopVar?.targetVarDecl(program.namespace)
|
||||
if(loopvar!=null) {
|
||||
val stepLiteral = iterableRange.step as? LiteralValue
|
||||
val stepLiteral = iterableRange.step as? NumericLiteralValue
|
||||
when(loopvar.datatype) {
|
||||
DataType.UBYTE -> {
|
||||
if(rangeFrom.type!= DataType.UBYTE) {
|
||||
@@ -593,123 +598,77 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
|
||||
return resultStmt
|
||||
}
|
||||
|
||||
override fun visit(literalValue: LiteralValue): LiteralValue {
|
||||
val litval = super.visit(literalValue)
|
||||
if(litval.isString) {
|
||||
// intern the string; move it into the heap
|
||||
if(litval.strvalue!!.length !in 1..255)
|
||||
addError(ExpressionError("string literal length must be between 1 and 255", litval.position))
|
||||
else {
|
||||
litval.addToHeap(program.heap) // TODO: we don't know the actual string type yet, STR != STR_S etc...
|
||||
}
|
||||
} else if(litval.arrayvalue!=null) {
|
||||
// first, adjust the array datatype
|
||||
val litval2 = adjustArrayValDatatype(litval)
|
||||
litval2.addToHeap(program.heap)
|
||||
return litval2
|
||||
}
|
||||
return litval
|
||||
}
|
||||
|
||||
private fun adjustArrayValDatatype(litval: LiteralValue): LiteralValue {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun visit(refLiteral: ReferenceLiteralValue): Expression {
|
||||
val litval = super.visit(refLiteral)
|
||||
if(litval is ReferenceLiteralValue) {
|
||||
if (litval.isArray) {
|
||||
val vardecl = litval.parent as? VarDecl
|
||||
if (vardecl!=null) {
|
||||
return fixupArrayDatatype(litval, vardecl, program.heap)
|
||||
}
|
||||
|
||||
if(arrayDt!=litval.type) {
|
||||
return LiteralValue(arrayDt, arrayvalue = litval.arrayvalue, position = litval.position)
|
||||
}
|
||||
}
|
||||
return litval
|
||||
}
|
||||
|
||||
override fun visit(assignment: Assignment): IStatement {
|
||||
override fun visit(assignment: Assignment): Statement {
|
||||
super.visit(assignment)
|
||||
val lv = assignment.value as? LiteralValue
|
||||
val lv = assignment.value as? NumericLiteralValue
|
||||
if(lv!=null) {
|
||||
// see if we can promote/convert a literal value to the required datatype
|
||||
when(assignment.target.inferType(program, assignment)) {
|
||||
DataType.UWORD -> {
|
||||
// we can convert to UWORD: any UBYTE, BYTE/WORD that are >=0, FLOAT that's an integer 0..65535,
|
||||
if(lv.type== DataType.UBYTE)
|
||||
assignment.value = LiteralValue(DataType.UWORD, wordvalue = lv.asIntegerValue, position = lv.position)
|
||||
else if(lv.type== DataType.BYTE && lv.bytevalue!!>=0)
|
||||
assignment.value = LiteralValue(DataType.UWORD, wordvalue = lv.asIntegerValue, position = lv.position)
|
||||
else if(lv.type== DataType.WORD && lv.wordvalue!!>=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.BYTE && lv.number.toInt()>=0)
|
||||
assignment.value = NumericLiteralValue(DataType.UWORD, lv.number.toInt(), lv.position)
|
||||
else if(lv.type== DataType.WORD && lv.number.toInt()>=0)
|
||||
assignment.value = NumericLiteralValue(DataType.UWORD, lv.number.toInt(), lv.position)
|
||||
else if(lv.type== DataType.FLOAT) {
|
||||
val d = lv.floatvalue!!
|
||||
val d = lv.number.toDouble()
|
||||
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 -> {
|
||||
// we can convert to UBYTE: UWORD <=255, BYTE >=0, FLOAT that's an integer 0..255,
|
||||
if(lv.type== DataType.UWORD && lv.wordvalue!! <= 255)
|
||||
assignment.value = LiteralValue(DataType.UBYTE, lv.wordvalue.toShort(), position = lv.position)
|
||||
else if(lv.type== DataType.BYTE && lv.bytevalue!! >=0)
|
||||
assignment.value = LiteralValue(DataType.UBYTE, lv.bytevalue.toShort(), position = lv.position)
|
||||
if(lv.type== DataType.UWORD && lv.number.toInt() <= 255)
|
||||
assignment.value = NumericLiteralValue(DataType.UBYTE, lv.number.toShort(), lv.position)
|
||||
else if(lv.type== DataType.BYTE && lv.number.toInt() >=0)
|
||||
assignment.value = NumericLiteralValue(DataType.UBYTE, lv.number.toShort(), lv.position)
|
||||
else if(lv.type== DataType.FLOAT) {
|
||||
val d = lv.floatvalue!!
|
||||
val d = lv.number.toDouble()
|
||||
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 -> {
|
||||
// we can convert to BYTE: UWORD/UBYTE <= 127, FLOAT that's an integer 0..127
|
||||
if(lv.type== DataType.UWORD && lv.wordvalue!! <= 127)
|
||||
assignment.value = LiteralValue(DataType.BYTE, lv.wordvalue.toShort(), position = lv.position)
|
||||
else if(lv.type== DataType.UBYTE && lv.bytevalue!! <= 127)
|
||||
assignment.value = LiteralValue(DataType.BYTE, lv.bytevalue, position = lv.position)
|
||||
if(lv.type== DataType.UWORD && lv.number.toInt() <= 127)
|
||||
assignment.value = NumericLiteralValue(DataType.BYTE, lv.number.toShort(), lv.position)
|
||||
else if(lv.type== DataType.UBYTE && lv.number.toInt() <= 127)
|
||||
assignment.value = NumericLiteralValue(DataType.BYTE, lv.number.toShort(), lv.position)
|
||||
else if(lv.type== DataType.FLOAT) {
|
||||
val d = lv.floatvalue!!
|
||||
val d = lv.number.toDouble()
|
||||
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 -> {
|
||||
// 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)
|
||||
assignment.value = LiteralValue(DataType.WORD, wordvalue = lv.bytevalue!!.toInt(), position = lv.position)
|
||||
else if(lv.type== DataType.UWORD && lv.wordvalue!! <= 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.UWORD && lv.number.toInt() <= 32767)
|
||||
assignment.value = NumericLiteralValue(DataType.WORD, lv.number.toInt(), lv.position)
|
||||
else if(lv.type== DataType.FLOAT) {
|
||||
val d = lv.floatvalue!!
|
||||
val d = lv.number.toDouble()
|
||||
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 -> {
|
||||
if(lv.isNumeric)
|
||||
assignment.value = LiteralValue(DataType.FLOAT, floatvalue = lv.asNumericValue?.toDouble(), position = lv.position)
|
||||
assignment.value = NumericLiteralValue(DataType.FLOAT, lv.number.toDouble(), lv.position)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.AstException
|
||||
import prog8.ast.statements.NopStatement
|
||||
import prog8.parser.ParsingFailedError
|
||||
|
||||
|
||||
@@ -31,14 +30,6 @@ internal fun Program.constantFold() {
|
||||
internal fun Program.optimizeStatements(optimizeInlining: Boolean): Int {
|
||||
val optimizer = StatementOptimizer(this, optimizeInlining)
|
||||
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
|
||||
|
||||
return optimizer.optimizationsDone
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.AstException
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.IntegerDatatypes
|
||||
@@ -8,6 +8,7 @@ import prog8.ast.base.NumericDatatypes
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.processing.IAstModifyingVisitor
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.Statement
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.log2
|
||||
|
||||
@@ -21,13 +22,13 @@ import kotlin.math.log2
|
||||
internal class SimplifyExpressions(private val program: Program) : IAstModifyingVisitor {
|
||||
var optimizationsDone: Int = 0
|
||||
|
||||
override fun visit(assignment: Assignment): IStatement {
|
||||
override fun visit(assignment: Assignment): Statement {
|
||||
if (assignment.aug_op != null)
|
||||
throw AstException("augmented assignments should have been converted to normal assignments before this optimizer")
|
||||
return super.visit(assignment)
|
||||
}
|
||||
|
||||
override fun visit(memread: DirectMemoryRead): IExpression {
|
||||
override fun visit(memread: DirectMemoryRead): Expression {
|
||||
// @( &thing ) --> thing
|
||||
val addrOf = memread.addressExpression as? AddressOf
|
||||
if(addrOf!=null)
|
||||
@@ -35,11 +36,11 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
||||
return super.visit(memread)
|
||||
}
|
||||
|
||||
override fun visit(typecast: TypecastExpression): IExpression {
|
||||
override fun visit(typecast: TypecastExpression): Expression {
|
||||
var tc = typecast
|
||||
|
||||
// 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) {
|
||||
val newLiteral = literal.cast(tc.type)
|
||||
if(newLiteral!=null && newLiteral!==literal) {
|
||||
@@ -81,7 +82,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(expr: PrefixExpression): IExpression {
|
||||
override fun visit(expr: PrefixExpression): Expression {
|
||||
if (expr.operator == "+") {
|
||||
// +X --> X
|
||||
optimizationsDone++
|
||||
@@ -128,12 +129,12 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
||||
return super.visit(expr)
|
||||
}
|
||||
|
||||
override fun visit(expr: BinaryExpression): IExpression {
|
||||
override fun visit(expr: BinaryExpression): Expression {
|
||||
super.visit(expr)
|
||||
val leftVal = expr.left.constValue(program)
|
||||
val rightVal = expr.right.constValue(program)
|
||||
val constTrue = LiteralValue.fromBoolean(true, expr.position)
|
||||
val constFalse = LiteralValue.fromBoolean(false, expr.position)
|
||||
val constTrue = NumericLiteralValue.fromBoolean(true, expr.position)
|
||||
val constFalse = NumericLiteralValue.fromBoolean(false, expr.position)
|
||||
|
||||
val leftDt = expr.left.inferType(program)
|
||||
val rightDt = expr.right.inferType(program)
|
||||
@@ -174,10 +175,10 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
||||
|
||||
// X + (-value) --> X - value
|
||||
if (expr.operator == "+" && rightVal != null) {
|
||||
val rv = rightVal.asNumericValue?.toDouble()
|
||||
if (rv != null && rv < 0.0) {
|
||||
val rv = rightVal.number.toDouble()
|
||||
if (rv < 0.0) {
|
||||
expr.operator = "-"
|
||||
expr.right = LiteralValue.fromNumber(-rv, rightVal.type, rightVal.position)
|
||||
expr.right = NumericLiteralValue(rightVal.type, -rv, rightVal.position)
|
||||
optimizationsDone++
|
||||
return expr
|
||||
}
|
||||
@@ -185,10 +186,10 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
||||
|
||||
// (-value) + X --> X - value
|
||||
if (expr.operator == "+" && leftVal != null) {
|
||||
val lv = leftVal.asNumericValue?.toDouble()
|
||||
if (lv != null && lv < 0.0) {
|
||||
val lv = leftVal.number.toDouble()
|
||||
if (lv < 0.0) {
|
||||
expr.operator = "-"
|
||||
expr.right = LiteralValue.fromNumber(-lv, leftVal.type, leftVal.position)
|
||||
expr.right = NumericLiteralValue(leftVal.type, -lv, leftVal.position)
|
||||
optimizationsDone++
|
||||
return expr
|
||||
}
|
||||
@@ -204,10 +205,10 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
||||
|
||||
// X - (-value) --> X + value
|
||||
if (expr.operator == "-" && rightVal != null) {
|
||||
val rv = rightVal.asNumericValue?.toDouble()
|
||||
if (rv != null && rv < 0.0) {
|
||||
val rv = rightVal.number.toDouble()
|
||||
if (rv < 0.0) {
|
||||
expr.operator = "+"
|
||||
expr.right = LiteralValue.fromNumber(-rv, rightVal.type, rightVal.position)
|
||||
expr.right = NumericLiteralValue(rightVal.type, -rv, rightVal.position)
|
||||
optimizationsDone++
|
||||
return expr
|
||||
}
|
||||
@@ -225,7 +226,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
||||
val x = expr.right
|
||||
val y = determineY(x, leftBinExpr)
|
||||
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)
|
||||
}
|
||||
} else {
|
||||
@@ -234,7 +235,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
||||
val x = expr.right
|
||||
val y = determineY(x, leftBinExpr)
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -246,7 +247,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
||||
val x = expr.left
|
||||
val y = determineY(x, rightBinExpr)
|
||||
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)
|
||||
}
|
||||
} else {
|
||||
@@ -255,7 +256,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
||||
val x = expr.left
|
||||
val y = determineY(x, rightBinExpr)
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -341,7 +342,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
||||
return expr
|
||||
}
|
||||
|
||||
private fun determineY(x: IExpression, subBinExpr: BinaryExpression): IExpression? {
|
||||
private fun determineY(x: Expression, subBinExpr: BinaryExpression): Expression? {
|
||||
return when {
|
||||
subBinExpr.left isSameAs x -> subBinExpr.right
|
||||
subBinExpr.right isSameAs x -> subBinExpr.left
|
||||
@@ -350,58 +351,58 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
||||
}
|
||||
|
||||
private fun adjustDatatypes(expr: BinaryExpression,
|
||||
leftConstVal: LiteralValue?, leftDt: DataType,
|
||||
rightConstVal: LiteralValue?, rightDt: DataType): Boolean {
|
||||
leftConstVal: NumericLiteralValue?, leftDt: DataType,
|
||||
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)
|
||||
return Pair(false, value)
|
||||
when(value.type) {
|
||||
DataType.UBYTE -> {
|
||||
if (targetDt == DataType.BYTE) {
|
||||
if(value.bytevalue!! < 127)
|
||||
return Pair(true, LiteralValue(targetDt, value.bytevalue, position = value.position))
|
||||
if(value.number.toInt() < 127)
|
||||
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
|
||||
}
|
||||
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 -> {
|
||||
if (targetDt == DataType.UBYTE) {
|
||||
if(value.bytevalue!! >= 0)
|
||||
return Pair(true, LiteralValue(targetDt, value.bytevalue, position = value.position))
|
||||
if(value.number.toInt() >= 0)
|
||||
return Pair(true, NumericLiteralValue(targetDt, value.number.toInt(), value.position))
|
||||
}
|
||||
else if (targetDt == DataType.UWORD) {
|
||||
if(value.bytevalue!! >= 0)
|
||||
return Pair(true, LiteralValue(targetDt, wordvalue = value.bytevalue.toInt(), position = value.position))
|
||||
if(value.number.toInt() >= 0)
|
||||
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 -> {
|
||||
if (targetDt == DataType.UBYTE) {
|
||||
if(value.wordvalue!! <= 255)
|
||||
return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position = value.position))
|
||||
if(value.number.toInt() <= 255)
|
||||
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
|
||||
}
|
||||
else if (targetDt == DataType.BYTE) {
|
||||
if(value.wordvalue!! <= 127)
|
||||
return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position = value.position))
|
||||
if(value.number.toInt() <= 127)
|
||||
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
|
||||
}
|
||||
else if (targetDt == DataType.WORD) {
|
||||
if(value.wordvalue!! <= 32767)
|
||||
return Pair(true, LiteralValue(targetDt, wordvalue = value.wordvalue, position = value.position))
|
||||
if(value.number.toInt() <= 32767)
|
||||
return Pair(true, NumericLiteralValue(targetDt, value.number.toInt(), value.position))
|
||||
}
|
||||
}
|
||||
DataType.WORD -> {
|
||||
if (targetDt == DataType.UBYTE) {
|
||||
if(value.wordvalue!! in 0..255)
|
||||
return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position = value.position))
|
||||
if(value.number.toInt() in 0..255)
|
||||
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
|
||||
}
|
||||
else if (targetDt == DataType.BYTE) {
|
||||
if(value.wordvalue!! in -128..127)
|
||||
return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position = value.position))
|
||||
if(value.number.toInt() in -128..127)
|
||||
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
|
||||
}
|
||||
else if (targetDt == DataType.UWORD) {
|
||||
if(value.wordvalue!! >= 0)
|
||||
return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position = value.position))
|
||||
if(value.number.toInt() >= 0)
|
||||
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
@@ -434,9 +435,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) {
|
||||
// swap left and right so that right is always the constant
|
||||
val tmp = expr.left
|
||||
@@ -448,15 +449,15 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
||||
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)
|
||||
return pexpr
|
||||
|
||||
val (expr, _, rightVal) = reorderAssociative(pexpr, pleftVal)
|
||||
if(rightVal!=null) {
|
||||
// right value is a constant, see if we can optimize
|
||||
val rightConst: LiteralValue = rightVal
|
||||
when(rightConst.asNumericValue?.toDouble()) {
|
||||
val rightConst: NumericLiteralValue = rightVal
|
||||
when(rightConst.number.toDouble()) {
|
||||
0.0 -> {
|
||||
// left
|
||||
optimizationsDone++
|
||||
@@ -469,14 +470,14 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
||||
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)
|
||||
return expr
|
||||
|
||||
if(rightVal!=null) {
|
||||
// right value is a constant, see if we can optimize
|
||||
val rightConst: LiteralValue = rightVal
|
||||
when(rightConst.asNumericValue?.toDouble()) {
|
||||
val rightConst: NumericLiteralValue = rightVal
|
||||
when(rightConst.number.toDouble()) {
|
||||
0.0 -> {
|
||||
// left
|
||||
optimizationsDone++
|
||||
@@ -486,7 +487,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
||||
}
|
||||
if(leftVal!=null) {
|
||||
// left value is a constant, see if we can optimize
|
||||
when(leftVal.asNumericValue?.toDouble()) {
|
||||
when(leftVal.number.toDouble()) {
|
||||
0.0 -> {
|
||||
// -right
|
||||
optimizationsDone++
|
||||
@@ -498,38 +499,38 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
||||
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)
|
||||
return expr
|
||||
|
||||
if(rightVal!=null) {
|
||||
// right value is a constant, see if we can optimize
|
||||
val rightConst: LiteralValue = rightVal
|
||||
when(rightConst.asNumericValue?.toDouble()) {
|
||||
val rightConst: NumericLiteralValue = rightVal
|
||||
when(rightConst.number.toDouble()) {
|
||||
-3.0 -> {
|
||||
// -1/(left*left*left)
|
||||
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),
|
||||
expr.position)
|
||||
}
|
||||
-2.0 -> {
|
||||
// -1/(left*left)
|
||||
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),
|
||||
expr.position)
|
||||
}
|
||||
-1.0 -> {
|
||||
// -1/left
|
||||
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)
|
||||
}
|
||||
0.0 -> {
|
||||
// 1
|
||||
optimizationsDone++
|
||||
return LiteralValue.fromNumber(1, rightConst.type, expr.position)
|
||||
return NumericLiteralValue(rightConst.type, 1, expr.position)
|
||||
}
|
||||
0.5 -> {
|
||||
// sqrt(left)
|
||||
@@ -555,21 +556,21 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
||||
}
|
||||
if(leftVal!=null) {
|
||||
// left value is a constant, see if we can optimize
|
||||
when(leftVal.asNumericValue?.toDouble()) {
|
||||
when(leftVal.number.toDouble()) {
|
||||
-1.0 -> {
|
||||
// -1
|
||||
optimizationsDone++
|
||||
return LiteralValue(DataType.FLOAT, floatvalue = -1.0, position = expr.position)
|
||||
return NumericLiteralValue(DataType.FLOAT, -1.0, expr.position)
|
||||
}
|
||||
0.0 -> {
|
||||
// 0
|
||||
optimizationsDone++
|
||||
return LiteralValue.fromNumber(0, leftVal.type, expr.position)
|
||||
return NumericLiteralValue(leftVal.type, 0, expr.position)
|
||||
}
|
||||
1.0 -> {
|
||||
//1
|
||||
optimizationsDone++
|
||||
return LiteralValue.fromNumber(1, leftVal.type, expr.position)
|
||||
return NumericLiteralValue(leftVal.type, 1, expr.position)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -578,22 +579,22 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
||||
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)
|
||||
return expr
|
||||
|
||||
// simplify assignments A = B <operator> C
|
||||
|
||||
val cv = rightVal?.asIntegerValue?.toDouble()
|
||||
val cv = rightVal?.number?.toInt()?.toDouble()
|
||||
when(expr.operator) {
|
||||
"%" -> {
|
||||
if (cv == 1.0) {
|
||||
optimizationsDone++
|
||||
return LiteralValue.fromNumber(0, expr.inferType(program)!!, expr.position)
|
||||
return NumericLiteralValue(expr.inferType(program)!!, 0, expr.position)
|
||||
} else if (cv == 2.0) {
|
||||
optimizationsDone++
|
||||
expr.operator = "&"
|
||||
expr.right = LiteralValue.optimalInteger(1, expr.position)
|
||||
expr.right = NumericLiteralValue.optimalInteger(1, expr.position)
|
||||
return expr
|
||||
}
|
||||
}
|
||||
@@ -602,7 +603,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
||||
|
||||
}
|
||||
|
||||
private fun optimizeDivision(expr: BinaryExpression, leftVal: LiteralValue?, rightVal: LiteralValue?): IExpression {
|
||||
private fun optimizeDivision(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression {
|
||||
if(leftVal==null && rightVal==null)
|
||||
return expr
|
||||
|
||||
@@ -610,8 +611,8 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
||||
|
||||
if(rightVal!=null) {
|
||||
// right value is a constant, see if we can optimize
|
||||
val rightConst: LiteralValue = rightVal
|
||||
val cv = rightConst.asNumericValue?.toDouble()
|
||||
val rightConst: NumericLiteralValue = rightVal
|
||||
val cv = rightConst.number.toDouble()
|
||||
val leftDt = expr.left.inferType(program)
|
||||
when(cv) {
|
||||
-1.0 -> {
|
||||
@@ -633,7 +634,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
||||
// divided by a power of two => shift right
|
||||
optimizationsDone++
|
||||
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 -> {
|
||||
@@ -641,32 +642,32 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
||||
// divided by a negative power of two => negate, then shift right
|
||||
optimizationsDone++
|
||||
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(abs(rightConst.asNumericValue!!.toDouble()) >= 256.0) {
|
||||
if(abs(rightConst.number.toDouble()) >= 256.0) {
|
||||
optimizationsDone++
|
||||
return LiteralValue(DataType.UBYTE, 0, position = expr.position)
|
||||
return NumericLiteralValue(DataType.UBYTE, 0, expr.position)
|
||||
}
|
||||
}
|
||||
else if (leftDt == DataType.UWORD) {
|
||||
if(abs(rightConst.asNumericValue!!.toDouble()) >= 65536.0) {
|
||||
if(abs(rightConst.number.toDouble()) >= 65536.0) {
|
||||
optimizationsDone++
|
||||
return LiteralValue(DataType.UBYTE, 0, position = expr.position)
|
||||
return NumericLiteralValue(DataType.UBYTE, 0, expr.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(leftVal!=null) {
|
||||
// left value is a constant, see if we can optimize
|
||||
when(leftVal.asNumericValue?.toDouble()) {
|
||||
when(leftVal.number.toDouble()) {
|
||||
0.0 -> {
|
||||
// 0
|
||||
optimizationsDone++
|
||||
return LiteralValue.fromNumber(0, leftVal.type, expr.position)
|
||||
return NumericLiteralValue(leftVal.type, 0, expr.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -674,16 +675,16 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
||||
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)
|
||||
return pexpr
|
||||
|
||||
val (expr, _, rightVal) = reorderAssociative(pexpr, pleftVal)
|
||||
if(rightVal!=null) {
|
||||
// right value is a constant, see if we can optimize
|
||||
val leftValue: IExpression = expr.left
|
||||
val rightConst: LiteralValue = rightVal
|
||||
when(val cv = rightConst.asNumericValue?.toDouble()) {
|
||||
val leftValue: Expression = expr.left
|
||||
val rightConst: NumericLiteralValue = rightVal
|
||||
when(val cv = rightConst.number.toDouble()) {
|
||||
-1.0 -> {
|
||||
// -left
|
||||
optimizationsDone++
|
||||
@@ -692,7 +693,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
||||
0.0 -> {
|
||||
// 0
|
||||
optimizationsDone++
|
||||
return LiteralValue.fromNumber(0, rightConst.type, expr.position)
|
||||
return NumericLiteralValue(rightConst.type, 0, expr.position)
|
||||
}
|
||||
1.0 -> {
|
||||
// left
|
||||
@@ -704,7 +705,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
||||
// times a power of two => shift left
|
||||
optimizationsDone++
|
||||
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 -> {
|
||||
@@ -712,7 +713,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
|
||||
// times a negative power of two => negate, then shift left
|
||||
optimizationsDone++
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,14 +20,10 @@ import kotlin.math.floor
|
||||
internal class StatementOptimizer(private val program: Program, private val optimizeInlining: Boolean) : IAstModifyingVisitor {
|
||||
var optimizationsDone: Int = 0
|
||||
private set
|
||||
var scopesToFlatten = mutableListOf<INameScope>()
|
||||
|
||||
private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure }
|
||||
private val callgraph = CallGraph(program)
|
||||
|
||||
companion object {
|
||||
private var generatedLabelSequenceNumber = 0
|
||||
}
|
||||
private var generatedLabelSequenceNumber = 0
|
||||
|
||||
override fun visit(program: Program) {
|
||||
removeUnusedCode(callgraph)
|
||||
@@ -60,7 +56,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
||||
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 })
|
||||
if(caller !is IFunctionCall || caller !is Statement || sub.statements.any { it is Subroutine })
|
||||
return
|
||||
|
||||
if(sub.parameters.isEmpty() && sub.returntypes.isEmpty()) {
|
||||
@@ -137,7 +133,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 (block.containsNoCodeNorVars()) {
|
||||
optimizationsDone++
|
||||
@@ -155,7 +151,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
||||
return super.visit(block)
|
||||
}
|
||||
|
||||
override fun visit(subroutine: Subroutine): IStatement {
|
||||
override fun visit(subroutine: Subroutine): Statement {
|
||||
super.visit(subroutine)
|
||||
val forceOutput = "force_output" in subroutine.definingBlock().options()
|
||||
if(subroutine.asmAddress==null && !forceOutput) {
|
||||
@@ -192,11 +188,11 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
||||
return subroutine
|
||||
}
|
||||
|
||||
override fun visit(decl: VarDecl): IStatement {
|
||||
override fun visit(decl: VarDecl): Statement {
|
||||
val forceOutput = "force_output" in decl.definingBlock().options()
|
||||
if(decl !in callgraph.usedSymbols && !forceOutput) {
|
||||
if(decl.type!=VarDeclType.CONST)
|
||||
printWarning("removing unused variable '${decl.name}'", decl.position)
|
||||
if(decl.type == VarDeclType.VAR)
|
||||
printWarning("removing unused variable ${decl.type} '${decl.name}'", decl.position)
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(decl)
|
||||
}
|
||||
@@ -204,13 +200,13 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
||||
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
|
||||
val linesToRemove = mutableListOf<Int>()
|
||||
var previousAssignmentLine: Int? = null
|
||||
for (i in 0 until statements.size) {
|
||||
val stmt = statements[i] as? Assignment
|
||||
if (stmt != null && stmt.value is LiteralValue) {
|
||||
if (stmt != null && stmt.value is NumericLiteralValue) {
|
||||
if (previousAssignmentLine == null) {
|
||||
previousAssignmentLine = i
|
||||
continue
|
||||
@@ -229,7 +225,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
||||
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) {
|
||||
val functionName = functionCallStatement.target.nameInSource[0]
|
||||
if (functionName in pureBuiltinFunctions) {
|
||||
@@ -242,8 +238,6 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
||||
if(functionCallStatement.target.nameInSource==listOf("c64scr", "print") ||
|
||||
functionCallStatement.target.nameInSource==listOf("c64scr", "print_p")) {
|
||||
// printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters
|
||||
if(functionCallStatement.arglist.single() is LiteralValue)
|
||||
throw AstException("string argument should be on heap already")
|
||||
val stringVar = functionCallStatement.arglist.single() as? IdentifierReference
|
||||
if(stringVar!=null) {
|
||||
val heapId = stringVar.heapId(program.namespace)
|
||||
@@ -251,7 +245,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
||||
if(string.length==1) {
|
||||
val petscii = Petscii.encodePetscii(string, true)[0]
|
||||
functionCallStatement.arglist.clear()
|
||||
functionCallStatement.arglist.add(LiteralValue.optimalInteger(petscii, functionCallStatement.position))
|
||||
functionCallStatement.arglist.add(NumericLiteralValue.optimalInteger(petscii.toInt(), functionCallStatement.position))
|
||||
functionCallStatement.target = IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position)
|
||||
optimizationsDone++
|
||||
return functionCallStatement
|
||||
@@ -259,9 +253,9 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
||||
val petscii = Petscii.encodePetscii(string, true)
|
||||
val scope = AnonymousScope(mutableListOf(), functionCallStatement.position)
|
||||
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position),
|
||||
mutableListOf(LiteralValue.optimalInteger(petscii[0], functionCallStatement.position)), functionCallStatement.position))
|
||||
mutableListOf(NumericLiteralValue.optimalInteger(petscii[0].toInt(), functionCallStatement.position)), functionCallStatement.position))
|
||||
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position),
|
||||
mutableListOf(LiteralValue.optimalInteger(petscii[1], functionCallStatement.position)), functionCallStatement.position))
|
||||
mutableListOf(NumericLiteralValue.optimalInteger(petscii[1].toInt(), functionCallStatement.position)), functionCallStatement.position))
|
||||
optimizationsDone++
|
||||
return scope
|
||||
}
|
||||
@@ -287,7 +281,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
||||
return super.visit(functionCallStatement)
|
||||
}
|
||||
|
||||
override fun visit(functionCall: FunctionCall): IExpression {
|
||||
override fun visit(functionCall: FunctionCall): Expression {
|
||||
// if it calls a subroutine,
|
||||
// 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
|
||||
@@ -307,7 +301,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
||||
return super.visit(functionCall)
|
||||
}
|
||||
|
||||
override fun visit(ifStatement: IfStatement): IStatement {
|
||||
override fun visit(ifStatement: IfStatement): Statement {
|
||||
super.visit(ifStatement)
|
||||
|
||||
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsNoCodeNorVars()) {
|
||||
@@ -341,7 +335,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
||||
return ifStatement
|
||||
}
|
||||
|
||||
override fun visit(forLoop: ForLoop): IStatement {
|
||||
override fun visit(forLoop: ForLoop): Statement {
|
||||
super.visit(forLoop)
|
||||
if(forLoop.body.containsNoCodeNorVars()) {
|
||||
// remove empty for loop
|
||||
@@ -371,7 +365,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
||||
return forLoop
|
||||
}
|
||||
|
||||
override fun visit(whileLoop: WhileLoop): IStatement {
|
||||
override fun visit(whileLoop: WhileLoop): Statement {
|
||||
super.visit(whileLoop)
|
||||
val constvalue = whileLoop.condition.constValue(program)
|
||||
if(constvalue!=null) {
|
||||
@@ -380,10 +374,10 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
||||
printWarning("condition is always true", whileLoop.position)
|
||||
if(hasContinueOrBreak(whileLoop.body))
|
||||
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(Jump(null,
|
||||
IdentifierReference(listOf("__back"), whileLoop.condition.position),
|
||||
IdentifierReference(listOf("_prog8_back"), whileLoop.condition.position),
|
||||
null, whileLoop.condition.position))
|
||||
optimizationsDone++
|
||||
return whileLoop.body
|
||||
@@ -397,7 +391,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
||||
return whileLoop
|
||||
}
|
||||
|
||||
override fun visit(repeatLoop: RepeatLoop): IStatement {
|
||||
override fun visit(repeatLoop: RepeatLoop): Statement {
|
||||
super.visit(repeatLoop)
|
||||
val constvalue = repeatLoop.untilCondition.constValue(program)
|
||||
if(constvalue!=null) {
|
||||
@@ -427,7 +421,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
||||
return repeatLoop
|
||||
}
|
||||
|
||||
override fun visit(whenStatement: WhenStatement): IStatement {
|
||||
override fun visit(whenStatement: WhenStatement): Statement {
|
||||
val choices = whenStatement.choices.toList()
|
||||
for(choice in choices) {
|
||||
if(choice.statements.containsNoCodeNorVars())
|
||||
@@ -442,12 +436,12 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
||||
{
|
||||
var count=0
|
||||
|
||||
override fun visit(breakStmt: Break): IStatement {
|
||||
override fun visit(breakStmt: Break): Statement {
|
||||
count++
|
||||
return super.visit(breakStmt)
|
||||
}
|
||||
|
||||
override fun visit(contStmt: Continue): IStatement {
|
||||
override fun visit(contStmt: Continue): Statement {
|
||||
count++
|
||||
return super.visit(contStmt)
|
||||
}
|
||||
@@ -461,7 +455,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
||||
return s.count > 0
|
||||
}
|
||||
|
||||
override fun visit(jump: Jump): IStatement {
|
||||
override fun visit(jump: Jump): Statement {
|
||||
val subroutine = jump.identifier?.targetSubroutine(program.namespace)
|
||||
if(subroutine!=null) {
|
||||
// if the first instruction in the subroutine is another jump, shortcut this one
|
||||
@@ -485,23 +479,25 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
||||
return jump
|
||||
}
|
||||
|
||||
override fun visit(assignment: Assignment): IStatement {
|
||||
override fun visit(assignment: Assignment): Statement {
|
||||
if(assignment.aug_op!=null)
|
||||
throw AstException("augmented assignments should have been converted to normal assignments before this optimizer")
|
||||
|
||||
if(assignment.target isSameAs assignment.value) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(assignment)
|
||||
if(assignment.target.isNotMemory(program.namespace)) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(assignment)
|
||||
}
|
||||
}
|
||||
val targetDt = assignment.target.inferType(program, assignment)
|
||||
val bexpr=assignment.value as? BinaryExpression
|
||||
if(bexpr!=null) {
|
||||
val cv = bexpr.right.constValue(program)?.asNumericValue?.toDouble()
|
||||
val cv = bexpr.right.constValue(program)?.number?.toDouble()
|
||||
if (cv == null) {
|
||||
if (bexpr.operator == "+" && targetDt != DataType.FLOAT) {
|
||||
if (bexpr.left isSameAs bexpr.right && assignment.target isSameAs bexpr.left) {
|
||||
bexpr.operator = "*"
|
||||
bexpr.right = LiteralValue.optimalInteger(2, assignment.value.position)
|
||||
bexpr.right = NumericLiteralValue.optimalInteger(2, assignment.value.position)
|
||||
optimizationsDone++
|
||||
return assignment
|
||||
}
|
||||
@@ -570,7 +566,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
||||
}
|
||||
if (((targetDt == DataType.UWORD || targetDt == DataType.WORD) && cv > 15.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)
|
||||
optimizationsDone++
|
||||
} else {
|
||||
@@ -592,7 +588,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
||||
}
|
||||
if (((targetDt == DataType.UWORD || targetDt == DataType.WORD) && cv > 15.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)
|
||||
optimizationsDone++
|
||||
} else {
|
||||
@@ -616,20 +612,15 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
||||
return super.visit(assignment)
|
||||
}
|
||||
|
||||
override fun visit(scope: AnonymousScope): IStatement {
|
||||
override fun visit(scope: AnonymousScope): Statement {
|
||||
val linesToRemove = deduplicateAssignments(scope.statements)
|
||||
if(linesToRemove.isNotEmpty()) {
|
||||
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)
|
||||
}
|
||||
|
||||
override fun visit(label: Label): IStatement {
|
||||
override fun visit(label: Label): Statement {
|
||||
// remove duplicate labels
|
||||
val stmts = label.definingScope().statements
|
||||
val startIdx = stmts.indexOf(label)
|
||||
@@ -642,17 +633,38 @@ internal class StatementOptimizer(private val program: Program, private val opti
|
||||
|
||||
|
||||
|
||||
internal class RemoveNops: IAstVisitor {
|
||||
val nopStatements = mutableListOf<NopStatement>()
|
||||
internal class FlattenAnonymousScopesAndRemoveNops: IAstVisitor {
|
||||
private var scopesToFlatten = mutableListOf<INameScope>()
|
||||
private val nopStatements = mutableListOf<NopStatement>()
|
||||
|
||||
override fun visit(program: 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 {
|
||||
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) {
|
||||
nopStatements.add(nopStatement)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package prog8.parser
|
||||
|
||||
import org.antlr.v4.runtime.*
|
||||
import prog8.ast.*
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.antlr.toAst
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.base.SyntaxError
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package prog8.vm
|
||||
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.LiteralValue
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.ReferenceLiteralValue
|
||||
import prog8.compiler.HeapValues
|
||||
import prog8.compiler.target.c64.Petscii
|
||||
import kotlin.math.abs
|
||||
@@ -9,11 +10,12 @@ 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)
|
||||
* 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) {
|
||||
open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=null,
|
||||
val array: Array<Number>?=null, val heapId: Int?=null) {
|
||||
|
||||
val byteval: Short?
|
||||
val wordval: Int?
|
||||
@@ -21,16 +23,19 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
|
||||
val asBoolean: Boolean
|
||||
|
||||
companion object {
|
||||
fun from(literalValue: LiteralValue, heap: HeapValues): RuntimeValue {
|
||||
fun fromLv(literalValue: NumericLiteralValue): RuntimeValue {
|
||||
return RuntimeValue(literalValue.type, num = literalValue.number)
|
||||
}
|
||||
|
||||
fun fromLv(literalValue: ReferenceLiteralValue, heap: HeapValues): RuntimeValue {
|
||||
return when(literalValue.type) {
|
||||
in NumericDatatypes -> RuntimeValue(literalValue.type, num = literalValue.asNumericValue!!)
|
||||
in StringDatatypes -> from(literalValue.heapId!!, heap)
|
||||
in ArrayDatatypes -> from(literalValue.heapId!!, heap)
|
||||
in StringDatatypes -> fromHeapId(literalValue.heapId!!, heap)
|
||||
in ArrayDatatypes -> fromHeapId(literalValue.heapId!!, heap)
|
||||
else -> throw IllegalArgumentException("weird source value $literalValue")
|
||||
}
|
||||
}
|
||||
|
||||
fun from(heapId: Int, heap: HeapValues): RuntimeValue {
|
||||
fun fromHeapId(heapId: Int, heap: HeapValues): RuntimeValue {
|
||||
val value = heap.get(heapId)
|
||||
return when {
|
||||
value.type in StringDatatypes ->
|
||||
@@ -45,8 +50,7 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
|
||||
if(elt.value.integer!=null)
|
||||
resultArray.add(elt.value.integer!!)
|
||||
else {
|
||||
println("ADDRESSOF ${elt.value}")
|
||||
resultArray.add(0x8000)
|
||||
TODO("ADDRESSOF ${elt.value}")
|
||||
}
|
||||
}
|
||||
RuntimeValue(value.type, array = resultArray.toTypedArray(), heapId = heapId)
|
||||
@@ -103,8 +107,6 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
|
||||
asBoolean = floatval != 0.0
|
||||
}
|
||||
else -> {
|
||||
if(heapId==null)
|
||||
throw IllegalArgumentException("for non-numeric types, a heapId should be given")
|
||||
byteval = null
|
||||
wordval = null
|
||||
floatval = null
|
||||
@@ -113,19 +115,6 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
return when(type) {
|
||||
DataType.UBYTE -> "ub:%02x".format(byteval)
|
||||
@@ -637,7 +626,7 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
|
||||
}
|
||||
|
||||
|
||||
class RuntimeValueRange(type: DataType, val range: IntProgression): RuntimeValue(type, 0) {
|
||||
class RuntimeValueRange(type: DataType, val range: IntProgression): RuntimeValue(type, array=range.toList().toTypedArray()) {
|
||||
override fun iterator(): Iterator<Number> {
|
||||
return range.iterator()
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
package prog8.vm.astvm
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.base.initvarsSubName
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.LiteralValue
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.target.c64.Mflpt5
|
||||
import prog8.compiler.IntegerOrAddressOf
|
||||
import prog8.compiler.target.c64.MachineDefinition
|
||||
import prog8.compiler.target.c64.Petscii
|
||||
import prog8.vm.RuntimeValue
|
||||
import prog8.vm.RuntimeValueRange
|
||||
import prog8.compiler.target.c64.Petscii
|
||||
import java.awt.EventQueue
|
||||
import java.io.CharConversionException
|
||||
import java.util.*
|
||||
@@ -32,31 +34,31 @@ class StatusFlags {
|
||||
var negative: Boolean = false
|
||||
var irqd: Boolean = false
|
||||
|
||||
private fun setFlags(value: LiteralValue?) {
|
||||
private fun setFlags(value: NumericLiteralValue?) {
|
||||
if (value != null) {
|
||||
when (value.type) {
|
||||
DataType.UBYTE -> {
|
||||
val v = value.bytevalue!!.toInt()
|
||||
val v = value.number.toInt()
|
||||
negative = v > 127
|
||||
zero = v == 0
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
val v = value.bytevalue!!.toInt()
|
||||
val v = value.number.toInt()
|
||||
negative = v < 0
|
||||
zero = v == 0
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
val v = value.wordvalue!!
|
||||
val v = value.number.toInt()
|
||||
negative = v > 32767
|
||||
zero = v == 0
|
||||
}
|
||||
DataType.WORD -> {
|
||||
val v = value.wordvalue!!
|
||||
val v = value.number.toInt()
|
||||
negative = v < 0
|
||||
zero = v == 0
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
val flt = value.floatvalue!!
|
||||
val flt = value.number.toDouble()
|
||||
negative = flt < 0.0
|
||||
zero = flt == 0.0
|
||||
}
|
||||
@@ -265,7 +267,7 @@ class AstVm(val program: Program) {
|
||||
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<RuntimeValue>, startAtLabel: Label?=null): RuntimeValue? {
|
||||
if(sub.isAsmSubroutine) {
|
||||
return performSyscall(sub, arguments)
|
||||
}
|
||||
@@ -282,10 +284,10 @@ class AstVm(val program: Program) {
|
||||
}
|
||||
|
||||
val statements = sub.statements.iterator()
|
||||
if(startlabel!=null) {
|
||||
if(startAtLabel!=null) {
|
||||
do {
|
||||
val stmt = statements.next()
|
||||
} while(stmt!==startlabel)
|
||||
} while(stmt!==startAtLabel)
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -312,7 +314,7 @@ class AstVm(val program: Program) {
|
||||
}
|
||||
|
||||
|
||||
private fun executeStatement(sub: INameScope, stmt: IStatement) {
|
||||
private fun executeStatement(sub: INameScope, stmt: Statement) {
|
||||
instructionCounter++
|
||||
if (instructionCounter % 200 == 0)
|
||||
Thread.sleep(1)
|
||||
@@ -387,7 +389,7 @@ class AstVm(val program: Program) {
|
||||
runtimeVariables.set(identScope, ident.name, value)
|
||||
}
|
||||
VarDeclType.MEMORY -> {
|
||||
val addr=ident.value!!.constValue(program)!!.asIntegerValue!!
|
||||
val addr=ident.value!!.constValue(program)!!.number.toInt()
|
||||
val newval = when {
|
||||
stmt.operator == "++" -> mem.getUByte(addr)+1 and 255
|
||||
stmt.operator == "--" -> mem.getUByte(addr)-1 and 255
|
||||
@@ -413,9 +415,9 @@ class AstVm(val program: Program) {
|
||||
val elementType = stmt.target.arrayindexed!!.inferType(program)!!
|
||||
val index = evaluate(stmt.target.arrayindexed!!.arrayspec.index, evalCtx).integerValue()
|
||||
var value = RuntimeValue(elementType, arrayvalue.array!![index].toInt())
|
||||
when {
|
||||
stmt.operator == "++" -> value=value.inc()
|
||||
stmt.operator == "--" -> value=value.dec()
|
||||
value = when {
|
||||
stmt.operator == "++" -> value.inc()
|
||||
stmt.operator == "--" -> value.dec()
|
||||
else -> throw VmExecutionException("strange postincdec operator $stmt")
|
||||
}
|
||||
arrayvalue.array[index] = value.numericValue()
|
||||
@@ -471,7 +473,7 @@ class AstVm(val program: Program) {
|
||||
loopvar = IdentifierReference(listOf(stmt.loopRegister.name), stmt.position)
|
||||
} else {
|
||||
loopvarDt = stmt.loopVar!!.inferType(program)!!
|
||||
loopvar = stmt.loopVar
|
||||
loopvar = stmt.loopVar!!
|
||||
}
|
||||
val iterator = iterable.iterator()
|
||||
for (loopvalue in iterator) {
|
||||
@@ -517,8 +519,8 @@ class AstVm(val program: Program) {
|
||||
executeAnonymousScope(choice.statements)
|
||||
break
|
||||
} else {
|
||||
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 value = choice.values!!.single().constValue(evalCtx.program) ?: throw VmExecutionException("can only use const values in when choices ${choice.position}")
|
||||
val rtval = RuntimeValue.fromLv(value)
|
||||
if(condition==rtval) {
|
||||
executeAnonymousScope(choice.statements)
|
||||
break
|
||||
@@ -543,11 +545,13 @@ class AstVm(val program: Program) {
|
||||
performAssignment(target2, value1, swap, evalCtx)
|
||||
}
|
||||
|
||||
fun performAssignment(target: AssignTarget, value: RuntimeValue, contextStmt: IStatement, evalCtx: EvalContext) {
|
||||
fun performAssignment(target: AssignTarget, value: RuntimeValue, contextStmt: Statement, evalCtx: EvalContext) {
|
||||
val targetIdent = target.identifier
|
||||
val targetArrayIndexed = target.arrayindexed
|
||||
when {
|
||||
target.identifier != null -> {
|
||||
val decl = contextStmt.definingScope().lookup(target.identifier.nameInSource, contextStmt) as? VarDecl
|
||||
?: throw VmExecutionException("can't find assignment target ${target.identifier}")
|
||||
targetIdent != null -> {
|
||||
val decl = contextStmt.definingScope().lookup(targetIdent.nameInSource, contextStmt) as? VarDecl
|
||||
?: throw VmExecutionException("can't find assignment target $targetIdent")
|
||||
if (decl.type == VarDeclType.MEMORY) {
|
||||
val address = runtimeVariables.getMemoryAddress(decl.definingScope(), decl.name)
|
||||
when (decl.datatype) {
|
||||
@@ -564,14 +568,14 @@ class AstVm(val program: Program) {
|
||||
runtimeVariables.set(decl.definingScope(), decl.name, value)
|
||||
}
|
||||
target.memoryAddress != null -> {
|
||||
val address = evaluate(target.memoryAddress!!.addressExpression, evalCtx).wordval!!
|
||||
val address = evaluate(target.memoryAddress.addressExpression, evalCtx).wordval!!
|
||||
evalCtx.mem.setUByte(address, value.byteval!!)
|
||||
}
|
||||
target.arrayindexed != null -> {
|
||||
val vardecl = target.arrayindexed.identifier.targetVarDecl(program.namespace)!!
|
||||
targetArrayIndexed != null -> {
|
||||
val vardecl = targetArrayIndexed.identifier.targetVarDecl(program.namespace)!!
|
||||
if(vardecl.type==VarDeclType.VAR) {
|
||||
val array = evaluate(target.arrayindexed.identifier, evalCtx)
|
||||
val index = evaluate(target.arrayindexed.arrayspec.index, evalCtx)
|
||||
val array = evaluate(targetArrayIndexed.identifier, evalCtx)
|
||||
val index = evaluate(targetArrayIndexed.arrayspec.index, evalCtx)
|
||||
when (array.type) {
|
||||
DataType.ARRAY_UB -> {
|
||||
if (value.type != DataType.UBYTE)
|
||||
@@ -605,7 +609,7 @@ class AstVm(val program: Program) {
|
||||
val indexInt = index.integerValue()
|
||||
val newchr = Petscii.decodePetscii(listOf(value.numericValue().toShort()), true)
|
||||
val newstr = array.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}")
|
||||
val identScope = ident.definingScope()
|
||||
program.heap.update(array.heapId!!, newstr)
|
||||
@@ -613,15 +617,15 @@ class AstVm(val program: Program) {
|
||||
}
|
||||
}
|
||||
else {
|
||||
val address = (vardecl.value as LiteralValue).asIntegerValue!!
|
||||
val index = evaluate(target.arrayindexed.arrayspec.index, evalCtx).integerValue()
|
||||
val elementType = target.arrayindexed.inferType(program)!!
|
||||
val address = (vardecl.value as NumericLiteralValue).number.toInt()
|
||||
val index = evaluate(targetArrayIndexed.arrayspec.index, evalCtx).integerValue()
|
||||
val elementType = targetArrayIndexed.inferType(program)!!
|
||||
when(elementType) {
|
||||
DataType.UBYTE -> mem.setUByte(address+index, value.byteval!!)
|
||||
DataType.BYTE -> mem.setSByte(address+index, value.byteval!!)
|
||||
DataType.UWORD -> mem.setUWord(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")
|
||||
}
|
||||
}
|
||||
@@ -640,7 +644,7 @@ class AstVm(val program: Program) {
|
||||
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? {
|
||||
var result: RuntimeValue? = null
|
||||
@@ -672,23 +676,23 @@ class AstVm(val program: Program) {
|
||||
dialog.canvas.printText(args[0].wordval!!.toString(), true)
|
||||
}
|
||||
"c64scr.print_ubhex" -> {
|
||||
val prefix = if (args[0].asBoolean) "$" else ""
|
||||
val number = args[1].byteval!!
|
||||
val number = args[0].byteval!!
|
||||
val prefix = if (args[1].asBoolean) "$" else ""
|
||||
dialog.canvas.printText("$prefix${number.toString(16).padStart(2, '0')}", true)
|
||||
}
|
||||
"c64scr.print_uwhex" -> {
|
||||
val prefix = if (args[0].asBoolean) "$" else ""
|
||||
val number = args[1].wordval!!
|
||||
val number = args[0].wordval!!
|
||||
val prefix = if (args[1].asBoolean) "$" else ""
|
||||
dialog.canvas.printText("$prefix${number.toString(16).padStart(4, '0')}", true)
|
||||
}
|
||||
"c64scr.print_uwbin" -> {
|
||||
val prefix = if (args[0].asBoolean) "%" else ""
|
||||
val number = args[1].wordval!!
|
||||
val number = args[0].wordval!!
|
||||
val prefix = if (args[1].asBoolean) "%" else ""
|
||||
dialog.canvas.printText("$prefix${number.toString(2).padStart(16, '0')}", true)
|
||||
}
|
||||
"c64scr.print_ubbin" -> {
|
||||
val prefix = if (args[0].asBoolean) "%" else ""
|
||||
val number = args[1].byteval!!
|
||||
val number = args[0].byteval!!
|
||||
val prefix = if (args[1].asBoolean) "%" else ""
|
||||
dialog.canvas.printText("$prefix${number.toString(2).padStart(8, '0')}", true)
|
||||
}
|
||||
"c64scr.clear_screenchars" -> {
|
||||
@@ -730,7 +734,7 @@ class AstVm(val program: Program) {
|
||||
result = RuntimeValue(DataType.UBYTE, paddedStr.indexOf('\u0000'))
|
||||
}
|
||||
"c64flt.print_f" -> {
|
||||
dialog.canvas.printText(args[0].floatval.toString(), true)
|
||||
dialog.canvas.printText(args[0].floatval.toString(), false)
|
||||
}
|
||||
"c64.CHROUT" -> {
|
||||
dialog.canvas.printPetscii(args[0].byteval!!)
|
||||
@@ -834,34 +838,34 @@ class AstVm(val program: Program) {
|
||||
}
|
||||
}
|
||||
"max" -> {
|
||||
val numbers = args.map { it.numericValue().toDouble() }
|
||||
RuntimeValue(args[0].type, numbers.max())
|
||||
val numbers = args.single().array!!.map { it.toDouble() }
|
||||
RuntimeValue(ArrayElementTypes.getValue(args[0].type), numbers.max())
|
||||
}
|
||||
"min" -> {
|
||||
val numbers = args.map { it.numericValue().toDouble() }
|
||||
RuntimeValue(args[0].type, numbers.min())
|
||||
val numbers = args.single().array!!.map { it.toDouble() }
|
||||
RuntimeValue(ArrayElementTypes.getValue(args[0].type), numbers.min())
|
||||
}
|
||||
"avg" -> {
|
||||
val numbers = args.map { it.numericValue().toDouble() }
|
||||
val numbers = args.single().array!!.map { it.toDouble() }
|
||||
RuntimeValue(DataType.FLOAT, numbers.average())
|
||||
}
|
||||
"sum" -> {
|
||||
val sum = args.map { it.numericValue().toDouble() }.sum()
|
||||
val sum = args.single().array!!.map { it.toDouble() }.sum()
|
||||
when (args[0].type) {
|
||||
DataType.UBYTE -> RuntimeValue(DataType.UWORD, sum)
|
||||
DataType.BYTE -> RuntimeValue(DataType.WORD, sum)
|
||||
DataType.UWORD -> RuntimeValue(DataType.UWORD, sum)
|
||||
DataType.WORD -> RuntimeValue(DataType.WORD, sum)
|
||||
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, sum)
|
||||
DataType.ARRAY_UB -> RuntimeValue(DataType.UWORD, sum)
|
||||
DataType.ARRAY_B -> RuntimeValue(DataType.WORD, sum)
|
||||
DataType.ARRAY_UW -> RuntimeValue(DataType.UWORD, sum)
|
||||
DataType.ARRAY_W -> RuntimeValue(DataType.WORD, sum)
|
||||
DataType.ARRAY_F -> RuntimeValue(DataType.FLOAT, sum)
|
||||
else -> throw VmExecutionException("weird sum type ${args[0]}")
|
||||
}
|
||||
}
|
||||
"any" -> {
|
||||
val numbers = args.map { it.numericValue().toDouble() }
|
||||
val numbers = args.single().array!!.map { it.toDouble() }
|
||||
RuntimeValue(DataType.UBYTE, if (numbers.any { it != 0.0 }) 1 else 0)
|
||||
}
|
||||
"all" -> {
|
||||
val numbers = args.map { it.numericValue().toDouble() }
|
||||
val numbers = args.single().array!!.map { it.toDouble() }
|
||||
RuntimeValue(DataType.UBYTE, if (numbers.all { it != 0.0 }) 1 else 0)
|
||||
}
|
||||
"swap" ->
|
||||
@@ -874,27 +878,31 @@ class AstVm(val program: Program) {
|
||||
RuntimeValue(DataType.UBYTE, args[0].str!!.length)
|
||||
}
|
||||
"memset" -> {
|
||||
val target = args[0].array!!
|
||||
val heapId = args[0].wordval!!
|
||||
val target = program.heap.get(heapId).array ?: throw VmExecutionException("memset target is not an array")
|
||||
val amount = args[1].integerValue()
|
||||
val value = args[2].integerValue()
|
||||
for (i in 0 until amount) {
|
||||
target[i] = value
|
||||
target[i] = IntegerOrAddressOf(value, null)
|
||||
}
|
||||
null
|
||||
}
|
||||
"memsetw" -> {
|
||||
val target = args[0].array!!
|
||||
val heapId = args[0].wordval!!
|
||||
val target = program.heap.get(heapId).array ?: throw VmExecutionException("memset target is not an array")
|
||||
val amount = args[1].integerValue()
|
||||
val value = args[2].integerValue()
|
||||
for (i in 0 until amount step 2) {
|
||||
target[i * 2] = value and 255
|
||||
target[i * 2 + 1] = value ushr 8
|
||||
target[i * 2] = IntegerOrAddressOf(value and 255, null)
|
||||
target[i * 2 + 1] = IntegerOrAddressOf(value ushr 8, null)
|
||||
}
|
||||
null
|
||||
}
|
||||
"memcopy" -> {
|
||||
val source = args[0].array!!
|
||||
val dest = args[1].array!!
|
||||
val sourceHeapId = args[0].wordval!!
|
||||
val destHeapId = args[1].wordval!!
|
||||
val source = program.heap.get(sourceHeapId).array!!
|
||||
val dest = program.heap.get(destHeapId).array!!
|
||||
val amount = args[2].integerValue()
|
||||
for(i in 0 until amount) {
|
||||
dest[i] = source[i]
|
||||
@@ -951,4 +959,3 @@ class AstVm(val program: Program) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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,8 +1,9 @@
|
||||
package prog8.vm.astvm
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.ArrayElementTypes
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
|
||||
@@ -13,19 +14,27 @@ import prog8.vm.RuntimeValue
|
||||
import prog8.vm.RuntimeValueRange
|
||||
import kotlin.math.abs
|
||||
|
||||
|
||||
typealias BuiltinfunctionCaller = (name: String, args: List<RuntimeValue>, flags: StatusFlags) -> RuntimeValue?
|
||||
typealias SubroutineCaller = (sub: Subroutine, args: List<RuntimeValue>, startAtLabel: Label?) -> RuntimeValue?
|
||||
|
||||
|
||||
class EvalContext(val program: Program, val mem: Memory, val statusflags: StatusFlags,
|
||||
val runtimeVars: RuntimeVariables,
|
||||
val performBuiltinFunction: (String, List<RuntimeValue>, StatusFlags) -> RuntimeValue?,
|
||||
val executeSubroutine: (sub: Subroutine, args: List<RuntimeValue>, startlabel: Label?) -> RuntimeValue?)
|
||||
val performBuiltinFunction: BuiltinfunctionCaller,
|
||||
val executeSubroutine: SubroutineCaller)
|
||||
|
||||
fun evaluate(expr: IExpression, ctx: EvalContext): RuntimeValue {
|
||||
fun evaluate(expr: Expression, ctx: EvalContext): RuntimeValue {
|
||||
val constval = expr.constValue(ctx.program)
|
||||
if(constval!=null)
|
||||
return RuntimeValue.from(constval, ctx.program.heap)
|
||||
return RuntimeValue.fromLv(constval)
|
||||
|
||||
when(expr) {
|
||||
is LiteralValue -> {
|
||||
return RuntimeValue.from(expr, ctx.program.heap)
|
||||
is NumericLiteralValue -> {
|
||||
return RuntimeValue.fromLv(expr)
|
||||
}
|
||||
is ReferenceLiteralValue -> {
|
||||
return RuntimeValue.fromLv(expr, ctx.program.heap)
|
||||
}
|
||||
is PrefixExpression -> {
|
||||
return when(expr.operator) {
|
||||
@@ -82,8 +91,14 @@ fun evaluate(expr: IExpression, ctx: EvalContext): RuntimeValue {
|
||||
}
|
||||
is AddressOf -> {
|
||||
// we support: address of heap var -> the heap id
|
||||
val heapId = expr.identifier.heapId(ctx.program.namespace)
|
||||
return RuntimeValue(DataType.UWORD, heapId)
|
||||
return try {
|
||||
val heapId = expr.identifier.heapId(ctx.program.namespace)
|
||||
RuntimeValue(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
|
||||
RuntimeValue(DataType.UWORD, address)
|
||||
}
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
val address = evaluate(expr.addressExpression, ctx).wordval!!
|
||||
@@ -94,19 +109,21 @@ fun evaluate(expr: IExpression, ctx: EvalContext): RuntimeValue {
|
||||
val scope = expr.definingScope()
|
||||
val variable = scope.lookup(expr.nameInSource, expr)
|
||||
if(variable is VarDecl) {
|
||||
if(variable.type==VarDeclType.VAR)
|
||||
return ctx.runtimeVars.get(variable.definingScope(), variable.name)
|
||||
else {
|
||||
val address = ctx.runtimeVars.getMemoryAddress(variable.definingScope(), variable.name)
|
||||
return when(variable.datatype) {
|
||||
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, ctx.mem.getUByte(address))
|
||||
DataType.BYTE -> RuntimeValue(DataType.BYTE, ctx.mem.getSByte(address))
|
||||
DataType.UWORD -> RuntimeValue(DataType.UWORD, ctx.mem.getUWord(address))
|
||||
DataType.WORD -> RuntimeValue(DataType.WORD, ctx.mem.getSWord(address))
|
||||
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, ctx.mem.getFloat(address))
|
||||
DataType.STR -> RuntimeValue(DataType.STR, str = ctx.mem.getString(address))
|
||||
DataType.STR_S -> RuntimeValue(DataType.STR_S, str = ctx.mem.getScreencodeString(address))
|
||||
else -> throw VmExecutionException("unexpected datatype $variable")
|
||||
when {
|
||||
variable.type==VarDeclType.VAR -> return ctx.runtimeVars.get(variable.definingScope(), variable.name)
|
||||
variable.datatype==DataType.STRUCT -> throw VmExecutionException("cannot process structs by-value. at ${expr.position}")
|
||||
else -> {
|
||||
val address = ctx.runtimeVars.getMemoryAddress(variable.definingScope(), variable.name)
|
||||
return when(variable.datatype) {
|
||||
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, ctx.mem.getUByte(address))
|
||||
DataType.BYTE -> RuntimeValue(DataType.BYTE, ctx.mem.getSByte(address))
|
||||
DataType.UWORD -> RuntimeValue(DataType.UWORD, ctx.mem.getUWord(address))
|
||||
DataType.WORD -> RuntimeValue(DataType.WORD, ctx.mem.getSWord(address))
|
||||
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, ctx.mem.getFloat(address))
|
||||
DataType.STR -> RuntimeValue(DataType.STR, str = ctx.mem.getString(address))
|
||||
DataType.STR_S -> RuntimeValue(DataType.STR_S, str = ctx.mem.getScreencodeString(address))
|
||||
else -> throw VmExecutionException("unexpected datatype $variable")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package prog8.vm.astvm
|
||||
|
||||
import prog8.compiler.target.c64.Mflpt5
|
||||
import prog8.compiler.target.c64.MachineDefinition
|
||||
import prog8.compiler.target.c64.Petscii
|
||||
import kotlin.math.abs
|
||||
|
||||
@@ -80,7 +80,7 @@ class Memory(private val readObserver: (address: Int, value: Short) -> Short,
|
||||
}
|
||||
|
||||
fun setFloat(address: Int, value: Double) {
|
||||
val mflpt5 = Mflpt5.fromNumber(value)
|
||||
val mflpt5 = MachineDefinition.Mflpt5.fromNumber(value)
|
||||
setUByte(address, mflpt5.b0)
|
||||
setUByte(address+1, mflpt5.b1)
|
||||
setUByte(address+2, mflpt5.b2)
|
||||
@@ -89,7 +89,7 @@ class Memory(private val readObserver: (address: Int, value: Short) -> Short,
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package prog8.vm.astvm
|
||||
|
||||
import prog8.compiler.target.c64.Charset
|
||||
import prog8.compiler.target.c64.Colors
|
||||
import prog8.compiler.target.c64.MachineDefinition
|
||||
import prog8.compiler.target.c64.Petscii
|
||||
import java.awt.*
|
||||
import java.awt.event.KeyEvent
|
||||
@@ -19,7 +18,7 @@ class BitmapScreenPanel : KeyListener, JPanel() {
|
||||
private val g2d = image.graphics as Graphics2D
|
||||
private var cursorX: Int=0
|
||||
private var cursorY: Int=0
|
||||
val keyboardBuffer: Deque<Char> = LinkedList<Char>()
|
||||
val keyboardBuffer: Deque<Char> = LinkedList()
|
||||
|
||||
init {
|
||||
val size = Dimension(image.width * SCALING, image.height * SCALING)
|
||||
@@ -51,16 +50,16 @@ class BitmapScreenPanel : KeyListener, JPanel() {
|
||||
}
|
||||
|
||||
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)
|
||||
cursorX = 0
|
||||
cursorY = 0
|
||||
}
|
||||
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) {
|
||||
g2d.color = Colors.palette[color % Colors.palette.size]
|
||||
g2d.color = MachineDefinition.colorPalette[color % MachineDefinition.colorPalette.size]
|
||||
g2d.drawLine(x1, y1, x2, y2)
|
||||
}
|
||||
|
||||
@@ -95,7 +94,7 @@ class BitmapScreenPanel : KeyListener, JPanel() {
|
||||
val graphics = image.graphics as Graphics2D
|
||||
graphics.drawImage(screen, 0, -8, null)
|
||||
val color = graphics.color
|
||||
graphics.color = Colors.palette[6]
|
||||
graphics.color = MachineDefinition.colorPalette[6]
|
||||
graphics.fillRect(0, 24*8, SCREENWIDTH, 25*8)
|
||||
graphics.color=color
|
||||
cursorY--
|
||||
@@ -103,7 +102,7 @@ class BitmapScreenPanel : KeyListener, JPanel() {
|
||||
}
|
||||
|
||||
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
|
||||
for(clearx in xx until xx+text.length) {
|
||||
g2d.clearRect(8*clearx, 8*y, 8, 8)
|
||||
@@ -117,16 +116,16 @@ class BitmapScreenPanel : KeyListener, JPanel() {
|
||||
|
||||
fun setPetscii(x: Int, y: Int, petscii: Short, color: Short, inverseVideo: Boolean) {
|
||||
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 coloredImage = Charset.getColoredChar(screencode, colorIdx)
|
||||
val coloredImage = MachineDefinition.Charset.getColoredChar(screencode, colorIdx)
|
||||
g2d.drawImage(coloredImage, 8*x, 8*y , null)
|
||||
}
|
||||
|
||||
fun setChar(x: Int, y: Int, screencode: Short, color: Short) {
|
||||
g2d.clearRect(8*x, 8*y, 8, 8)
|
||||
val colorIdx = (color % Colors.palette.size).toShort()
|
||||
val coloredImage = Charset.getColoredChar(screencode, colorIdx)
|
||||
val colorIdx = (color % MachineDefinition.colorPalette.size).toShort()
|
||||
val coloredImage = MachineDefinition.Charset.getColoredChar(screencode, colorIdx)
|
||||
g2d.drawImage(coloredImage, 8*x, 8*y , null)
|
||||
}
|
||||
|
||||
@@ -160,19 +159,19 @@ class ScreenDialog(title: String) : JFrame(title) {
|
||||
// the borders (top, left, right, bottom)
|
||||
val borderTop = JPanel().apply {
|
||||
preferredSize = Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH +2*borderWidth), BitmapScreenPanel.SCALING * borderWidth)
|
||||
background = Colors.palette[14]
|
||||
background = MachineDefinition.colorPalette[14]
|
||||
}
|
||||
val borderBottom = JPanel().apply {
|
||||
preferredSize =Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH +2*borderWidth), BitmapScreenPanel.SCALING * borderWidth)
|
||||
background = Colors.palette[14]
|
||||
background = MachineDefinition.colorPalette[14]
|
||||
}
|
||||
val borderLeft = JPanel().apply {
|
||||
preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT)
|
||||
background = Colors.palette[14]
|
||||
background = MachineDefinition.colorPalette[14]
|
||||
}
|
||||
val borderRight = JPanel().apply {
|
||||
preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT)
|
||||
background = Colors.palette[14]
|
||||
background = MachineDefinition.colorPalette[14]
|
||||
}
|
||||
var c = GridBagConstraints()
|
||||
c.gridx=0; c.gridy=1; c.gridwidth=3
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
package prog8.vm.astvm
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.LiteralValue
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.base.Register
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.ReferenceLiteralValue
|
||||
import prog8.ast.processing.IAstModifyingVisitor
|
||||
import prog8.ast.statements.Statement
|
||||
import prog8.ast.statements.StructDecl
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.ast.statements.ZeropageWish
|
||||
import prog8.compiler.HeapValues
|
||||
import prog8.vm.RuntimeValue
|
||||
|
||||
@@ -17,9 +24,12 @@ class VariablesCreator(private val runtimeVariables: RuntimeVariables, private v
|
||||
runtimeVariables.define(program.namespace, Register.Y.name, RuntimeValue(DataType.UBYTE, 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 vdX = VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, Register.X.name, LiteralValue.optimalInteger(255, globalpos), isArray = false, autoGenerated = 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 vdA = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.DONTCARE, null, Register.A.name, null,
|
||||
NumericLiteralValue.optimalInteger(0, globalpos), isArray = false, autogeneratedDontRemove = 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)
|
||||
vdX.linkParents(program.namespace)
|
||||
vdY.linkParents(program.namespace)
|
||||
@@ -30,24 +40,34 @@ class VariablesCreator(private val runtimeVariables: RuntimeVariables, private v
|
||||
super.visit(program)
|
||||
}
|
||||
|
||||
override fun visit(decl: VarDecl): IStatement {
|
||||
when(decl.type) {
|
||||
// we can assume the value in the vardecl already has been converted into a constant LiteralValue here.
|
||||
VarDeclType.VAR -> {
|
||||
val value = RuntimeValue.from(decl.value as LiteralValue, heap)
|
||||
runtimeVariables.define(decl.definingScope(), decl.name, value)
|
||||
}
|
||||
VarDeclType.MEMORY -> {
|
||||
runtimeVariables.defineMemory(decl.definingScope(), decl.name, (decl.value as LiteralValue).asIntegerValue!!)
|
||||
}
|
||||
VarDeclType.CONST -> {
|
||||
// consts should have been const-folded away
|
||||
override fun visit(decl: VarDecl): Statement {
|
||||
// if the decl is part of a struct, just skip it
|
||||
if(decl.parent !is StructDecl) {
|
||||
when (decl.type) {
|
||||
VarDeclType.VAR -> {
|
||||
if(decl.datatype!=DataType.STRUCT) {
|
||||
val numericLv = decl.value as? NumericLiteralValue
|
||||
val value = if(numericLv!=null) {
|
||||
RuntimeValue.fromLv(numericLv)
|
||||
} else {
|
||||
val referenceLv = decl.value as ReferenceLiteralValue
|
||||
RuntimeValue.fromLv(referenceLv, heap)
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
// override fun accept(assignment: Assignment): IStatement {
|
||||
// override fun accept(assignment: Assignment): Statement {
|
||||
// if(assignment is VariableInitializationAssignment) {
|
||||
// println("INIT VAR $assignment")
|
||||
// }
|
||||
|
||||
@@ -3,24 +3,32 @@ package prog8tests
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.expressions.LiteralValue
|
||||
import prog8.ast.base.Position
|
||||
import kotlin.test.*
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.ReferenceLiteralValue
|
||||
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
|
||||
}
|
||||
|
||||
private fun sameValueAndType(rv1: ReferenceLiteralValue, rv2: ReferenceLiteralValue): Boolean {
|
||||
return rv1.type==rv2.type && rv1==rv2
|
||||
}
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestParserLiteralValue {
|
||||
class TestParserNumericLiteralValue {
|
||||
|
||||
private val dummyPos = Position("test", 0, 0, 0)
|
||||
|
||||
@Test
|
||||
fun testIdentity() {
|
||||
val v = LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos)
|
||||
val v = NumericLiteralValue(DataType.UWORD, 12345, dummyPos)
|
||||
assertEquals(v, v)
|
||||
assertFalse(v != v)
|
||||
assertTrue(v <= v)
|
||||
@@ -28,104 +36,109 @@ class TestParserLiteralValue {
|
||||
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
|
||||
fun testEqualsAndNotEquals() {
|
||||
assertEquals(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UBYTE, 100, position = dummyPos))
|
||||
assertEquals(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 100, position = dummyPos))
|
||||
assertEquals(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos))
|
||||
assertEquals(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos), LiteralValue(DataType.UBYTE, 254, position = dummyPos))
|
||||
assertEquals(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos))
|
||||
assertEquals(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 12345.0, position = dummyPos))
|
||||
assertEquals(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos), LiteralValue(DataType.UBYTE, 100, position = dummyPos))
|
||||
assertEquals(LiteralValue(DataType.FLOAT, floatvalue = 22239.0, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 22239, position = dummyPos))
|
||||
assertEquals(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos))
|
||||
assertEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
|
||||
assertEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 100, dummyPos))
|
||||
assertEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos))
|
||||
assertEquals(NumericLiteralValue(DataType.UWORD, 254, dummyPos), NumericLiteralValue(DataType.UBYTE, 254, dummyPos))
|
||||
assertEquals(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12345, dummyPos))
|
||||
assertEquals(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12345.0, dummyPos))
|
||||
assertEquals(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
|
||||
assertEquals(NumericLiteralValue(DataType.FLOAT, 22239.0, dummyPos), NumericLiteralValue(DataType.UWORD, 22239, 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)))
|
||||
assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 100, position = dummyPos)))
|
||||
assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos)))
|
||||
assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos), LiteralValue(DataType.UBYTE, 254, position = dummyPos)))
|
||||
assertTrue(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos)))
|
||||
assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 12345.0, position = dummyPos)))
|
||||
assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos), LiteralValue(DataType.UBYTE, 100, position = dummyPos)))
|
||||
assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 22239.0, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 22239, position = dummyPos)))
|
||||
assertTrue(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos)))
|
||||
assertTrue(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 100, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 254, dummyPos), NumericLiteralValue(DataType.UBYTE, 254, dummyPos)))
|
||||
assertTrue(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12345, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12345.0, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 22239.0, dummyPos), NumericLiteralValue(DataType.UWORD, 22239, 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(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 101, position = dummyPos))
|
||||
assertNotEquals(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 101.0, position = dummyPos))
|
||||
assertNotEquals(LiteralValue(DataType.UWORD, wordvalue = 245, position = dummyPos), LiteralValue(DataType.UBYTE, 246, position = dummyPos))
|
||||
assertNotEquals(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 12346, position = dummyPos))
|
||||
assertNotEquals(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 12346.0, position = dummyPos))
|
||||
assertNotEquals(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.UBYTE, 9, position = dummyPos))
|
||||
assertNotEquals(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 9, position = dummyPos))
|
||||
assertNotEquals(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 9.0, position = dummyPos))
|
||||
assertNotEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 101, dummyPos))
|
||||
assertNotEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 101, dummyPos))
|
||||
assertNotEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 101.0, dummyPos))
|
||||
assertNotEquals(NumericLiteralValue(DataType.UWORD, 245, dummyPos), NumericLiteralValue(DataType.UBYTE, 246, dummyPos))
|
||||
assertNotEquals(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12346, dummyPos))
|
||||
assertNotEquals(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12346.0, dummyPos))
|
||||
assertNotEquals(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UBYTE, 9, dummyPos))
|
||||
assertNotEquals(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UWORD, 9, 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(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 101, position = dummyPos)))
|
||||
assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 101.0, position = dummyPos)))
|
||||
assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 245, position = dummyPos), LiteralValue(DataType.UBYTE, 246, position = dummyPos)))
|
||||
assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 12346, position = dummyPos)))
|
||||
assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 12346.0, position = dummyPos)))
|
||||
assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.UBYTE, 9, position = dummyPos)))
|
||||
assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 9, position = dummyPos)))
|
||||
assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 9.0, position = dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 101, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 101, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 101.0, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 245, dummyPos), NumericLiteralValue(DataType.UBYTE, 246, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12346, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12346.0, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UBYTE, 9, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UWORD, 9, 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)
|
||||
val lvOneR = LiteralValue(DataType.UBYTE, 1, position = dummyPos)
|
||||
val lvTwoR = LiteralValue(DataType.UBYTE, 2, position = dummyPos)
|
||||
val lvThreeR = LiteralValue(DataType.UBYTE, 3, position = dummyPos)
|
||||
val lvFour= LiteralValue(DataType.UBYTE, 4, position = dummyPos)
|
||||
val lv1 = LiteralValue(DataType.ARRAY_UB, arrayvalue = arrayOf(lvOne, lvTwo, lvThree), position = dummyPos)
|
||||
val lv2 = LiteralValue(DataType.ARRAY_UB, arrayvalue = arrayOf(lvOneR, lvTwoR, lvThreeR), position = dummyPos)
|
||||
val lv3 = LiteralValue(DataType.ARRAY_UB, arrayvalue = arrayOf(lvOneR, lvTwoR, lvFour), position = dummyPos)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEqualsRef() {
|
||||
assertTrue(sameValueAndType(ReferenceLiteralValue(DataType.STR, str = "hello", position = dummyPos), ReferenceLiteralValue(DataType.STR, str = "hello", position = dummyPos)))
|
||||
assertFalse(sameValueAndType(ReferenceLiteralValue(DataType.STR, str = "hello", position = dummyPos), ReferenceLiteralValue(DataType.STR, str = "bye", position = dummyPos)))
|
||||
|
||||
val lvOne = NumericLiteralValue(DataType.UBYTE, 1, dummyPos)
|
||||
val lvTwo = NumericLiteralValue(DataType.UBYTE, 2, 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 = ReferenceLiteralValue(DataType.ARRAY_UB, array = arrayOf(lvOne, lvTwo, lvThree), position = dummyPos)
|
||||
val lv2 = ReferenceLiteralValue(DataType.ARRAY_UB, array = arrayOf(lvOneR, lvTwoR, lvThreeR), position = dummyPos)
|
||||
val lv3 = ReferenceLiteralValue(DataType.ARRAY_UB, array = arrayOf(lvOneR, lvTwoR, lvFour), position = dummyPos)
|
||||
assertEquals(lv1, lv2)
|
||||
assertNotEquals(lv1, lv3)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGreaterThan(){
|
||||
assertTrue(LiteralValue(DataType.UBYTE, 100, position = dummyPos) > LiteralValue(DataType.UBYTE, 99, position = dummyPos))
|
||||
assertTrue(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) > LiteralValue(DataType.UWORD, wordvalue = 253, position = dummyPos))
|
||||
assertTrue(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) > LiteralValue(DataType.FLOAT, floatvalue = 99.9, position = dummyPos))
|
||||
assertTrue(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) > NumericLiteralValue(DataType.UBYTE, 99, dummyPos))
|
||||
assertTrue(NumericLiteralValue(DataType.UWORD, 254, dummyPos) > NumericLiteralValue(DataType.UWORD, 253, 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(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) >= LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos))
|
||||
assertTrue(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) >= LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos))
|
||||
assertTrue(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) >= NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
|
||||
assertTrue(NumericLiteralValue(DataType.UWORD, 254, dummyPos) >= NumericLiteralValue(DataType.UWORD, 254, 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(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) > LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos))
|
||||
assertFalse(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) > LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos))
|
||||
assertFalse(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) > NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
|
||||
assertFalse(NumericLiteralValue(DataType.UWORD, 254, dummyPos) > NumericLiteralValue(DataType.UWORD, 254, 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(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) >= LiteralValue(DataType.UWORD, wordvalue = 255, position = dummyPos))
|
||||
assertFalse(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) >= LiteralValue(DataType.FLOAT, floatvalue = 100.1, position = dummyPos))
|
||||
assertFalse(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) >= NumericLiteralValue(DataType.UBYTE, 101, dummyPos))
|
||||
assertFalse(NumericLiteralValue(DataType.UWORD, 254, dummyPos) >= NumericLiteralValue(DataType.UWORD, 255, dummyPos))
|
||||
assertFalse(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) >= NumericLiteralValue(DataType.FLOAT, 100.1, dummyPos))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLessThan() {
|
||||
assertTrue(LiteralValue(DataType.UBYTE, 100, position = dummyPos) < LiteralValue(DataType.UBYTE, 101, position = dummyPos))
|
||||
assertTrue(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) < LiteralValue(DataType.UWORD, wordvalue = 255, position = dummyPos))
|
||||
assertTrue(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) < LiteralValue(DataType.FLOAT, floatvalue = 100.1, position = dummyPos))
|
||||
assertTrue(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) < NumericLiteralValue(DataType.UBYTE, 101, dummyPos))
|
||||
assertTrue(NumericLiteralValue(DataType.UWORD, 254, dummyPos) < NumericLiteralValue(DataType.UWORD, 255, 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(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) <= LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos))
|
||||
assertTrue(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) <= LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos))
|
||||
assertTrue(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) <= NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
|
||||
assertTrue(NumericLiteralValue(DataType.UWORD, 254, dummyPos) <= NumericLiteralValue(DataType.UWORD, 254, 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(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) < LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos))
|
||||
assertFalse(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) < LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos))
|
||||
assertFalse(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) < NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
|
||||
assertFalse(NumericLiteralValue(DataType.UWORD, 254, dummyPos) < NumericLiteralValue(DataType.UWORD, 254, 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(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) <= LiteralValue(DataType.UWORD, wordvalue = 253, position = dummyPos))
|
||||
assertFalse(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) <= LiteralValue(DataType.FLOAT, floatvalue = 99.9, position = dummyPos))
|
||||
assertFalse(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) <= NumericLiteralValue(DataType.UBYTE, 99, dummyPos))
|
||||
assertFalse(NumericLiteralValue(DataType.UWORD, 254, dummyPos) <= NumericLiteralValue(DataType.UWORD, 253, dummyPos))
|
||||
assertFalse(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) <= NumericLiteralValue(DataType.FLOAT, 99.9, dummyPos))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -116,18 +116,6 @@ class TestRuntimeValue {
|
||||
assertFalse(sameValueAndType(RuntimeValue(DataType.FLOAT, 9.99), RuntimeValue(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()
|
||||
{
|
||||
|
||||
+30
-26
@@ -7,10 +7,15 @@ import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.LiteralValue
|
||||
import prog8.vm.RuntimeValue
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.ReferenceLiteralValue
|
||||
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.RuntimeValue
|
||||
import java.io.CharConversionException
|
||||
import kotlin.test.*
|
||||
|
||||
@@ -140,8 +145,12 @@ class TestZeropage {
|
||||
assertFailsWith<CompilerException> {
|
||||
zp.allocate("", DataType.FLOAT, null)
|
||||
}
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true))
|
||||
zp2.allocate("", DataType.FLOAT, null)
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true))
|
||||
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
|
||||
@@ -160,14 +169,16 @@ class TestZeropage {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO test dontuse option
|
||||
|
||||
@Test
|
||||
fun testFreeSpaces() {
|
||||
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))
|
||||
assertEquals(95, zp2.available())
|
||||
assertEquals(91, zp2.available())
|
||||
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))
|
||||
assertEquals(238, zp4.available())
|
||||
}
|
||||
@@ -197,11 +208,10 @@ class TestZeropage {
|
||||
@Test
|
||||
fun testBasicsafeAllocation() {
|
||||
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> {
|
||||
// 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)
|
||||
}
|
||||
|
||||
@@ -251,16 +261,16 @@ class TestZeropage {
|
||||
@Test
|
||||
fun testEfficientAllocation() {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
|
||||
assertEquals(20, zp.available())
|
||||
assertEquals(0x04, zp.allocate("", DataType.FLOAT, null))
|
||||
assertEquals(0x09, zp.allocate("", DataType.UBYTE, null))
|
||||
assertEquals(0x0d, zp.allocate("", DataType.UWORD, null))
|
||||
assertEquals(16, zp.available())
|
||||
assertEquals(0x04, zp.allocate("", DataType.WORD, null))
|
||||
assertEquals(0x06, zp.allocate("", DataType.UBYTE, null))
|
||||
assertEquals(0x0a, zp.allocate("", DataType.UBYTE, null))
|
||||
assertEquals(0x94, zp.allocate("", DataType.UWORD, null))
|
||||
assertEquals(0xa7, zp.allocate("", DataType.UWORD, null))
|
||||
assertEquals(0xa9, zp.allocate("", DataType.UWORD, null))
|
||||
assertEquals(0xb5, 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(0, zp.available())
|
||||
}
|
||||
@@ -336,8 +346,8 @@ class TestPetscii {
|
||||
|
||||
@Test
|
||||
fun testLiteralValueComparisons() {
|
||||
val ten = LiteralValue(DataType.UWORD, wordvalue = 10, position = Position("", 0, 0, 0))
|
||||
val nine = LiteralValue(DataType.UBYTE, bytevalue = 9, position = Position("", 0, 0, 0))
|
||||
val ten = NumericLiteralValue(DataType.UWORD, 10, Position("", 0, 0, 0))
|
||||
val nine = NumericLiteralValue(DataType.UBYTE, 9, Position("", 0, 0, 0))
|
||||
assertEquals(ten, ten)
|
||||
assertNotEquals(ten, nine)
|
||||
assertFalse(ten != ten)
|
||||
@@ -353,17 +363,11 @@ class TestPetscii {
|
||||
assertTrue(ten <= ten)
|
||||
assertFalse(ten < ten)
|
||||
|
||||
val abc = LiteralValue(DataType.STR, strvalue = "abc", position = Position("", 0, 0, 0))
|
||||
val abd = LiteralValue(DataType.STR, strvalue = "abd", position = Position("", 0, 0, 0))
|
||||
val abc = ReferenceLiteralValue(DataType.STR, str = "abc", position = Position("", 0, 0, 0))
|
||||
val abd = ReferenceLiteralValue(DataType.STR, str = "abd", position = Position("", 0, 0, 0))
|
||||
assertEquals(abc, abc)
|
||||
assertTrue(abc!=abd)
|
||||
assertFalse(abc!=abc)
|
||||
assertTrue(abc < abd)
|
||||
assertTrue(abc <= abd)
|
||||
assertFalse(abd <= abc)
|
||||
assertTrue(abd >= abc)
|
||||
assertTrue(abd > abc)
|
||||
assertFalse(abc > abd)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+68
-17
@@ -2,6 +2,8 @@
|
||||
Writing and building a program
|
||||
==============================
|
||||
|
||||
.. _building_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 recent "prog8compiler.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.
|
||||
#. 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-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
|
||||
#. it will output "prog8compiler.jar" file which contains everything.
|
||||
#. run the compiler with "java -jar prog8compiler.jar" to see how you can use it.
|
||||
The Gradle build system is used to build the compiler.
|
||||
The most interesting gradle commands to run are probably:
|
||||
|
||||
**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
|
||||
#. it will create the commands and required libraries in the "./compiler/build/install/p8compile/" directory
|
||||
#. run the compiler with the "./compiler/build/install/p8compile/bin/p8compile" command to see how you can use it.
|
||||
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!
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
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.
|
||||
|
||||
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``
|
||||
Other options are also available, see the introduction page about how
|
||||
to build and run the compiler.
|
||||
|
||||
|
||||
By default, assembly code is generated and written to ``sourcefile.asm``.
|
||||
@@ -71,6 +87,13 @@ 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
|
||||
(for the machine code monitor) into the emulator.
|
||||
|
||||
Continuous compilation mode
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Almost instant compilation times (<0.1 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.
|
||||
|
||||
|
||||
Module source code files
|
||||
------------------------
|
||||
@@ -85,6 +108,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.
|
||||
|
||||
|
||||
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 (with Vice)
|
||||
@@ -131,3 +161,24 @@ or::
|
||||
|
||||
$ ./p8compile.sh -emu examples/rasterbars.p8
|
||||
|
||||
|
||||
|
||||
Virtual Machine
|
||||
---------------
|
||||
|
||||
You may have noticed the ``-avm`` and ``-vm`` command line options for the compiler:
|
||||
|
||||
-avm
|
||||
Launches the "AST virtual machine" 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.
|
||||
|
||||
-vm <vm bytecode file>
|
||||
Launches the "intermediate code VM"
|
||||
it interprets the intermediate code that the compiler can write when using the ``-writevm``
|
||||
option. This is the code that will be fed to the assembly code generator,
|
||||
so you'll skip that last step.
|
||||
+41
-36
@@ -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
|
||||
|
||||
|
||||
Code example
|
||||
------------
|
||||
Code examples
|
||||
-------------
|
||||
|
||||
This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
||||
|
||||
%import c64utils
|
||||
%zeropage basicsafe
|
||||
|
||||
~ main {
|
||||
main {
|
||||
|
||||
ubyte[256] sieve
|
||||
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
|
||||
:align: center
|
||||
: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
|
||||
------------------------------
|
||||
|
||||
@@ -106,7 +140,8 @@ Design principles and features
|
||||
- 'One statement per line' code style, resulting in clear readable programs.
|
||||
- Modular programming and scoping via modules, code blocks, and subroutines.
|
||||
- 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)
|
||||
- Complex nested expressions are possible
|
||||
- Values are typed. Types supported include signed and unsigned bytes and words, arrays, strings and floats.
|
||||
@@ -150,37 +185,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).
|
||||
But a bare Kotlin SDK installation should work just as well.
|
||||
|
||||
|
||||
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!
|
||||
Instructions on how to obtain a working compiler are in :ref:`building_compiler`.
|
||||
|
||||
|
||||
.. toctree::
|
||||
|
||||
+66
-14
@@ -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
|
||||
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...
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
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)
|
||||
- subroutines (nested named scopes)
|
||||
- for, while, repeat loops (anonymous scope)
|
||||
- if statements and branching conditionals (anonymous scope)
|
||||
- subroutines (nested named 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
|
||||
@@ -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)::
|
||||
|
||||
~ main {
|
||||
main {
|
||||
sub start () {
|
||||
; program entrypoint code here
|
||||
return
|
||||
@@ -237,6 +245,7 @@ Arrays
|
||||
^^^^^^
|
||||
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[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]
|
||||
@@ -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,
|
||||
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::
|
||||
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),
|
||||
@@ -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.
|
||||
|
||||
|
||||
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
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -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
|
||||
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
|
||||
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
|
||||
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::
|
||||
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
|
||||
@@ -334,12 +390,6 @@ are generated)
|
||||
this behavior may change in a future version so that subsequent runs always
|
||||
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
|
||||
-----
|
||||
@@ -358,7 +408,9 @@ 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
|
||||
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)
|
||||
Loop variables that are declared inline are scoped in the loop body so they're not accessible at all after the loop finishes.
|
||||
Loop variables that are declared inline are not different to them being
|
||||
defined in a separate var declaration in the subroutine, it's just a readability convenience.
|
||||
(this may change in the future if the compiler gets more advanced with additional sub-scopes)
|
||||
|
||||
|
||||
Conditional Execution
|
||||
|
||||
@@ -74,6 +74,7 @@ Directives
|
||||
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
|
||||
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`.
|
||||
|
||||
@@ -173,7 +174,7 @@ Code blocks
|
||||
A named block of actual program code. Itefines a *scope* (also known as 'namespace') and
|
||||
can contain Prog8 *code*, *directives*, *variable declarations* and *subroutines*::
|
||||
|
||||
~ <blockname> [<address>] {
|
||||
<blockname> [<address>] {
|
||||
<directives>
|
||||
<variables>
|
||||
<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.
|
||||
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...
|
||||
}
|
||||
|
||||
@@ -215,7 +216,7 @@ Variable declarations
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
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
|
||||
when selecting variables to be put into zeropage.
|
||||
The syntax is::
|
||||
@@ -231,10 +232,11 @@ Various examples::
|
||||
str name = "my name is Irmen"
|
||||
uword address = &counter
|
||||
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
|
||||
|
||||
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
|
||||
@@ -358,6 +360,26 @@ Syntax is familiar with brackets: ``arrayvar[x]`` ::
|
||||
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
|
||||
---------
|
||||
@@ -366,7 +388,7 @@ arithmetic: ``+`` ``-`` ``*`` ``/`` ``**`` ``%``
|
||||
``+``, ``-``, ``*``, ``/`` 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 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).
|
||||
|
||||
bitwise arithmetic: ``&`` ``|`` ``^`` ``~`` ``<<`` ``>>``
|
||||
|
||||
@@ -168,7 +168,7 @@ These routines are::
|
||||
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::
|
||||
|
||||
~ irq {
|
||||
irq {
|
||||
sub irq() {
|
||||
; ... irq handling here ...
|
||||
}
|
||||
|
||||
+22
-19
@@ -2,6 +2,28 @@
|
||||
TODO
|
||||
====
|
||||
|
||||
|
||||
Fixes
|
||||
^^^^^
|
||||
variable naming issue::
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
for A in 0 to 10 {
|
||||
ubyte note1 = 44
|
||||
Y+=note1
|
||||
}
|
||||
delay(1)
|
||||
|
||||
sub delay(ubyte note1) { ; TODO: redef of note1 above, conflicts because that one was moved to the zeropage
|
||||
A= note1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Memory Block Operations integrated in language?
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -52,25 +74,6 @@ Allocate a fixed word in ZP that is the TOS so we can operate on TOS directly
|
||||
without having to to index into the stack?
|
||||
|
||||
|
||||
structs?
|
||||
^^^^^^^^
|
||||
|
||||
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
|
||||
^^^^
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
%import c64utils
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
|
||||
byte bb
|
||||
ubyte ub
|
||||
word ww
|
||||
uword uw
|
||||
&ubyte membyte=9999
|
||||
&uword memword=9999
|
||||
ubyte[10] barray
|
||||
|
||||
sub start() {
|
||||
lsr(A)
|
||||
lsl(A)
|
||||
ror(A)
|
||||
rol(A)
|
||||
ror2(A)
|
||||
rol2(A)
|
||||
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))
|
||||
lsr(barray[1])
|
||||
lsl(barray[1])
|
||||
ror(barray[1])
|
||||
rol(barray[1])
|
||||
ror2(barray[1])
|
||||
rol2(barray[1])
|
||||
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -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')
|
||||
}
|
||||
}
|
||||
@@ -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')
|
||||
}
|
||||
}
|
||||
@@ -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')
|
||||
}
|
||||
}
|
||||
@@ -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')
|
||||
}
|
||||
}
|
||||
@@ -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')
|
||||
}
|
||||
}
|
||||
@@ -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')
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
%import c64lib
|
||||
|
||||
~ main {
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
|
||||
@@ -18,10 +18,10 @@ sub start() {
|
||||
|
||||
while(true) {
|
||||
for uword note in notes {
|
||||
ubyte n1 = lsb(note)
|
||||
ubyte n2 = msb(note)
|
||||
c64.FREQ1 = music_freq_table[n1] ; set lo+hi freq of voice 1
|
||||
c64.FREQ2 = music_freq_table[n2] ; set lo+hi freq of voice 2
|
||||
ubyte note1 = lsb(note)
|
||||
ubyte note2 = msb(note)
|
||||
c64.FREQ1 = music_freq_table[note1] ; set lo+hi freq of voice 1
|
||||
c64.FREQ2 = music_freq_table[note2] ; set lo+hi freq of voice 2
|
||||
|
||||
; retrigger voice 1 and 2 ADSR
|
||||
c64.CR1 = waveform <<4 | 0
|
||||
@@ -29,7 +29,7 @@ sub start() {
|
||||
c64.CR1 = waveform <<4 | 1
|
||||
c64.CR2 = waveform <<4 | 1
|
||||
|
||||
print_notes(n1, n2)
|
||||
print_notes(note1, note2)
|
||||
delay()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
%zeropage basicsafe
|
||||
|
||||
|
||||
~ main {
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
%import c64flt
|
||||
%zeropage basicsafe
|
||||
|
||||
~ main {
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
%zeropage basicsafe
|
||||
|
||||
|
||||
~ main {
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
%zeropage basicsafe
|
||||
|
||||
|
||||
~ main {
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
%zeropage basicsafe
|
||||
|
||||
|
||||
~ main {
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
%import c64utils
|
||||
%zeropage basicsafe
|
||||
|
||||
~ main {
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
%import c64flt
|
||||
%zeropage basicsafe
|
||||
|
||||
~ main {
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
%import c64utils
|
||||
%zeropage basicsafe
|
||||
|
||||
~ main {
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
%import c64utils
|
||||
%zeropage basicsafe
|
||||
|
||||
~ main {
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
%import c64utils
|
||||
%zeropage basicsafe
|
||||
|
||||
~ main {
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loaded 100 of 137 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user