Compare commits

..

14 Commits
v1.51 ... v1.52

55 changed files with 4288 additions and 3681 deletions

View File

@ -1,11 +1,11 @@
language: java language: java
sudo: false
# jdk: openjdk8 # jdk: openjdk8
# dist: xenial # dist: xenial
# sudo: false
before_install: before_install:
- chmod +x gradlew - chmod +x gradlew
script: script:
- ./gradlew test - gradle test

View File

@ -484,8 +484,8 @@ internal class Compiler(private val program: Program) {
translatePrefixOperator(expr.operator, expr.expression.inferType(program)) translatePrefixOperator(expr.operator, expr.expression.inferType(program))
} }
is BinaryExpression -> { is BinaryExpression -> {
val leftDt = expr.left.inferType(program)!! val leftDt = expr.left.inferType(program)
val rightDt = expr.right.inferType(program)!! val rightDt = expr.right.inferType(program)
val (commonDt, _) = expr.commonDatatype(leftDt, rightDt, expr.left, expr.right) val (commonDt, _) = expr.commonDatatype(leftDt, rightDt, expr.left, expr.right)
translate(expr.left) translate(expr.left)
if(leftDt!=commonDt) if(leftDt!=commonDt)
@ -654,7 +654,7 @@ internal class Compiler(private val program: Program) {
// cast type if needed // cast type if needed
if(builtinFuncParams!=null) { if(builtinFuncParams!=null) {
val paramDts = builtinFuncParams[index].possibleDatatypes val paramDts = builtinFuncParams[index].possibleDatatypes
val argDt = arg.inferType(program)!! val argDt = arg.inferType(program)
if(argDt !in paramDts) { if(argDt !in paramDts) {
for(paramDt in paramDts.sorted()) for(paramDt in paramDts.sorted())
if(tryConvertType(argDt, paramDt)) if(tryConvertType(argDt, paramDt))
@ -827,8 +827,8 @@ internal class Compiler(private val program: Program) {
// swap(x,y) is treated differently, it's not a normal function call // swap(x,y) is treated differently, it's not a normal function call
if (args.size != 2) if (args.size != 2)
throw AstException("swap requires 2 arguments") throw AstException("swap requires 2 arguments")
val dt1 = args[0].inferType(program)!! val dt1 = args[0].inferType(program)
val dt2 = args[1].inferType(program)!! val dt2 = args[1].inferType(program)
if (dt1 != dt2) if (dt1 != dt2)
throw AstException("swap requires 2 args of identical type") throw AstException("swap requires 2 args of identical type")
if (args[0].constValue(program) != null || args[1].constValue(program) != null) if (args[0].constValue(program) != null || args[1].constValue(program) != null)
@ -861,7 +861,7 @@ internal class Compiler(private val program: Program) {
// (subroutine arguments are not passed via the stack!) // (subroutine arguments are not passed via the stack!)
for (arg in arguments.zip(subroutine.parameters)) { for (arg in arguments.zip(subroutine.parameters)) {
translate(arg.first) translate(arg.first)
convertType(arg.first.inferType(program)!!, arg.second.type) // convert types of arguments to required parameter type convertType(arg.first.inferType(program), arg.second.type) // convert types of arguments to required parameter type
val opcode = opcodePopvar(arg.second.type) val opcode = opcodePopvar(arg.second.type)
prog.instr(opcode, callLabel = subroutine.scopedname + "." + arg.second.name) prog.instr(opcode, callLabel = subroutine.scopedname + "." + arg.second.name)
} }

View File

@ -14,32 +14,29 @@ as used in many home computers from that era. It is a medium to low level progra
which aims to provide many conveniences over raw assembly code (even when using a macro assembler): which aims to provide many conveniences over raw assembly code (even when using a macro assembler):
- reduction of source code length - reduction of source code length
- easier program understanding (because it's higher level, and way more compact)
- modularity, symbol scoping, subroutines - modularity, symbol scoping, subroutines
- subroutines have enforced input- and output parameter definitions
- various data types other than just bytes (16-bit words, floats, strings) - various data types other than just bytes (16-bit words, floats, strings)
- automatic variable allocations, automatic string variables and string sharing - automatic variable allocations, automatic string and array variables and string sharing
- constant folding in expressions (compile-time evaluation) - subroutines with a input- and output parameter signature
- constant folding in expressions
- conditional branches - conditional branches
- when statement to provide a 'jump table' alternative to if/elseif chains - 'when' statement to provide a concise jump table alternative to if/elseif chains
- structs to group together sets of variables and manipulate them at once - structs to group together sets of variables and manipulate them at once
- automatic type conversions - floating point operations (requires the C64 Basic ROM routines for this)
- 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 - 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 - inline assembly allows you to have full control when every cycle or byte matters
- many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``
Rapid edit-compile-run-debug cycle: Rapid edit-compile-run-debug cycle:
- use modern PC to work on - use modern PC to work on
- quick compilation times (couple of seconds, and less than a second when using the continuous compilation mode) - quick compilation times (seconds)
- option to automatically run the program in the Vice emulator - option to automatically run the program in the Vice emulator
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them - breakpoints, that let the Vice emulator drop into the monitor if execution hits them
- source code labels automatically loaded in Vice emulator so it can show them in disassembly - source code labels automatically loaded in Vice emulator so it can show them in disassembly
- the compiler includes a virtual machine that can execute compiled code directy on the - virtual machine that can execute compiled code directy on the host system,
host system without having to actually convert it to assembly to run on a real 6502. 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. It is mainly targeted at the Commodore-64 machine at this time.
Contributions to add support for other 8-bit (or other?!) machines are welcome. Contributions to add support for other 8-bit (or other?!) machines are welcome.

View File

@ -22,8 +22,6 @@ repositories {
jcenter() jcenter()
} }
sourceCompatibility = 1.8
def prog8version = rootProject.file('compiler/res/version.txt').text.trim() def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
dependencies { dependencies {
@ -49,6 +47,12 @@ compileKotlin {
} }
} }
compileTestKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}
sourceSets { sourceSets {
main { main {
java { java {

View File

@ -8,7 +8,7 @@
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" /> <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build" /> <excludeFolder url="file://$MODULE_DIR$/build" />
</content> </content>
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" /> <orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" /> <orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="library" name="antlr-runtime-4.7.2" level="project" /> <orderEntry type="library" name="antlr-runtime-4.7.2" level="project" />

View File

@ -954,6 +954,16 @@ func_sum_f .proc
bne - bne -
+ jmp push_fac1_as_result + jmp push_fac1_as_result
.pend .pend
sign_f .proc
jsr pop_float_fac1
jsr SIGN
sta c64.ESTACK_LO,x
dex
rts
.pend
}} }}
} ; ------ end of block c64flt } ; ------ end of block c64flt

View File

@ -643,3 +643,40 @@ mul_word_40 .proc
sta c64.ESTACK_HI+1,x sta c64.ESTACK_HI+1,x
rts rts
.pend .pend
sign_b .proc
lda c64.ESTACK_LO+1,x
beq _sign_zero
bmi _sign_neg
_sign_pos lda #1
sta c64.ESTACK_LO+1,x
rts
_sign_neg lda #-1
_sign_zero sta c64.ESTACK_LO+1,x
rts
.pend
sign_ub .proc
lda c64.ESTACK_LO+1,x
beq sign_b._sign_zero
bne sign_b._sign_pos
.pend
sign_w .proc
lda c64.ESTACK_HI+1,x
bmi sign_b._sign_neg
beq sign_ub
bne sign_b._sign_pos
.pend
sign_uw .proc
lda c64.ESTACK_HI+1,x
beq _sign_possibly_zero
_sign_pos lda #1
sta c64.ESTACK_LO+1,x
rts
_sign_possibly_zero lda c64.ESTACK_LO+1,x
bne _sign_pos
sta c64.ESTACK_LO+1,x
rts
.pend

View File

@ -1 +1 @@
1.51 1.52

View File

@ -77,7 +77,7 @@ private fun compileMain(args: Array<String>) {
val event = watchservice.take() val event = watchservice.take()
for(changed in event.pollEvents()) { for(changed in event.pollEvents()) {
val changedPath = changed.context() as Path val changedPath = changed.context() as Path
println(" change detected: ${changedPath}") println(" change detected: $changedPath")
} }
event.reset() event.reset()
println("\u001b[H\u001b[2J") // clear the screen println("\u001b[H\u001b[2J") // clear the screen

View File

@ -132,7 +132,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
for(param in subroutine.parameters.zip(subroutine.asmParameterRegisters)) { for(param in subroutine.parameters.zip(subroutine.asmParameterRegisters)) {
val reg = val reg =
when { when {
true==param.second.stack -> "stack" param.second.stack -> "stack"
param.second.registerOrPair!=null -> param.second.registerOrPair.toString() param.second.registerOrPair!=null -> param.second.registerOrPair.toString()
param.second.statusflag!=null -> param.second.statusflag.toString() param.second.statusflag!=null -> param.second.statusflag.toString()
else -> "?????1" else -> "?????1"
@ -256,15 +256,12 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
output(numLiteral.number.toString()) output(numLiteral.number.toString())
} }
override fun visit(refLiteral: ReferenceLiteralValue) { override fun visit(string: StringLiteralValue) {
when { output("\"${escape(string.value)}\"")
refLiteral.isString -> output("\"${escape(refLiteral.str!!)}\"") }
refLiteral.isArray -> {
if(refLiteral.array!=null) { override fun visit(array: ArrayLiteralValue) {
outputListMembers(refLiteral.array.asSequence(), '[', ']') outputListMembers(array.value.asSequence(), '[', ']')
}
}
}
} }
private fun outputListMembers(array: Sequence<Expression>, openchar: Char, closechar: Char) { private fun outputListMembers(array: Sequence<Expression>, openchar: Char, closechar: Char) {

View File

@ -429,7 +429,7 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
else -> throw FatalAstException("invalid datatype for numeric literal") else -> throw FatalAstException("invalid datatype for numeric literal")
} }
litval.floatliteral()!=null -> NumericLiteralValue(DataType.FLOAT, litval.floatliteral().toAst(), 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.stringliteral()!=null -> StringLiteralValue(DataType.STR, unescape(litval.stringliteral().text, litval.toPosition()), position = litval.toPosition())
litval.charliteral()!=null -> { litval.charliteral()!=null -> {
try { try {
NumericLiteralValue(DataType.UBYTE, Petscii.encodePetscii(unescape(litval.charliteral().text, litval.toPosition()), true)[0], litval.toPosition()) NumericLiteralValue(DataType.UBYTE, Petscii.encodePetscii(unescape(litval.charliteral().text, litval.toPosition()), true)[0], litval.toPosition())
@ -438,10 +438,10 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
} }
} }
litval.arrayliteral()!=null -> { litval.arrayliteral()!=null -> {
val array = litval.arrayliteral()?.toAst() val array = litval.arrayliteral().toAst()
// the actual type of the arraysize can not yet be determined here (missing namespace & heap) // the actual type of the arraysize can not yet be determined here (missing namespace & heap)
// the ConstantFold takes care of that and converts the type if needed. // the ConstantFold takes care of that and converts the type if needed.
ReferenceLiteralValue(DataType.ARRAY_UB, array = array, position = litval.toPosition()) ArrayLiteralValue(DataType.ARRAY_UB, array, position = litval.toPosition())
} }
litval.structliteral()!=null -> { litval.structliteral()!=null -> {
val values = litval.structliteral().expression().map { it.toAst() } val values = litval.structliteral().expression().map { it.toAst() }
@ -596,10 +596,10 @@ private fun prog8Parser.WhenstmtContext.toAst(): WhenStatement {
private fun prog8Parser.When_choiceContext.toAst(): WhenChoice { private fun prog8Parser.When_choiceContext.toAst(): WhenChoice {
val values = expression_list()?.toAst() val values = expression_list()?.toAst()
val stmt = statement()?.toAst() val stmt = statement()?.toAst()
val stmt_block = statement_block()?.toAst()?.toMutableList() ?: mutableListOf() val stmtBlock = statement_block()?.toAst()?.toMutableList() ?: mutableListOf()
if(stmt!=null) if(stmt!=null)
stmt_block.add(stmt) stmtBlock.add(stmt)
val scope = AnonymousScope(stmt_block, toPosition()) val scope = AnonymousScope(stmtBlock, toPosition())
return WhenChoice(values, scope, toPosition()) return WhenChoice(values, scope, toPosition())
} }

View File

@ -4,7 +4,7 @@ import prog8.ast.Module
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.processing.* import prog8.ast.processing.*
import prog8.compiler.CompilationOptions import prog8.compiler.CompilationOptions
import prog8.compiler.target.c64.codegen2.AnonymousScopeVarsCleanup import prog8.compiler.target.c64.codegen.AnonymousScopeVarsCleanup
import prog8.optimizer.FlattenAnonymousScopesAndRemoveNops import prog8.optimizer.FlattenAnonymousScopesAndRemoveNops
@ -40,6 +40,11 @@ internal fun Program.reorderStatements() {
checker.visit(this) checker.visit(this)
} }
internal fun Program.addTypecasts() {
val caster = TypecastsAdder(this)
caster.visit(this)
}
internal fun Module.checkImportedValid() { internal fun Module.checkImportedValid() {
val checker = ImportedModuleDirectiveRemover() val checker = ImportedModuleDirectiveRemover()
checker.visit(this) checker.visit(this)

View File

@ -30,7 +30,7 @@ sealed class Expression: Node {
abstract fun accept(visitor: IAstModifyingVisitor): Expression abstract fun accept(visitor: IAstModifyingVisitor): Expression
abstract fun accept(visitor: IAstVisitor) abstract fun accept(visitor: IAstVisitor)
abstract fun referencesIdentifiers(vararg name: String): Boolean // todo: remove this and add identifier usage tracking into CallGraph instead abstract fun referencesIdentifiers(vararg name: String): Boolean // todo: remove this and add identifier usage tracking into CallGraph instead
abstract fun inferType(program: Program): DataType? abstract fun inferType(program: Program): InferredTypes.InferredType
infix fun isSameAs(other: Expression): Boolean { infix fun isSameAs(other: Expression): Boolean {
if(this===other) if(this===other)
@ -68,7 +68,7 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name) override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name)
override fun inferType(program: Program): DataType? = expression.inferType(program) override fun inferType(program: Program): InferredTypes.InferredType = expression.inferType(program)
override fun toString(): String { override fun toString(): String {
return "Prefix($operator $expression)" return "Prefix($operator $expression)"
@ -94,15 +94,22 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String) = left.referencesIdentifiers(*name) || right.referencesIdentifiers(*name) override fun referencesIdentifiers(vararg name: String) = left.referencesIdentifiers(*name) || right.referencesIdentifiers(*name)
override fun inferType(program: Program): DataType? { override fun inferType(program: Program): InferredTypes.InferredType {
val leftDt = left.inferType(program) val leftDt = left.inferType(program)
val rightDt = right.inferType(program) val rightDt = right.inferType(program)
return when (operator) { return when (operator) {
"+", "-", "*", "**", "%", "/" -> if (leftDt == null || rightDt == null) null else { "+", "-", "*", "**", "%", "/" -> {
try { if (!leftDt.isKnown || !rightDt.isKnown)
commonDatatype(leftDt, rightDt, null, null).first InferredTypes.unknown()
} catch (x: FatalAstException) { else {
null try {
InferredTypes.knownFor(commonDatatype(
leftDt.typeOrElse(DataType.BYTE),
rightDt.typeOrElse(DataType.BYTE),
null, null).first)
} catch (x: FatalAstException) {
InferredTypes.unknown()
}
} }
} }
"&" -> leftDt "&" -> leftDt
@ -111,7 +118,7 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
"and", "or", "xor", "and", "or", "xor",
"<", ">", "<", ">",
"<=", ">=", "<=", ">=",
"==", "!=" -> DataType.UBYTE "==", "!=" -> InferredTypes.knownFor(DataType.UBYTE)
"<<", ">>" -> leftDt "<<", ">>" -> leftDt
else -> throw FatalAstException("resulting datatype check for invalid operator $operator") else -> throw FatalAstException("resulting datatype check for invalid operator $operator")
} }
@ -191,17 +198,16 @@ class ArrayIndexedExpression(var identifier: IdentifierReference,
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String) = identifier.referencesIdentifiers(*name) override fun referencesIdentifiers(vararg name: String) = identifier.referencesIdentifiers(*name)
override fun inferType(program: Program): DataType? { override fun inferType(program: Program): InferredTypes.InferredType {
val target = identifier.targetStatement(program.namespace) val target = identifier.targetStatement(program.namespace)
if (target is VarDecl) { if (target is VarDecl) {
return when (target.datatype) { return when (target.datatype) {
in NumericDatatypes -> null in StringDatatypes -> InferredTypes.knownFor(DataType.UBYTE)
in StringDatatypes -> DataType.UBYTE in ArrayDatatypes -> InferredTypes.knownFor(ArrayElementTypes.getValue(target.datatype))
in ArrayDatatypes -> ArrayElementTypes[target.datatype] else -> InferredTypes.unknown()
else -> throw FatalAstException("invalid dt")
} }
} }
return null return InferredTypes.unknown()
} }
override fun toString(): String { override fun toString(): String {
@ -220,7 +226,7 @@ class TypecastExpression(var expression: Expression, var type: DataType, val imp
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name) override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name)
override fun inferType(program: Program): DataType? = type override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
override fun constValue(program: Program): NumericLiteralValue? { override fun constValue(program: Program): NumericLiteralValue? {
val cv = expression.constValue(program) ?: return null val cv = expression.constValue(program) ?: return null
return cv.cast(type) return cv.cast(type)
@ -243,7 +249,7 @@ data class AddressOf(var identifier: IdentifierReference, override val position:
override fun constValue(program: Program): NumericLiteralValue? = null override fun constValue(program: Program): NumericLiteralValue? = null
override fun referencesIdentifiers(vararg name: String) = false override fun referencesIdentifiers(vararg name: String) = false
override fun inferType(program: Program) = DataType.UWORD override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UWORD)
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
} }
@ -259,7 +265,7 @@ class DirectMemoryRead(var addressExpression: Expression, override val position:
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String) = false override fun referencesIdentifiers(vararg name: String) = false
override fun inferType(program: Program): DataType? = DataType.UBYTE override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UBYTE)
override fun constValue(program: Program): NumericLiteralValue? = null override fun constValue(program: Program): NumericLiteralValue? = null
override fun toString(): String { override fun toString(): String {
@ -315,7 +321,7 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
override fun toString(): String = "NumericLiteral(${type.name}:$number)" override fun toString(): String = "NumericLiteral(${type.name}:$number)"
override fun inferType(program: Program) = type override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
override fun hashCode(): Int = Objects.hash(type, number) override fun hashCode(): Int = Objects.hash(type, number)
@ -399,123 +405,105 @@ class StructLiteralValue(var values: List<Expression>,
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String) = values.any { it.referencesIdentifiers(*name) } override fun referencesIdentifiers(vararg name: String) = values.any { it.referencesIdentifiers(*name) }
override fun inferType(program: Program) = DataType.STRUCT override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.STRUCT)
override fun toString(): String { override fun toString(): String {
return "struct{ ${values.joinToString(", ")} }" return "struct{ ${values.joinToString(", ")} }"
} }
} }
class ReferenceLiteralValue(val type: DataType, // only reference types allowed here class StringLiteralValue(val type: DataType, // only string types
val str: String? = null, val value: String,
val array: Array<Expression>? = null, initHeapId: Int? =null,
// actually, at the moment, we don't have struct literals in the language override val position: Position) : Expression() {
initHeapId: Int? =null,
override val position: Position) : Expression() {
override lateinit var parent: Node 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 var heapId = initHeapId
private set private set
init { override fun linkParents(parent: Node) {
when(type){ this.parent = parent
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 referencesIdentifiers(vararg name: String) = false
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 toString(): String = "'${escape(value)}'"
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
operator fun compareTo(other: StringLiteralValue): Int = value.compareTo(other.value)
override fun hashCode(): Int = Objects.hash(value, type)
override fun equals(other: Any?): Boolean {
if(other==null || other !is StringLiteralValue)
return false
return value==other.value && type==other.type
}
fun addToHeap(heap: HeapValues) {
if (heapId != null)
return
else
heapId = heap.addString(type, value)
}
}
class ArrayLiteralValue(val type: DataType, // only array types
val value: Array<Expression>,
initHeapId: Int? =null,
override val position: Position) : Expression() {
override lateinit var parent: Node
var heapId = initHeapId
private set
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
array?.forEach {it.linkParents(this)} value.forEach {it.linkParents(this)}
} }
override fun referencesIdentifiers(vararg name: String) = value.any { it.referencesIdentifiers(*name) }
override fun constValue(program: Program): NumericLiteralValue? { override fun constValue(program: Program): NumericLiteralValue? = null
// 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: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun toString(): String = "$value"
override fun toString(): String { override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
val valueStr = when(type) { operator fun compareTo(other: ArrayLiteralValue): Int = throw ExpressionError("cannot order compare arrays", position)
in StringDatatypes -> "'${escape(str!!)}'" override fun hashCode(): Int = Objects.hash(value, type)
in ArrayDatatypes -> "$array"
else -> throw FatalAstException("weird ref type")
}
return "RefValueLit($type, $valueStr)"
}
override fun inferType(program: Program) = type
override fun hashCode(): Int = Objects.hash(str, array, type)
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if(other==null || other !is ReferenceLiteralValue) if(other==null || other !is ArrayLiteralValue)
return false return false
if(isArray && other.isArray) return type==other.type && value.contentEquals(other.value)
return array!!.contentEquals(other.array!!) && heapId==other.heapId
if(isString && other.isString)
return str==other.str && heapId==other.heapId
if(type!=other.type)
return false
return compareTo(other) == 0
} }
operator fun compareTo(other: ReferenceLiteralValue): Int { fun cast(targettype: DataType): ArrayLiteralValue? {
throw ExpressionError("cannot order compare type $type with ${other.type}", other.position)
}
fun cast(targettype: DataType): ReferenceLiteralValue? {
if(type==targettype) if(type==targettype)
return this return this
when(type) { if(targettype in ArrayDatatypes) {
in StringDatatypes -> { val elementType = ArrayElementTypes.getValue(targettype)
if(targettype in StringDatatypes) val castArray = value.map{
return ReferenceLiteralValue(targettype, str, position = position) val num = it as? NumericLiteralValue
} if(num==null) {
in ArrayDatatypes -> { // an array of UWORDs could possibly also contain AddressOfs
if(targettype in ArrayDatatypes) { if (elementType != DataType.UWORD || it !is AddressOf)
val elementType = ArrayElementTypes.getValue(targettype) throw FatalAstException("weird array element $it")
val castArray = array!!.map{ it
val num = it as? NumericLiteralValue } else {
if(num==null) { try {
// an array of UWORDs could possibly also contain AddressOfs num.cast(elementType)
if (elementType != DataType.UWORD || it !is AddressOf) } catch(x: ExpressionError) {
throw FatalAstException("weird array element $it") return null
it }
} else {
num.cast(elementType) // TODO this can throw an exception
}
}.toTypedArray()
return ReferenceLiteralValue(targettype, null, array=castArray, position = position)
} }
} }.toTypedArray()
else -> {} return ArrayLiteralValue(targettype, castArray, position = position)
} }
return null // invalid type conversion from $this to $targettype return null // invalid type conversion from $this to $targettype
} }
fun addToHeap(heap: HeapValues) { fun addToHeap(heap: HeapValues) {
if (heapId != null) return if (heapId != null)
if (str != null) { return
heapId = heap.addString(type, str) else {
} if(value.any {it is AddressOf }) {
else if (array!=null) { val intArrayWithAddressOfs = value.map {
if(array.any {it is AddressOf }) {
val intArrayWithAddressOfs = array.map {
when (it) { when (it) {
is AddressOf -> IntegerOrAddressOf(null, it) is AddressOf -> IntegerOrAddressOf(null, it)
is NumericLiteralValue -> IntegerOrAddressOf(it.number.toInt(), null) is NumericLiteralValue -> IntegerOrAddressOf(it.number.toInt(), null)
@ -524,7 +512,7 @@ class ReferenceLiteralValue(val type: DataType, // only reference types allo
} }
heapId = heap.addIntegerArray(type, intArrayWithAddressOfs.toTypedArray()) heapId = heap.addIntegerArray(type, intArrayWithAddressOfs.toTypedArray())
} else { } else {
val valuesInArray = array.map { (it as? NumericLiteralValue)?.number } val valuesInArray = value.map { (it as? NumericLiteralValue)?.number }
if(null !in valuesInArray) { if(null !in valuesInArray) {
heapId = if (type == DataType.ARRAY_F) { heapId = if (type == DataType.ARRAY_F) {
val doubleArray = valuesInArray.map { it!!.toDouble() }.toDoubleArray() val doubleArray = valuesInArray.map { it!!.toDouble() }.toDoubleArray()
@ -556,18 +544,18 @@ class RangeExpr(var from: Expression,
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String): Boolean = from.referencesIdentifiers(*name) || to.referencesIdentifiers(*name) override fun referencesIdentifiers(vararg name: String): Boolean = from.referencesIdentifiers(*name) || to.referencesIdentifiers(*name)
override fun inferType(program: Program): DataType? { override fun inferType(program: Program): InferredTypes.InferredType {
val fromDt=from.inferType(program) val fromDt=from.inferType(program)
val toDt=to.inferType(program) val toDt=to.inferType(program)
return when { return when {
fromDt==null || toDt==null -> null !fromDt.isKnown || !toDt.isKnown -> InferredTypes.unknown()
fromDt== DataType.UBYTE && toDt== DataType.UBYTE -> DataType.ARRAY_UB fromDt istype DataType.UBYTE && toDt istype DataType.UBYTE -> InferredTypes.knownFor(DataType.ARRAY_UB)
fromDt== DataType.UWORD && toDt== DataType.UWORD -> DataType.ARRAY_UW fromDt istype DataType.UWORD && toDt istype DataType.UWORD -> InferredTypes.knownFor(DataType.ARRAY_UW)
fromDt== DataType.STR && toDt== DataType.STR -> DataType.STR fromDt istype DataType.STR && toDt istype DataType.STR -> InferredTypes.knownFor(DataType.STR)
fromDt== DataType.STR_S && toDt== DataType.STR_S -> DataType.STR_S fromDt istype DataType.STR_S && toDt istype DataType.STR_S -> InferredTypes.knownFor(DataType.STR_S)
fromDt== DataType.WORD || toDt== DataType.WORD -> DataType.ARRAY_W fromDt istype DataType.WORD || toDt istype DataType.WORD -> InferredTypes.knownFor(DataType.ARRAY_W)
fromDt== DataType.BYTE || toDt== DataType.BYTE -> DataType.ARRAY_B fromDt istype DataType.BYTE || toDt istype DataType.BYTE -> InferredTypes.knownFor(DataType.ARRAY_B)
else -> DataType.ARRAY_UB else -> InferredTypes.knownFor(DataType.ARRAY_UB)
} }
} }
override fun toString(): String { override fun toString(): String {
@ -585,12 +573,12 @@ class RangeExpr(var from: Expression,
fun toConstantIntegerRange(): IntProgression? { fun toConstantIntegerRange(): IntProgression? {
val fromVal: Int val fromVal: Int
val toVal: Int val toVal: Int
val fromRlv = from as? ReferenceLiteralValue val fromString = from as? StringLiteralValue
val toRlv = to as? ReferenceLiteralValue val toString = to as? StringLiteralValue
if(fromRlv!=null && fromRlv.isString && toRlv!=null && toRlv.isString) { if(fromString!=null && toString!=null ) {
// string range -> int range over petscii values // string range -> int range over petscii values
fromVal = Petscii.encodePetscii(fromRlv.str!!, true)[0].toInt() fromVal = Petscii.encodePetscii(fromString.value, true)[0].toInt()
toVal = Petscii.encodePetscii(toRlv.str!!, true)[0].toInt() toVal = Petscii.encodePetscii(toString.value, true)[0].toInt()
} else { } else {
val fromLv = from as? NumericLiteralValue val fromLv = from as? NumericLiteralValue
val toLv = to as? NumericLiteralValue val toLv = to as? NumericLiteralValue
@ -601,17 +589,21 @@ class RangeExpr(var from: Expression,
toVal = toLv.number.toInt() toVal = toLv.number.toInt()
} }
val stepVal = (step as? NumericLiteralValue)?.number?.toInt() ?: 1 val stepVal = (step as? NumericLiteralValue)?.number?.toInt() ?: 1
return when { return makeRange(fromVal, toVal, stepVal)
fromVal <= toVal -> when { }
stepVal <= 0 -> IntRange.EMPTY }
stepVal == 1 -> fromVal..toVal
else -> fromVal..toVal step stepVal internal fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
} return when {
else -> when { fromVal <= toVal -> when {
stepVal >= 0 -> IntRange.EMPTY stepVal <= 0 -> IntRange.EMPTY
stepVal == -1 -> fromVal downTo toVal stepVal == 1 -> fromVal..toVal
else -> fromVal downTo toVal step abs(stepVal) else -> fromVal..toVal step stepVal
} }
else -> when {
stepVal >= 0 -> IntRange.EMPTY
stepVal == -1 -> fromVal downTo toVal
else -> fromVal downTo toVal step abs(stepVal)
} }
} }
} }
@ -631,7 +623,7 @@ class RegisterExpr(val register: Register, override val position: Position) : Ex
return "RegisterExpr(register=$register, pos=$position)" return "RegisterExpr(register=$register, pos=$position)"
} }
override fun inferType(program: Program) = DataType.UBYTE override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UBYTE)
} }
data class IdentifierReference(val nameInSource: List<String>, override val position: Position) : Expression() { data class IdentifierReference(val nameInSource: List<String>, override val position: Position) : Expression() {
@ -668,12 +660,12 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String): Boolean = nameInSource.last() in name // @todo is this correct all the time? override fun referencesIdentifiers(vararg name: String): Boolean = nameInSource.last() in name
override fun inferType(program: Program): DataType? { override fun inferType(program: Program): InferredTypes.InferredType {
val targetStmt = targetStatement(program.namespace) val targetStmt = targetStatement(program.namespace)
if(targetStmt is VarDecl) { if(targetStmt is VarDecl) {
return targetStmt.datatype return InferredTypes.knownFor(targetStmt.datatype)
} else { } else {
throw FatalAstException("cannot get datatype from identifier reference ${this}, pos=$position") throw FatalAstException("cannot get datatype from identifier reference ${this}, pos=$position")
} }
@ -686,17 +678,11 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
val value = (node as? VarDecl)?.value ?: throw FatalAstException("requires a reference value") val value = (node as? VarDecl)?.value ?: throw FatalAstException("requires a reference value")
return when (value) { return when (value) {
is IdentifierReference -> value.heapId(namespace) is IdentifierReference -> value.heapId(namespace)
is ReferenceLiteralValue -> value.heapId ?: throw FatalAstException("refLv is not on the heap: $value") is StringLiteralValue -> value.heapId ?: throw FatalAstException("string is not on the heap: $value")
is ArrayLiteralValue -> value.heapId ?: throw FatalAstException("array is not on the heap: $value")
else -> throw FatalAstException("requires a reference 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, class FunctionCall(override var target: IdentifierReference,
@ -729,7 +715,7 @@ class FunctionCall(override var target: IdentifierReference,
if(withDatatypeCheck) { if(withDatatypeCheck) {
val resultDt = this.inferType(program) val resultDt = this.inferType(program)
if(resultValue==null || resultDt == resultValue.type) if(resultValue==null || resultDt istype resultValue.type)
return resultValue return resultValue
throw FatalAstException("evaluated const expression result value doesn't match expected datatype $resultDt, pos=$position") throw FatalAstException("evaluated const expression result value doesn't match expected datatype $resultDt, pos=$position")
} else { } else {
@ -750,27 +736,27 @@ class FunctionCall(override var target: IdentifierReference,
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String): Boolean = target.referencesIdentifiers(*name) || arglist.any{it.referencesIdentifiers(*name)} override fun referencesIdentifiers(vararg name: String): Boolean = target.referencesIdentifiers(*name) || arglist.any{it.referencesIdentifiers(*name)}
override fun inferType(program: Program): DataType? { override fun inferType(program: Program): InferredTypes.InferredType {
val constVal = constValue(program ,false) val constVal = constValue(program ,false)
if(constVal!=null) if(constVal!=null)
return constVal.type return InferredTypes.knownFor(constVal.type)
val stmt = target.targetStatement(program.namespace) ?: return null val stmt = target.targetStatement(program.namespace) ?: return InferredTypes.unknown()
when (stmt) { when (stmt) {
is BuiltinFunctionStatementPlaceholder -> { is BuiltinFunctionStatementPlaceholder -> {
if(target.nameInSource[0] == "set_carry" || target.nameInSource[0]=="set_irqd" || if(target.nameInSource[0] == "set_carry" || target.nameInSource[0]=="set_irqd" ||
target.nameInSource[0] == "clear_carry" || target.nameInSource[0]=="clear_irqd") { target.nameInSource[0] == "clear_carry" || target.nameInSource[0]=="clear_irqd") {
return null // these have no return value return InferredTypes.void() // these have no return value
} }
return builtinFunctionReturnType(target.nameInSource[0], this.arglist, program) return builtinFunctionReturnType(target.nameInSource[0], this.arglist, program)
} }
is Subroutine -> { is Subroutine -> {
if(stmt.returntypes.isEmpty()) if(stmt.returntypes.isEmpty())
return null // no return value return InferredTypes.void() // no return value
if(stmt.returntypes.size==1) if(stmt.returntypes.size==1)
return stmt.returntypes[0] return InferredTypes.knownFor(stmt.returntypes[0])
return null // has multiple return types... so not a single resulting datatype possible return InferredTypes.unknown() // has multiple return types... so not a single resulting datatype possible
} }
else -> return null else -> return InferredTypes.unknown()
} }
} }
} }

View File

@ -0,0 +1,50 @@
package prog8.ast.expressions
import prog8.ast.base.DataType
object InferredTypes {
class InferredType private constructor(val isUnknown: Boolean, val isVoid: Boolean, private var datatype: DataType?) {
init {
if(datatype!=null && (isUnknown || isVoid))
throw IllegalArgumentException("invalid combination of args")
}
val isKnown = datatype!=null
fun typeOrElse(alternative: DataType) = if(isUnknown || isVoid) alternative else datatype!!
infix fun istype(type: DataType): Boolean = if(isUnknown || isVoid) false else this.datatype==type
companion object {
fun unknown() = InferredType(isUnknown = true, isVoid = false, datatype = null)
fun void() = InferredType(isUnknown = false, isVoid = true, datatype = null)
fun known(type: DataType) = InferredType(isUnknown = false, isVoid = false, datatype = type)
}
override fun equals(other: Any?): Boolean {
if(other !is InferredType)
return false
return isVoid==other.isVoid && datatype==other.datatype
}
}
private val unknownInstance = InferredType.unknown()
private val voidInstance = InferredType.void()
private val knownInstances = mapOf(
DataType.UBYTE to InferredType.known(DataType.UBYTE),
DataType.BYTE to InferredType.known(DataType.BYTE),
DataType.UWORD to InferredType.known(DataType.UWORD),
DataType.WORD to InferredType.known(DataType.WORD),
DataType.FLOAT to InferredType.known(DataType.FLOAT),
DataType.STR to InferredType.known(DataType.STR),
DataType.STR_S to InferredType.known(DataType.STR_S),
DataType.ARRAY_UB to InferredType.known(DataType.ARRAY_UB),
DataType.ARRAY_B to InferredType.known(DataType.ARRAY_B),
DataType.ARRAY_UW to InferredType.known(DataType.ARRAY_UW),
DataType.ARRAY_W to InferredType.known(DataType.ARRAY_W),
DataType.ARRAY_F to InferredType.known(DataType.ARRAY_F),
DataType.STRUCT to InferredType.known(DataType.STRUCT)
)
fun void() = voidInstance
fun unknown() = unknownInstance
fun knownFor(type: DataType) = knownInstances.getValue(type)
}

View File

@ -7,7 +7,6 @@ import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.CompilationOptions import prog8.compiler.CompilationOptions
import prog8.compiler.HeapValues
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_NEGATIVE import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_POSITIVE import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_POSITIVE
import prog8.functions.BuiltinFunctions import prog8.functions.BuiltinFunctions
@ -98,14 +97,18 @@ internal class AstChecker(private val program: Program,
} }
if(expectedReturnValues.size==1 && returnStmt.value!=null) { if(expectedReturnValues.size==1 && returnStmt.value!=null) {
val valueDt = returnStmt.value!!.inferType(program) val valueDt = returnStmt.value!!.inferType(program)
if(expectedReturnValues[0]!=valueDt) if(!valueDt.isKnown) {
checkResult.add(ExpressionError("type $valueDt of return value doesn't match subroutine's return type", returnStmt.value!!.position)) checkResult.add(ExpressionError("return value type mismatch", returnStmt.value!!.position))
} else {
if (expectedReturnValues[0] != valueDt.typeOrElse(DataType.STRUCT))
checkResult.add(ExpressionError("type $valueDt of return value doesn't match subroutine's return type", returnStmt.value!!.position))
}
} }
super.visit(returnStmt) super.visit(returnStmt)
} }
override fun visit(ifStatement: IfStatement) { override fun visit(ifStatement: IfStatement) {
if(ifStatement.condition.inferType(program) !in IntegerDatatypes) if(ifStatement.condition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
checkResult.add(ExpressionError("condition value should be an integer type", ifStatement.condition.position)) checkResult.add(ExpressionError("condition value should be an integer type", ifStatement.condition.position))
super.visit(ifStatement) super.visit(ifStatement)
} }
@ -114,7 +117,7 @@ internal class AstChecker(private val program: Program,
if(forLoop.body.containsNoCodeNorVars()) if(forLoop.body.containsNoCodeNorVars())
printWarning("for loop body is empty", forLoop.position) printWarning("for loop body is empty", forLoop.position)
val iterableDt = forLoop.iterable.inferType(program) val iterableDt = forLoop.iterable.inferType(program).typeOrElse(DataType.BYTE)
if(iterableDt !in IterableDatatypes && forLoop.iterable !is RangeExpr) { if(iterableDt !in IterableDatatypes && forLoop.iterable !is RangeExpr) {
checkResult.add(ExpressionError("can only loop over an iterable type", forLoop.position)) checkResult.add(ExpressionError("can only loop over an iterable type", forLoop.position))
} else { } else {
@ -329,7 +332,7 @@ internal class AstChecker(private val program: Program,
override fun visit(repeatLoop: RepeatLoop) { override fun visit(repeatLoop: RepeatLoop) {
if(repeatLoop.untilCondition.referencesIdentifiers("A", "X", "Y")) if(repeatLoop.untilCondition.referencesIdentifiers("A", "X", "Y"))
printWarning("using a register in the loop condition is risky (it could get clobbered)", repeatLoop.untilCondition.position) printWarning("using a register in the loop condition is risky (it could get clobbered)", repeatLoop.untilCondition.position)
if(repeatLoop.untilCondition.inferType(program) !in IntegerDatatypes) if(repeatLoop.untilCondition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
checkResult.add(ExpressionError("condition value should be an integer type", repeatLoop.untilCondition.position)) checkResult.add(ExpressionError("condition value should be an integer type", repeatLoop.untilCondition.position))
super.visit(repeatLoop) super.visit(repeatLoop)
} }
@ -337,7 +340,7 @@ internal class AstChecker(private val program: Program,
override fun visit(whileLoop: WhileLoop) { override fun visit(whileLoop: WhileLoop) {
if(whileLoop.condition.referencesIdentifiers("A", "X", "Y")) if(whileLoop.condition.referencesIdentifiers("A", "X", "Y"))
printWarning("using a register in the loop condition is risky (it could get clobbered)", whileLoop.condition.position) printWarning("using a register in the loop condition is risky (it could get clobbered)", whileLoop.condition.position)
if(whileLoop.condition.inferType(program) !in IntegerDatatypes) if(whileLoop.condition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
checkResult.add(ExpressionError("condition value should be an integer type", whileLoop.condition.position)) checkResult.add(ExpressionError("condition value should be an integer type", whileLoop.condition.position))
super.visit(whileLoop) super.visit(whileLoop)
} }
@ -351,7 +354,8 @@ internal class AstChecker(private val program: Program,
if(stmt.returntypes.size>1) if(stmt.returntypes.size>1)
checkResult.add(ExpressionError("It's not possible to store the multiple results of this asmsub call; you should use a small block of custom inline assembly for this.", assignment.value.position)) checkResult.add(ExpressionError("It's not possible to store the multiple results of this asmsub call; you should use a small block of custom inline assembly for this.", assignment.value.position))
else { else {
if(stmt.returntypes.single()!=assignment.target.inferType(program, assignment)) { val idt = assignment.target.inferType(program, assignment)
if(!idt.isKnown || stmt.returntypes.single()!=idt.typeOrElse(DataType.BYTE)) {
checkResult.add(ExpressionError("return type mismatch", assignment.value.position)) checkResult.add(ExpressionError("return type mismatch", assignment.value.position))
} }
} }
@ -403,7 +407,7 @@ internal class AstChecker(private val program: Program,
} }
} }
} }
val targetDt = assignTarget.inferType(program, assignment) val targetDt = assignTarget.inferType(program, assignment).typeOrElse(DataType.STR)
if(targetDt in StringDatatypes || targetDt in ArrayDatatypes) if(targetDt in StringDatatypes || targetDt in ArrayDatatypes)
checkResult.add(SyntaxError("cannot assign to a string or array type", assignTarget.position)) checkResult.add(SyntaxError("cannot assign to a string or array type", assignTarget.position))
@ -413,13 +417,13 @@ internal class AstChecker(private val program: Program,
throw FatalAstException("augmented assignment should have been converted into normal assignment") throw FatalAstException("augmented assignment should have been converted into normal assignment")
val targetDatatype = assignTarget.inferType(program, assignment) val targetDatatype = assignTarget.inferType(program, assignment)
if (targetDatatype != null) { if (targetDatatype.isKnown) {
val constVal = assignment.value.constValue(program) val constVal = assignment.value.constValue(program)
if (constVal != null) { if (constVal != null) {
checkValueTypeAndRange(targetDatatype, constVal) checkValueTypeAndRange(targetDatatype.typeOrElse(DataType.BYTE), constVal)
} else { } else {
val sourceDatatype: DataType? = assignment.value.inferType(program) val sourceDatatype = assignment.value.inferType(program)
if (sourceDatatype == null) { if (!sourceDatatype.isKnown) {
if (assignment.value is FunctionCall) { if (assignment.value is FunctionCall) {
val targetStmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace) val targetStmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace)
if (targetStmt != null) if (targetStmt != null)
@ -427,7 +431,8 @@ internal class AstChecker(private val program: Program,
} else } else
checkResult.add(ExpressionError("assignment value is invalid or has no proper datatype", assignment.value.position)) checkResult.add(ExpressionError("assignment value is invalid or has no proper datatype", assignment.value.position))
} else { } else {
checkAssignmentCompatible(targetDatatype, assignTarget, sourceDatatype, assignment.value, assignment.position) checkAssignmentCompatible(targetDatatype.typeOrElse(DataType.BYTE), assignTarget,
sourceDatatype.typeOrElse(DataType.BYTE), assignment.value, assignment.position)
} }
} }
} }
@ -526,14 +531,12 @@ internal class AstChecker(private val program: Program,
} }
when(decl.value) { when(decl.value) {
is RangeExpr -> throw FatalAstException("range expression should have been converted to a true array value") is RangeExpr -> throw FatalAstException("range expression should have been converted to a true array value")
is ReferenceLiteralValue -> { is StringLiteralValue -> {
val arraySpec = decl.arraysize ?: ( checkValueTypeAndRangeString(decl.datatype, decl.value as StringLiteralValue)
if((decl.value as ReferenceLiteralValue).isArray) }
ArrayIndex.forArray(decl.value as ReferenceLiteralValue, program.heap) is ArrayLiteralValue -> {
else val arraySpec = decl.arraysize ?: ArrayIndex.forArray(decl.value as ArrayLiteralValue)
ArrayIndex(NumericLiteralValue.optimalInteger(-2, decl.position), decl.position) checkValueTypeAndRangeArray(decl.datatype, decl.struct, arraySpec, decl.value as ArrayLiteralValue)
)
checkValueTypeAndRange(decl.datatype, decl.struct, arraySpec, decl.value as ReferenceLiteralValue, program.heap)
} }
is NumericLiteralValue -> { is NumericLiteralValue -> {
checkValueTypeAndRange(decl.datatype, decl.value as NumericLiteralValue) checkValueTypeAndRange(decl.datatype, decl.value as NumericLiteralValue)
@ -682,35 +685,29 @@ internal class AstChecker(private val program: Program,
checkResult.add(NameError("included file not found: $filename", directive.position)) checkResult.add(NameError("included file not found: $filename", directive.position))
} }
override fun visit(refLiteral: ReferenceLiteralValue) { override fun visit(array: ArrayLiteralValue) {
if(!compilerOptions.floats && refLiteral.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) { if(!compilerOptions.floats && array.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
checkResult.add(SyntaxError("floating point used, but that is not enabled via options", refLiteral.position)) checkResult.add(SyntaxError("floating point used, but that is not enabled via options", array.position))
} }
val arrayspec = val arrayspec = ArrayIndex.forArray(array)
if(refLiteral.isArray) checkValueTypeAndRangeArray(array.type, null, arrayspec, array)
ArrayIndex.forArray(refLiteral, program.heap)
else
ArrayIndex(NumericLiteralValue.optimalInteger(-3, refLiteral.position), refLiteral.position)
checkValueTypeAndRange(refLiteral.type, null, arrayspec, refLiteral, program.heap)
super.visit(refLiteral) super.visit(array)
when(refLiteral.type) { if(array.heapId==null)
in StringDatatypes -> { throw FatalAstException("array should have been moved to heap at ${array.position}")
if(refLiteral.heapId==null) }
throw FatalAstException("string should have been moved to heap at ${refLiteral.position}")
} override fun visit(string: StringLiteralValue) {
in ArrayDatatypes -> { checkValueTypeAndRangeString(string.type, string)
if(refLiteral.heapId==null) super.visit(string)
throw FatalAstException("array should have been moved to heap at ${refLiteral.position}") if(string.heapId==null)
} throw FatalAstException("string should have been moved to heap at ${string.position}")
else -> {}
}
} }
override fun visit(expr: PrefixExpression) { override fun visit(expr: PrefixExpression) {
if(expr.operator=="-") { if(expr.operator=="-") {
val dt = expr.inferType(program) val dt = expr.inferType(program).typeOrElse(DataType.STRUCT)
if (dt != DataType.BYTE && dt != DataType.WORD && dt != DataType.FLOAT) { if (dt != DataType.BYTE && dt != DataType.WORD && dt != DataType.FLOAT) {
checkResult.add(ExpressionError("can only take negative of a signed number type", expr.position)) checkResult.add(ExpressionError("can only take negative of a signed number type", expr.position))
} }
@ -719,8 +716,13 @@ internal class AstChecker(private val program: Program,
} }
override fun visit(expr: BinaryExpression) { override fun visit(expr: BinaryExpression) {
val leftDt = expr.left.inferType(program) val leftIDt = expr.left.inferType(program)
val rightDt = expr.right.inferType(program) val rightIDt = expr.right.inferType(program)
if(!leftIDt.isKnown || !rightIDt.isKnown) {
throw FatalAstException("can't determine datatype of both expression operands $expr")
}
val leftDt = leftIDt.typeOrElse(DataType.STRUCT)
val rightDt = rightIDt.typeOrElse(DataType.STRUCT)
when(expr.operator){ when(expr.operator){
"/", "%" -> { "/", "%" -> {
@ -851,30 +853,30 @@ internal class AstChecker(private val program: Program,
val paramTypesForAddressOf = PassByReferenceDatatypes + DataType.UWORD val paramTypesForAddressOf = PassByReferenceDatatypes + DataType.UWORD
for (arg in args.withIndex().zip(func.parameters)) { for (arg in args.withIndex().zip(func.parameters)) {
val argDt=arg.first.value.inferType(program) val argDt=arg.first.value.inferType(program)
if (argDt != null if (argDt.isKnown
&& !(argDt isAssignableTo arg.second.possibleDatatypes) && !(argDt.typeOrElse(DataType.STRUCT) isAssignableTo arg.second.possibleDatatypes)
&& (argDt != DataType.UWORD || arg.second.possibleDatatypes.intersect(paramTypesForAddressOf).isEmpty())) { && (argDt.typeOrElse(DataType.STRUCT) != DataType.UWORD || arg.second.possibleDatatypes.intersect(paramTypesForAddressOf).isEmpty())) {
checkResult.add(ExpressionError("builtin function '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.possibleDatatypes}", position)) checkResult.add(ExpressionError("builtin function '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.possibleDatatypes}", position))
} }
} }
if(target.name=="swap") { if(target.name=="swap") {
// swap() is a bit weird because this one is translated into a operations directly, instead of being a function call // swap() is a bit weird because this one is translated into a operations directly, instead of being a function call
val dt1 = args[0].inferType(program)!! val dt1 = args[0].inferType(program)
val dt2 = args[1].inferType(program)!! val dt2 = args[1].inferType(program)
if (dt1 != dt2) if (dt1 != dt2)
checkResult.add(ExpressionError("swap requires 2 args of identical type", position)) checkResult.add(ExpressionError("swap requires 2 args of identical type", position))
else if (args[0].constValue(program) != null || args[1].constValue(program) != null) else if (args[0].constValue(program) != null || args[1].constValue(program) != null)
checkResult.add(ExpressionError("swap requires 2 variables, not constant value(s)", position)) checkResult.add(ExpressionError("swap requires 2 variables, not constant value(s)", position))
else if(args[0] isSameAs args[1]) else if(args[0] isSameAs args[1])
checkResult.add(ExpressionError("swap should have 2 different args", position)) checkResult.add(ExpressionError("swap should have 2 different args", position))
else if(dt1 !in NumericDatatypes) else if(dt1.typeOrElse(DataType.STRUCT) !in NumericDatatypes)
checkResult.add(ExpressionError("swap requires args of numerical type", position)) checkResult.add(ExpressionError("swap requires args of numerical type", position))
} }
else if(target.name=="all" || target.name=="any") { else if(target.name=="all" || target.name=="any") {
if((args[0] as? AddressOf)?.identifier?.targetVarDecl(program.namespace)?.datatype in StringDatatypes) { if((args[0] as? AddressOf)?.identifier?.targetVarDecl(program.namespace)?.datatype in StringDatatypes) {
checkResult.add(ExpressionError("any/all on a string is useless (is always true unless the string is empty)", position)) checkResult.add(ExpressionError("any/all on a string is useless (is always true unless the string is empty)", position))
} }
if(args[0].inferType(program) in StringDatatypes) { if(args[0].inferType(program).typeOrElse(DataType.STR) in StringDatatypes) {
checkResult.add(ExpressionError("any/all on a string is useless (is always true unless the string is empty)", position)) checkResult.add(ExpressionError("any/all on a string is useless (is always true unless the string is empty)", position))
} }
} }
@ -884,8 +886,12 @@ internal class AstChecker(private val program: Program,
checkResult.add(SyntaxError("invalid number of arguments", position)) checkResult.add(SyntaxError("invalid number of arguments", position))
else { else {
for (arg in args.withIndex().zip(target.parameters)) { for (arg in args.withIndex().zip(target.parameters)) {
val argDt = arg.first.value.inferType(program) val argIDt = arg.first.value.inferType(program)
if(argDt!=null && !(argDt isAssignableTo arg.second.type)) { if(!argIDt.isKnown) {
throw FatalAstException("can't determine arg dt ${arg.first.value}")
}
val argDt=argIDt.typeOrElse(DataType.STRUCT)
if(!(argDt isAssignableTo arg.second.type)) {
// for asm subroutines having STR param it's okay to provide a UWORD (address value) // for asm subroutines having STR param it's okay to provide a UWORD (address value)
if(!(target.isAsmSubroutine && arg.second.type in StringDatatypes && argDt == DataType.UWORD)) if(!(target.isAsmSubroutine && arg.second.type in StringDatatypes && argDt == DataType.UWORD))
checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.type}", position)) checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.type}", position))
@ -956,9 +962,9 @@ internal class AstChecker(private val program: Program,
if(index!=null && (index<0 || index>=arraysize)) if(index!=null && (index<0 || index>=arraysize))
checkResult.add(ExpressionError("array index out of bounds", arrayIndexedExpression.arrayspec.position)) checkResult.add(ExpressionError("array index out of bounds", arrayIndexedExpression.arrayspec.position))
} else if(target.datatype in StringDatatypes) { } else if(target.datatype in StringDatatypes) {
if(target.value is ReferenceLiteralValue) { if(target.value is StringLiteralValue) {
// check string lengths for non-memory mapped strings // check string lengths for non-memory mapped strings
val heapId = (target.value as ReferenceLiteralValue).heapId!! val heapId = (target.value as StringLiteralValue).heapId!!
val stringLen = program.heap.get(heapId).str!!.length val stringLen = program.heap.get(heapId).str!!.length
val index = (arrayIndexedExpression.arrayspec.index as? NumericLiteralValue)?.number?.toInt() val index = (arrayIndexedExpression.arrayspec.index as? NumericLiteralValue)?.number?.toInt()
if (index != null && (index < 0 || index >= stringLen)) if (index != null && (index < 0 || index >= stringLen))
@ -969,7 +975,7 @@ internal class AstChecker(private val program: Program,
checkResult.add(SyntaxError("indexing requires a variable to act upon", arrayIndexedExpression.position)) checkResult.add(SyntaxError("indexing requires a variable to act upon", arrayIndexedExpression.position))
// check index value 0..255 // check index value 0..255
val dtx = arrayIndexedExpression.arrayspec.index.inferType(program) val dtx = arrayIndexedExpression.arrayspec.index.inferType(program).typeOrElse(DataType.STRUCT)
if(dtx!= DataType.UBYTE && dtx!= DataType.BYTE) if(dtx!= DataType.UBYTE && dtx!= DataType.BYTE)
checkResult.add(SyntaxError("array indexing is limited to byte size 0..255", arrayIndexedExpression.position)) checkResult.add(SyntaxError("array indexing is limited to byte size 0..255", arrayIndexedExpression.position))
@ -977,7 +983,7 @@ internal class AstChecker(private val program: Program,
} }
override fun visit(whenStatement: WhenStatement) { override fun visit(whenStatement: WhenStatement) {
val conditionType = whenStatement.condition.inferType(program) val conditionType = whenStatement.condition.inferType(program).typeOrElse(DataType.STRUCT)
if(conditionType !in IntegerDatatypes) if(conditionType !in IntegerDatatypes)
checkResult.add(SyntaxError("when condition must be an integer value", whenStatement.position)) checkResult.add(SyntaxError("when condition must be an integer value", whenStatement.position))
val choiceValues = whenStatement.choiceValues(program) val choiceValues = whenStatement.choiceValues(program)
@ -996,12 +1002,14 @@ internal class AstChecker(private val program: Program,
val whenStmt = whenChoice.parent as WhenStatement val whenStmt = whenChoice.parent as WhenStatement
if(whenChoice.values!=null) { if(whenChoice.values!=null) {
val conditionType = whenStmt.condition.inferType(program) val conditionType = whenStmt.condition.inferType(program)
if(!conditionType.isKnown)
throw FatalAstException("can't determine when choice datatype $whenChoice")
val constvalues = whenChoice.values!!.map { it.constValue(program) } val constvalues = whenChoice.values!!.map { it.constValue(program) }
for(constvalue in constvalues) { for(constvalue in constvalues) {
when { when {
constvalue == null -> checkResult.add(SyntaxError("choice value must be a constant", whenChoice.position)) constvalue == null -> checkResult.add(SyntaxError("choice value must be a constant", whenChoice.position))
constvalue.type !in IntegerDatatypes -> checkResult.add(SyntaxError("choice value must be a byte or word", whenChoice.position)) constvalue.type !in IntegerDatatypes -> checkResult.add(SyntaxError("choice value must be a byte or word", whenChoice.position))
constvalue.type != conditionType -> checkResult.add(SyntaxError("choice value datatype differs from condition value", whenChoice.position)) constvalue.type != conditionType.typeOrElse(DataType.STRUCT) -> checkResult.add(SyntaxError("choice value datatype differs from condition value", whenChoice.position))
} }
} }
} else { } else {
@ -1037,26 +1045,37 @@ internal class AstChecker(private val program: Program,
return null return null
} }
private fun checkValueTypeAndRange(targetDt: DataType, struct: StructDecl?, private fun checkValueTypeAndRangeString(targetDt: DataType, value: StringLiteralValue) : Boolean {
arrayspec: ArrayIndex, value: ReferenceLiteralValue, heap: HeapValues) : Boolean { fun err(msg: String): Boolean {
checkResult.add(ExpressionError(msg, value.position))
return false
}
return when (targetDt) {
in StringDatatypes -> {
return if (value.value.length > 255)
err("string length must be 0-255")
else
true
}
else -> false
}
}
private fun checkValueTypeAndRangeArray(targetDt: DataType, struct: StructDecl?,
arrayspec: ArrayIndex, value: ArrayLiteralValue) : Boolean {
fun err(msg: String) : Boolean { fun err(msg: String) : Boolean {
checkResult.add(ExpressionError(msg, value.position)) checkResult.add(ExpressionError(msg, value.position))
return false return false
} }
when (targetDt) { when (targetDt) {
in StringDatatypes -> { in StringDatatypes -> return err("string value expected")
if(!value.isString)
return err("string value expected")
if (value.str!!.length > 255)
return err("string length must be 0-255")
}
DataType.ARRAY_UB, DataType.ARRAY_B -> { DataType.ARRAY_UB, DataType.ARRAY_B -> {
// value may be either a single byte, or a byte arraysize (of all constant values), or a range // value may be either a single byte, or a byte arraysize (of all constant values), or a range
if(value.type==targetDt) { if(value.type==targetDt) {
if(!checkArrayValues(value, targetDt)) if(!checkArrayValues(value, targetDt))
return false return false
val arraySpecSize = arrayspec.size() val arraySpecSize = arrayspec.size()
val arraySize = value.array?.size ?: heap.get(value.heapId!!).arraysize val arraySize = value.value.size
if(arraySpecSize!=null && arraySpecSize>0) { if(arraySpecSize!=null && arraySpecSize>0) {
if(arraySpecSize<1 || arraySpecSize>256) if(arraySpecSize<1 || arraySpecSize>256)
return err("byte array length must be 1-256") return err("byte array length must be 1-256")
@ -1078,7 +1097,7 @@ internal class AstChecker(private val program: Program,
if(!checkArrayValues(value, targetDt)) if(!checkArrayValues(value, targetDt))
return false return false
val arraySpecSize = arrayspec.size() val arraySpecSize = arrayspec.size()
val arraySize = value.array?.size ?: heap.get(value.heapId!!).arraysize val arraySize = value.value.size
if(arraySpecSize!=null && arraySpecSize>0) { if(arraySpecSize!=null && arraySpecSize>0) {
if(arraySpecSize<1 || arraySpecSize>128) if(arraySpecSize<1 || arraySpecSize>128)
return err("word array length must be 1-128") return err("word array length must be 1-128")
@ -1099,7 +1118,7 @@ internal class AstChecker(private val program: Program,
if(value.type==targetDt) { if(value.type==targetDt) {
if(!checkArrayValues(value, targetDt)) if(!checkArrayValues(value, targetDt))
return false return false
val arraySize = value.array?.size ?: heap.get(value.heapId!!).doubleArray!!.size val arraySize = value.value.size
val arraySpecSize = arrayspec.size() val arraySpecSize = arrayspec.size()
if(arraySpecSize!=null && arraySpecSize>0) { if(arraySpecSize!=null && arraySpecSize>0) {
if(arraySpecSize < 1 || arraySpecSize>51) if(arraySpecSize < 1 || arraySpecSize>51)
@ -1114,10 +1133,7 @@ internal class AstChecker(private val program: Program,
return err("invalid float array size, must be 1-51") return err("invalid float array size, must be 1-51")
// check if the floating point values are all within range // check if the floating point values are all within range
val doubles = if(value.array!=null) val doubles = value.value.map {it.constValue(program)?.number!!.toDouble()}.toDoubleArray()
value.array.map {it.constValue(program)?.number!!.toDouble()}.toDoubleArray()
else
heap.get(value.heapId!!).doubleArray!!
if(doubles.any { it < FLOAT_MAX_NEGATIVE || it> FLOAT_MAX_POSITIVE }) if(doubles.any { it < FLOAT_MAX_NEGATIVE || it> FLOAT_MAX_POSITIVE })
return err("floating point value overflow") return err("floating point value overflow")
return true return true
@ -1126,12 +1142,12 @@ internal class AstChecker(private val program: Program,
} }
DataType.STRUCT -> { DataType.STRUCT -> {
if(value.type in ArrayDatatypes) { if(value.type in ArrayDatatypes) {
if(value.array!!.size != struct!!.numberOfElements) if(value.value.size != struct!!.numberOfElements)
return err("number of values is not the same as the number of members in the struct") return err("number of values is not the same as the number of members in the struct")
for(elt in value.array.zip(struct.statements)) { for(elt in value.value.zip(struct.statements)) {
val vardecl = elt.second as VarDecl val vardecl = elt.second as VarDecl
val valuetype = elt.first.inferType(program)!! val valuetype = elt.first.inferType(program)
if (!(valuetype isAssignableTo vardecl.datatype)) { if (!valuetype.isKnown || !(valuetype.typeOrElse(DataType.STRUCT) isAssignableTo vardecl.datatype)) {
checkResult.add(ExpressionError("invalid struct member init value type $valuetype, expected ${vardecl.datatype}", elt.first.position)) checkResult.add(ExpressionError("invalid struct member init value type $valuetype, expected ${vardecl.datatype}", elt.first.position))
return false return false
} }
@ -1142,7 +1158,6 @@ internal class AstChecker(private val program: Program,
} }
else -> return false else -> return false
} }
return true
} }
private fun checkValueTypeAndRange(targetDt: DataType, value: NumericLiteralValue) : Boolean { private fun checkValueTypeAndRange(targetDt: DataType, value: NumericLiteralValue) : Boolean {
@ -1189,10 +1204,10 @@ internal class AstChecker(private val program: Program,
return true return true
} }
private fun checkArrayValues(value: ReferenceLiteralValue, type: DataType): Boolean { private fun checkArrayValues(value: ArrayLiteralValue, type: DataType): Boolean {
if(value.isArray && value.heapId==null) { if(value.heapId==null) {
// hmm weird, array literal that hasn't been moved to the heap yet? // hmm weird, array literal that hasn't been moved to the heap yet?
val array = value.array!!.map { it.constValue(program)!! } val array = value.value.map { it.constValue(program)!! }
val correct: Boolean val correct: Boolean
when(type) { when(type) {
DataType.ARRAY_UB -> { DataType.ARRAY_UB -> {

View File

@ -144,8 +144,7 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
// NOTE: // NOTE:
// - numeric types BYTE and WORD and FLOAT are passed by value; // - numeric types BYTE and WORD and FLOAT are passed by value;
// - strings, arrays, matrices are passed by reference (their 16-bit address is passed as an uword parameter) // - strings, arrays, matrices are passed by reference (their 16-bit address is passed as an uword parameter)
// - do NOT do this is the statement can be transformed into an asm subroutine later! if(subroutine.asmAddress==null) {
if(subroutine.asmAddress==null && !subroutine.canBeAsmSubroutine) {
if(subroutine.asmParameterRegisters.isEmpty()) { if(subroutine.asmParameterRegisters.isEmpty()) {
subroutine.parameters subroutine.parameters
.filter { it.name !in namesInSub } .filter { it.name !in namesInSub }
@ -157,6 +156,10 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
} }
} }
} }
if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) {
checkResult.add(SyntaxError("asmsub can only contain inline assembly (%asm)", subroutine.position))
}
} }
return super.visit(subroutine) return super.visit(subroutine)
} }
@ -247,75 +250,84 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
return super.visit(returnStmt) return super.visit(returnStmt)
} }
override fun visit(refLiteral: ReferenceLiteralValue): Expression { override fun visit(arrayLiteral: ArrayLiteralValue): Expression {
val litval = super.visit(refLiteral) val array = super.visit(arrayLiteral)
if(litval is ReferenceLiteralValue) { if(array is ArrayLiteralValue) {
val vardecl = litval.parent as? VarDecl val vardecl = array.parent as? VarDecl
if (litval.isString) { return if (vardecl!=null) {
// intern the string; move it into the heap fixupArrayDatatype(array, vardecl, program.heap)
if (litval.str!!.length !in 1..255) } else {
checkResult.add(ExpressionError("string literal length must be between 1 and 255", litval.position)) // fix the datatype of the array (also on the heap) to the 'biggest' datatype in the array
else { // (we don't know the desired datatype here exactly so we guess)
litval.addToHeap(program.heap) val datatype = determineArrayDt(array.value)
} val litval2 = array.cast(datatype)!!
return if(vardecl!=null) litval2.parent = array.parent
litval // finally, replace the literal array by a identifier reference.
else makeIdentifierFromRefLv(litval2)
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 array
return litval
} }
private fun determineArrayDt(array: Array<Expression>): DataType? { override fun visit(stringLiteral: StringLiteralValue): Expression {
val datatypesInArray = array.mapNotNull { it.inferType(program) } val string = super.visit(stringLiteral)
if(datatypesInArray.isEmpty()) if(string is StringLiteralValue) {
return null val vardecl = string.parent as? VarDecl
if(DataType.FLOAT in datatypesInArray) // intern the string; move it into the heap
return DataType.ARRAY_F if (string.value.length !in 1..255)
if(DataType.WORD in datatypesInArray) checkResult.add(ExpressionError("string literal length must be between 1 and 255", string.position))
return DataType.ARRAY_W else {
if(DataType.UWORD in datatypesInArray) string.addToHeap(program.heap)
return DataType.ARRAY_UW }
if(DataType.BYTE in datatypesInArray) return if (vardecl != null)
return DataType.ARRAY_B string
if(DataType.UBYTE in datatypesInArray) else
return DataType.ARRAY_UB makeIdentifierFromRefLv(string) // replace the literal string by a identifier reference.
return null }
return string
} }
private fun makeIdentifierFromRefLv(refLiteral: ReferenceLiteralValue): IdentifierReference { private fun determineArrayDt(array: Array<Expression>): DataType {
val datatypesInArray = array.map { it.inferType(program) }
if(datatypesInArray.isEmpty() || datatypesInArray.any { !it.isKnown })
throw IllegalArgumentException("can't determine type of empty array")
val dts = datatypesInArray.map { it.typeOrElse(DataType.STRUCT) }
return when {
DataType.FLOAT in dts -> DataType.ARRAY_F
DataType.WORD in dts -> DataType.ARRAY_W
DataType.UWORD in dts -> DataType.ARRAY_UW
DataType.BYTE in dts -> DataType.ARRAY_B
DataType.UBYTE in dts -> DataType.ARRAY_UB
else -> throw IllegalArgumentException("can't determine type of array")
}
}
private fun makeIdentifierFromRefLv(array: ArrayLiteralValue): IdentifierReference {
// a referencetype literal value that's not declared as a variable // 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 // 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. // note: if the var references the same literal value, it is not yet de-duplicated here.
refLiteral.addToHeap(program.heap) array.addToHeap(program.heap)
val scope = refLiteral.definingScope() val scope = array.definingScope()
var variable = VarDecl.createAuto(refLiteral, program.heap) val variable = VarDecl.createAuto(array)
val existing = scope.lookup(listOf(variable.name), refLiteral) return replaceWithIdentifier(variable, scope, array.parent)
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 { private fun makeIdentifierFromRefLv(string: StringLiteralValue): IdentifierReference {
// register the scoped name of the referenced identifier // a referencetype literal value that's not declared as a variable
val variable= addressOf.identifier.targetVarDecl(program.namespace) ?: return addressOf // we need to introduce an auto-generated variable for this to be able to refer to the value
return super.visit(addressOf) // note: if the var references the same literal value, it is not yet de-duplicated here.
string.addToHeap(program.heap)
val scope = string.definingScope()
val variable = VarDecl.createAuto(string)
return replaceWithIdentifier(variable, scope, string.parent)
}
private fun replaceWithIdentifier(variable: VarDecl, scope: INameScope, parent: Node): IdentifierReference {
val variable1 = addVarDecl(scope, variable)
// replace the reference literal by a identifier reference
val identifier = IdentifierReference(listOf(variable1.name), variable1.position)
identifier.parent = parent
return identifier
} }
override fun visit(structDecl: StructDecl): Statement { override fun visit(structDecl: StructDecl): Statement {
@ -330,33 +342,30 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
override fun visit(expr: BinaryExpression): Expression { override fun visit(expr: BinaryExpression): Expression {
return when { return when {
expr.left is ReferenceLiteralValue -> expr.left is StringLiteralValue ->
processBinaryExprWithReferenceVal(expr.left as ReferenceLiteralValue, expr.right, expr) processBinaryExprWithString(expr.left as StringLiteralValue, expr.right, expr)
expr.right is ReferenceLiteralValue -> expr.right is StringLiteralValue ->
processBinaryExprWithReferenceVal(expr.right as ReferenceLiteralValue, expr.left, expr) processBinaryExprWithString(expr.right as StringLiteralValue, expr.left, expr)
else -> super.visit(expr) else -> super.visit(expr)
} }
} }
private fun processBinaryExprWithReferenceVal(refLv: ReferenceLiteralValue, operand: Expression, expr: BinaryExpression): Expression { private fun processBinaryExprWithString(string: StringLiteralValue, operand: Expression, expr: BinaryExpression): Expression {
// expressions on strings or arrays val constvalue = operand.constValue(program)
if(refLv.isString) { if(constvalue!=null) {
val constvalue = operand.constValue(program) if (expr.operator == "*") {
if(constvalue!=null) { // repeat a string a number of times
if (expr.operator == "*") { val idt = string.inferType(program)
// repeat a string a number of times return StringLiteralValue(idt.typeOrElse(DataType.STR),
return ReferenceLiteralValue(refLv.inferType(program), string.value.repeat(constvalue.number.toInt()), null, expr.position)
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)
}
} }
} }
if(expr.operator == "+" && operand is StringLiteralValue) {
// concatenate two strings
val idt = string.inferType(program)
return StringLiteralValue(idt.typeOrElse(DataType.STR),
"${string.value}${operand.value}", null, expr.position)
}
return expr return expr
} }
@ -375,7 +384,7 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
} }
internal fun fixupArrayDatatype(array: ReferenceLiteralValue, vardecl: VarDecl, heap: HeapValues): ReferenceLiteralValue { internal fun fixupArrayDatatype(array: ArrayLiteralValue, vardecl: VarDecl, heap: HeapValues): ArrayLiteralValue {
if(array.heapId!=null) { if(array.heapId!=null) {
val arrayDt = array.type val arrayDt = array.type
if(arrayDt!=vardecl.datatype) { if(arrayDt!=vardecl.datatype) {
@ -386,11 +395,11 @@ internal fun fixupArrayDatatype(array: ReferenceLiteralValue, vardecl: VarDecl,
} catch(x: ExpressionError) { } catch(x: ExpressionError) {
// couldn't cast permanently. // couldn't cast permanently.
// instead, simply adjust the array type and trust the AstChecker to report the exact error // instead, simply adjust the array type and trust the AstChecker to report the exact error
ReferenceLiteralValue(vardecl.datatype, null, array.array, array.heapId, array.position) ArrayLiteralValue(vardecl.datatype, array.value, array.heapId, array.position)
} }
vardecl.value = litval2 vardecl.value = litval2
litval2.linkParents(vardecl) litval2.linkParents(vardecl)
litval2.addToHeap(heap) // TODO is the previous array discarded from the resulting asm code? litval2.addToHeap(heap)
return litval2 return litval2
} }
} else { } else {

View File

@ -110,14 +110,16 @@ interface IAstModifyingVisitor {
return literalValue return literalValue
} }
fun visit(refLiteral: ReferenceLiteralValue): Expression { fun visit(stringLiteral: StringLiteralValue): Expression {
if(refLiteral.array!=null) { return stringLiteral
for(av in refLiteral.array.withIndex()) { }
val newvalue = av.value.accept(this)
refLiteral.array[av.index] = newvalue fun visit(arrayLiteral: ArrayLiteralValue): Expression {
} for(av in arrayLiteral.value.withIndex()) {
val newvalue = av.value.accept(this)
arrayLiteral.value[av.index] = newvalue
} }
return refLiteral return arrayLiteral
} }
fun visit(assignment: Assignment): Statement { fun visit(assignment: Assignment): Statement {

View File

@ -79,8 +79,11 @@ interface IAstVisitor {
fun visit(numLiteral: NumericLiteralValue) { fun visit(numLiteral: NumericLiteralValue) {
} }
fun visit(refLiteral: ReferenceLiteralValue) { fun visit(string: StringLiteralValue) {
refLiteral.array?.let { it.forEach { v->v.accept(this) }} }
fun visit(array: ArrayLiteralValue) {
array.value.forEach { v->v.accept(this) }
} }
fun visit(assignment: Assignment) { fun visit(assignment: Assignment) {

View File

@ -1,16 +1,16 @@
package prog8.ast.processing package prog8.ast.processing
import prog8.ast.* import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.base.DataType import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException import prog8.ast.base.FatalAstException
import prog8.ast.base.initvarsSubName import prog8.ast.base.initvarsSubName
import prog8.ast.base.printWarning
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.mangledStructMemberName
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.functions.BuiltinFunctions
fun flattenStructAssignmentFromIdentifier(structAssignment: Assignment, program: Program): List<Assignment> { private fun flattenStructAssignmentFromIdentifier(structAssignment: Assignment, program: Program): List<Assignment> {
val identifier = structAssignment.target.identifier!! val identifier = structAssignment.target.identifier!!
val identifierName = identifier.nameInSource.single() val identifierName = identifier.nameInSource.single()
val targetVar = identifier.targetVarDecl(program.namespace)!! val targetVar = identifier.targetVarDecl(program.namespace)!!
@ -61,9 +61,6 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
// - the 'start' subroutine in the 'main' block will be moved to the top immediately following the directives. // - the 'start' subroutine in the 'main' block will be moved to the top immediately following the directives.
// - all other subroutines will be moved to the end of their block. // - all other subroutines will be moved to the end of their block.
// - sorts the choices in when statement. // - sorts the choices in when statement.
//
// Also, makes sure any value assignments get the proper type casts if needed to cast them into the target variable's type.
// (this includes function call arguments)
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option") private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option")
@ -186,64 +183,34 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
return subroutine return subroutine
} }
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) = BinaryExpression.commonDatatype(leftDt, rightDt, expr2.left, expr2.right)
if(toFix!=null) {
when {
toFix===expr2.left -> {
expr2.left = TypecastExpression(expr2.left, commonDt, true, expr2.left.position)
expr2.left.linkParents(expr2)
}
toFix===expr2.right -> {
expr2.right = TypecastExpression(expr2.right, commonDt, true, expr2.right.position)
expr2.right.linkParents(expr2)
}
else -> throw FatalAstException("confused binary expression side")
}
}
}
return expr2
}
override fun visit(assignment: Assignment): Statement { override fun visit(assignment: Assignment): Statement {
val assg = super.visit(assignment) val assg = super.visit(assignment)
if(assg !is Assignment) if(assg !is Assignment)
return assg return assg
// see if a typecast is needed to convert the value's type into the proper target type // see if a typecast is needed to convert the value's type into the proper target type
val valuetype = assg.value.inferType(program) val valueItype = assg.value.inferType(program)
val targettype = assg.target.inferType(program, assg) val targetItype = assg.target.inferType(program, assg)
if(targettype!=null && valuetype!=null) {
if(valuetype!=targettype) { if(targetItype.isKnown && valueItype.isKnown) {
if (valuetype isAssignableTo targettype) { val targettype = targetItype.typeOrElse(DataType.STRUCT)
assg.value = TypecastExpression(assg.value, targettype, true, assg.value.position) val valuetype = valueItype.typeOrElse(DataType.STRUCT)
assg.value.linkParents(assg)
// struct assignments will be flattened (if it's not a struct literal)
if (valuetype == DataType.STRUCT && targettype == DataType.STRUCT) {
if (assg.value is StructLiteralValue)
return assg // do NOT flatten it at this point!! (the compiler will take care if it, later, if needed)
val assignments = flattenStructAssignmentFromIdentifier(assg, program) // 'structvar1 = structvar2'
return if (assignments.isEmpty()) {
// something went wrong (probably incompatible struct types)
// we'll get an error later from the AstChecker
assg
} else {
val scope = AnonymousScope(assignments.toMutableList(), assg.position)
scope.linkParents(assg.parent)
scope
} }
// if they're not assignable, we'll get a proper error later from the AstChecker
}
}
// 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
} }
} }
@ -267,134 +234,4 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
return assg return assg
} }
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
checkFunctionCallArguments(functionCallStatement, functionCallStatement.definingScope())
return super.visit(functionCallStatement)
}
override fun visit(functionCall: FunctionCall): Expression {
checkFunctionCallArguments(functionCall, functionCall.definingScope())
return super.visit(functionCall)
}
private fun checkFunctionCallArguments(call: IFunctionCall, scope: INameScope) {
// see if a typecast is needed to convert the arguments into the required parameter's type
when(val sub = call.target.targetStatement(scope)) {
is Subroutine -> {
for(arg in sub.parameters.zip(call.arglist.withIndex())) {
val argtype = arg.second.value.inferType(program)
if(argtype!=null) {
val requiredType = arg.first.type
if (requiredType != argtype) {
if (argtype isAssignableTo requiredType) {
val typecasted = TypecastExpression(arg.second.value, requiredType, true, arg.second.value.position)
typecasted.linkParents(arg.second.value.parent)
call.arglist[arg.second.index] = typecasted
}
// if they're not assignable, we'll get a proper error later from the AstChecker
}
}
}
}
is BuiltinFunctionStatementPlaceholder -> {
val func = BuiltinFunctions.getValue(sub.name)
if(func.pure) {
// non-pure functions don't get automatic typecasts because sometimes they act directly on their parameters
for (arg in func.parameters.zip(call.arglist.withIndex())) {
val argtype = arg.second.value.inferType(program)
if (argtype != null) {
if (arg.first.possibleDatatypes.any { argtype == it })
continue
for (possibleType in arg.first.possibleDatatypes) {
if (argtype isAssignableTo possibleType) {
val typecasted = TypecastExpression(arg.second.value, possibleType, true, arg.second.value.position)
typecasted.linkParents(arg.second.value.parent)
call.arglist[arg.second.index] = typecasted
break
}
}
}
}
}
}
null -> {}
else -> throw FatalAstException("call to something weird $sub ${call.target}")
}
}
override fun visit(typecast: TypecastExpression): Expression {
// warn about any implicit type casts to Float, because that may not be intended
if(typecast.implicit && typecast.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
printWarning("byte or word value implicitly converted to float. Suggestion: use explicit cast as float, a float number, or revert to integer arithmetic", typecast.position)
}
return super.visit(typecast)
}
override fun visit(memread: DirectMemoryRead): Expression {
// make sure the memory address is an uword
val dt = memread.addressExpression.inferType(program)
if(dt!=DataType.UWORD) {
val literaladdr = memread.addressExpression as? NumericLiteralValue
if(literaladdr!=null) {
memread.addressExpression = literaladdr.cast(DataType.UWORD)
} else {
memread.addressExpression = TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
memread.addressExpression.parent = memread
}
}
return super.visit(memread)
}
override fun visit(memwrite: DirectMemoryWrite) {
val dt = memwrite.addressExpression.inferType(program)
if(dt!=DataType.UWORD) {
val literaladdr = memwrite.addressExpression as? NumericLiteralValue
if(literaladdr!=null) {
memwrite.addressExpression = literaladdr.cast(DataType.UWORD)
} else {
memwrite.addressExpression = TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
memwrite.addressExpression.parent = memwrite
}
}
super.visit(memwrite)
}
override fun visit(structLv: StructLiteralValue): Expression {
val litval = super.visit(structLv)
if(litval !is StructLiteralValue)
return litval
val decl = litval.parent as? VarDecl
if(decl != null) {
val struct = decl.struct
if(struct != null) {
addTypecastsIfNeeded(litval, struct)
}
} else {
val assign = litval.parent as? Assignment
if (assign != null) {
val decl2 = assign.target.identifier?.targetVarDecl(program.namespace)
if(decl2 != null) {
val struct = decl2.struct
if(struct != null) {
addTypecastsIfNeeded(litval, struct)
}
}
}
}
return litval
}
private fun addTypecastsIfNeeded(structLv: StructLiteralValue, struct: StructDecl) {
structLv.values = struct.statements.zip(structLv.values).map {
val memberDt = (it.first as VarDecl).datatype
val valueDt = it.second.inferType(program)
if (valueDt != memberDt)
TypecastExpression(it.second, memberDt, true, it.second.position)
else
it.second
}
}
} }

View File

@ -0,0 +1,198 @@
package prog8.ast.processing
import prog8.ast.IFunctionCall
import prog8.ast.INameScope
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.base.printWarning
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.functions.BuiltinFunctions
internal class TypecastsAdder(private val program: Program): IAstModifyingVisitor {
// Make sure any value assignments get the proper type casts if needed to cast them into the target variable's type.
// (this includes function call arguments)
override fun visit(expr: BinaryExpression): Expression {
val expr2 = super.visit(expr)
if(expr2 !is BinaryExpression)
return expr2
val leftDt = expr2.left.inferType(program)
val rightDt = expr2.right.inferType(program)
if(leftDt.isKnown && rightDt.isKnown && leftDt!=rightDt) {
// determine common datatype and add typecast as required to make left and right equal types
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.typeOrElse(DataType.STRUCT), rightDt.typeOrElse(DataType.STRUCT), expr2.left, expr2.right)
if(toFix!=null) {
when {
toFix===expr2.left -> {
expr2.left = TypecastExpression(expr2.left, commonDt, true, expr2.left.position)
expr2.left.linkParents(expr2)
}
toFix===expr2.right -> {
expr2.right = TypecastExpression(expr2.right, commonDt, true, expr2.right.position)
expr2.right.linkParents(expr2)
}
else -> throw FatalAstException("confused binary expression side")
}
}
}
return expr2
}
override fun visit(assignment: Assignment): Statement {
val assg = super.visit(assignment)
if(assg !is Assignment)
return assg
// see if a typecast is needed to convert the value's type into the proper target type
val valueItype = assg.value.inferType(program)
val targetItype = assg.target.inferType(program, assg)
if(targetItype.isKnown && valueItype.isKnown) {
val targettype = targetItype.typeOrElse(DataType.STRUCT)
val valuetype = valueItype.typeOrElse(DataType.STRUCT)
if (valuetype != targettype) {
if (valuetype isAssignableTo targettype) {
assg.value = TypecastExpression(assg.value, targettype, true, assg.value.position)
assg.value.linkParents(assg)
}
// if they're not assignable, we'll get a proper error later from the AstChecker
}
}
return assg
}
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
checkFunctionCallArguments(functionCallStatement, functionCallStatement.definingScope())
return super.visit(functionCallStatement)
}
override fun visit(functionCall: FunctionCall): Expression {
checkFunctionCallArguments(functionCall, functionCall.definingScope())
return super.visit(functionCall)
}
private fun checkFunctionCallArguments(call: IFunctionCall, scope: INameScope) {
// see if a typecast is needed to convert the arguments into the required parameter's type
when(val sub = call.target.targetStatement(scope)) {
is Subroutine -> {
for(arg in sub.parameters.zip(call.arglist.withIndex())) {
val argItype = arg.second.value.inferType(program)
if(argItype.isKnown) {
val argtype = argItype.typeOrElse(DataType.STRUCT)
val requiredType = arg.first.type
if (requiredType != argtype) {
if (argtype isAssignableTo requiredType) {
val typecasted = TypecastExpression(arg.second.value, requiredType, true, arg.second.value.position)
typecasted.linkParents(arg.second.value.parent)
call.arglist[arg.second.index] = typecasted
}
// if they're not assignable, we'll get a proper error later from the AstChecker
}
}
}
}
is BuiltinFunctionStatementPlaceholder -> {
val func = BuiltinFunctions.getValue(sub.name)
if(func.pure) {
// non-pure functions don't get automatic typecasts because sometimes they act directly on their parameters
for (arg in func.parameters.zip(call.arglist.withIndex())) {
val argItype = arg.second.value.inferType(program)
if (argItype.isKnown) {
val argtype = argItype.typeOrElse(DataType.STRUCT)
if (arg.first.possibleDatatypes.any { argtype == it })
continue
for (possibleType in arg.first.possibleDatatypes) {
if (argtype isAssignableTo possibleType) {
val typecasted = TypecastExpression(arg.second.value, possibleType, true, arg.second.value.position)
typecasted.linkParents(arg.second.value.parent)
call.arglist[arg.second.index] = typecasted
break
}
}
}
}
}
}
null -> {}
else -> throw FatalAstException("call to something weird $sub ${call.target}")
}
}
override fun visit(typecast: TypecastExpression): Expression {
// warn about any implicit type casts to Float, because that may not be intended
if(typecast.implicit && typecast.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
printWarning("byte or word value implicitly converted to float. Suggestion: use explicit cast as float, a float number, or revert to integer arithmetic", typecast.position)
}
return super.visit(typecast)
}
override fun visit(memread: DirectMemoryRead): Expression {
// make sure the memory address is an uword
val dt = memread.addressExpression.inferType(program)
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
val literaladdr = memread.addressExpression as? NumericLiteralValue
if(literaladdr!=null) {
memread.addressExpression = literaladdr.cast(DataType.UWORD)
} else {
memread.addressExpression = TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
memread.addressExpression.parent = memread
}
}
return super.visit(memread)
}
override fun visit(memwrite: DirectMemoryWrite) {
val dt = memwrite.addressExpression.inferType(program)
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
val literaladdr = memwrite.addressExpression as? NumericLiteralValue
if(literaladdr!=null) {
memwrite.addressExpression = literaladdr.cast(DataType.UWORD)
} else {
memwrite.addressExpression = TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
memwrite.addressExpression.parent = memwrite
}
}
super.visit(memwrite)
}
override fun visit(structLv: StructLiteralValue): Expression {
val litval = super.visit(structLv)
if(litval !is StructLiteralValue)
return litval
val decl = litval.parent as? VarDecl
if(decl != null) {
val struct = decl.struct
if(struct != null) {
addTypecastsIfNeeded(litval, struct)
}
} else {
val assign = litval.parent as? Assignment
if (assign != null) {
val decl2 = assign.target.identifier?.targetVarDecl(program.namespace)
if(decl2 != null) {
val struct = decl2.struct
if(struct != null) {
addTypecastsIfNeeded(litval, struct)
}
}
}
}
return litval
}
private fun addTypecastsIfNeeded(structLv: StructLiteralValue, struct: StructDecl) {
structLv.values = struct.statements.zip(structLv.values).map {
val memberDt = (it.first as VarDecl).datatype
val valueDt = it.second.inferType(program)
if (valueDt.typeOrElse(memberDt) != memberDt)
TypecastExpression(it.second, memberDt, true, it.second.position)
else
it.second
}
}
}

View File

@ -42,7 +42,7 @@ internal class VarInitValueAndAddressOfCreator(private val program: Program): IA
if(decl.isArray && decl.value==null) { if(decl.isArray && decl.value==null) {
// array datatype without initialization value, add list of zeros // array datatype without initialization value, add list of zeros
val arraysize = decl.arraysize!!.size()!! val arraysize = decl.arraysize!!.size()!!
val array = ReferenceLiteralValue(decl.datatype, null, val array = ArrayLiteralValue(decl.datatype,
Array(arraysize) { NumericLiteralValue.optimalInteger(0, decl.position) }, Array(arraysize) { NumericLiteralValue.optimalInteger(0, decl.position) },
null, decl.position) null, decl.position)
array.addToHeap(program.heap) array.addToHeap(program.heap)
@ -107,7 +107,7 @@ internal class VarInitValueAndAddressOfCreator(private val program: Program): IA
if(argparam.second is AddressOf) if(argparam.second is AddressOf)
continue continue
val idref = argparam.second as? IdentifierReference val idref = argparam.second as? IdentifierReference
val strvalue = argparam.second as? ReferenceLiteralValue val strvalue = argparam.second as? StringLiteralValue
if(idref!=null) { if(idref!=null) {
val variable = idref.targetVarDecl(program.namespace) val variable = idref.targetVarDecl(program.namespace)
if(variable!=null && (variable.datatype in StringDatatypes || variable.datatype in ArrayDatatypes)) { if(variable!=null && (variable.datatype in StringDatatypes || variable.datatype in ArrayDatatypes)) {
@ -117,16 +117,14 @@ internal class VarInitValueAndAddressOfCreator(private val program: Program): IA
} }
} }
else if(strvalue!=null) { else if(strvalue!=null) {
if(strvalue.isString) { // add a vardecl so that the autovar can be resolved in later lookups
// add a vardecl so that the autovar can be resolved in later lookups val variable = VarDecl.createAuto(strvalue)
val variable = VarDecl.createAuto(strvalue, program.heap) addVarDecl(strvalue.definingScope(), variable)
addVarDecl(strvalue.definingScope(), variable) // replace the argument with &autovar
// replace the argument with &autovar val autoHeapvarRef = IdentifierReference(listOf(variable.name), strvalue.position)
val autoHeapvarRef = IdentifierReference(listOf(variable.name), strvalue.position) val pointerExpr = AddressOf(autoHeapvarRef, strvalue.position)
val pointerExpr = AddressOf(autoHeapvarRef, strvalue.position) pointerExpr.linkParents(arglist[argparam.first.index].parent)
pointerExpr.linkParents(arglist[argparam.first.index].parent) arglist[argparam.first.index] = pointerExpr
arglist[argparam.first.index] = pointerExpr
}
} }
} }
} }
@ -137,7 +135,7 @@ internal class VarInitValueAndAddressOfCreator(private val program: Program): IA
for(arg in args.withIndex().zip(signature.parameters)) { for(arg in args.withIndex().zip(signature.parameters)) {
val argvalue = arg.first.value val argvalue = arg.first.value
val argDt = argvalue.inferType(program) val argDt = argvalue.inferType(program)
if(argDt in PassByReferenceDatatypes && DataType.UWORD in arg.second.possibleDatatypes) { if(argDt.typeOrElse(DataType.UBYTE) in PassByReferenceDatatypes && DataType.UWORD in arg.second.possibleDatatypes) {
if(argvalue !is IdentifierReference) if(argvalue !is IdentifierReference)
throw CompilerException("pass-by-reference parameter isn't an identifier? $argvalue") throw CompilerException("pass-by-reference parameter isn't an identifier? $argvalue")
val addrOf = AddressOf(argvalue, argvalue.position) val addrOf = AddressOf(argvalue, argvalue.position)

View File

@ -5,7 +5,6 @@ import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.processing.IAstModifyingVisitor import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.processing.IAstVisitor import prog8.ast.processing.IAstVisitor
import prog8.compiler.HeapValues
sealed class Statement : Node { sealed class Statement : Node {
@ -111,8 +110,6 @@ data class Label(val name: String, override val position: Position) : Statement(
override fun toString(): String { override fun toString(): String {
return "Label(name=$name, pos=$position)" return "Label(name=$name, pos=$position)"
} }
val scopedname: String by lazy { makeScopedName(name) }
} }
open class Return(var value: Expression?, override val position: Position) : Statement() { open class Return(var value: Expression?, override val position: Position) : Statement() {
@ -197,20 +194,23 @@ class VarDecl(val type: VarDeclType,
companion object { companion object {
private var autoHeapValueSequenceNumber = 0 private var autoHeapValueSequenceNumber = 0
fun createAuto(refLv: ReferenceLiteralValue, heap: HeapValues): VarDecl { fun createAuto(string: StringLiteralValue): VarDecl {
if(refLv.heapId==null) if(string.heapId==null)
throw FatalAstException("can only create autovar for a ref lv that has a heapid $refLv") throw FatalAstException("can only create autovar for a string that has a heapid $string")
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
return VarDecl(VarDeclType.VAR, string.type, ZeropageWish.NOT_IN_ZEROPAGE, null, autoVarName, null, string,
isArray = false, autogeneratedDontRemove = true, position = string.position)
}
fun createAuto(array: ArrayLiteralValue): VarDecl {
if(array.heapId==null)
throw FatalAstException("can only create autovar for an array that has a heapid $array")
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}" val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
return if(refLv.isArray) { val declaredType = ArrayElementTypes.getValue(array.type)
val declaredType = ArrayElementTypes.getValue(refLv.type) val arraysize = ArrayIndex.forArray(array)
val arraysize = ArrayIndex.forArray(refLv, heap) return VarDecl(VarDeclType.VAR, declaredType, ZeropageWish.NOT_IN_ZEROPAGE, arraysize, autoVarName, null, array,
VarDecl(VarDeclType.VAR, declaredType, ZeropageWish.NOT_IN_ZEROPAGE, arraysize, autoVarName, null, refLv, isArray = true, autogeneratedDontRemove = true, position = array.position)
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)
}
} }
} }
@ -284,12 +284,6 @@ class VarDecl(val type: VarDeclType,
structHasBeenFlattened = true structHasBeenFlattened = true
return result 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: Expression, override val position: Position) : Node { class ArrayIndex(var index: Expression, override val position: Position) : Node {
@ -301,9 +295,8 @@ class ArrayIndex(var index: Expression, override val position: Position) : Node
} }
companion object { companion object {
fun forArray(v: ReferenceLiteralValue, heap: HeapValues): ArrayIndex { fun forArray(v: ArrayLiteralValue): ArrayIndex {
val arraySize = v.array?.size ?: heap.get(v.heapId!!).arraysize return ArrayIndex(NumericLiteralValue.optimalNumeric(v.value.size, v.position), v.position)
return ArrayIndex(NumericLiteralValue.optimalNumeric(arraySize, v.position), v.position)
} }
} }
@ -375,25 +368,23 @@ data class AssignTarget(val register: Register?,
} }
} }
fun inferType(program: Program, stmt: Statement): DataType? { fun inferType(program: Program, stmt: Statement): InferredTypes.InferredType {
if(register!=null) if(register!=null)
return DataType.UBYTE return InferredTypes.knownFor(DataType.UBYTE)
if(identifier!=null) { if(identifier!=null) {
val symbol = program.namespace.lookup(identifier!!.nameInSource, stmt) ?: return null val symbol = program.namespace.lookup(identifier!!.nameInSource, stmt) ?: return InferredTypes.unknown()
if (symbol is VarDecl) return symbol.datatype if (symbol is VarDecl) return InferredTypes.knownFor(symbol.datatype)
} }
if(arrayindexed!=null) { if(arrayindexed!=null) {
val dt = arrayindexed!!.inferType(program) return arrayindexed!!.inferType(program)
if(dt!=null)
return dt
} }
if(memoryAddress!=null) if(memoryAddress!=null)
return DataType.UBYTE return InferredTypes.knownFor(DataType.UBYTE)
return null return InferredTypes.unknown()
} }
infix fun isSameAs(value: Expression): Boolean { infix fun isSameAs(value: Expression): Boolean {
@ -608,40 +599,6 @@ class Subroutine(override val name: String,
.filter { it is InlineAssembly } .filter { it is InlineAssembly }
.map { (it as InlineAssembly).assembly } .map { (it as InlineAssembly).assembly }
.count { " rti" in it || "\trti" in it || " rts" in it || "\trts" in it || " jmp" in it || "\tjmp" in it } .count { " rti" in it || "\trti" in it || " rts" in it || "\trts" in it || " jmp" in it || "\tjmp" in it }
val canBeAsmSubroutine =false // TODO disabled for now, see below about problem with converting to asm subroutine
// !isAsmSubroutine
// && ((parameters.size == 1 && parameters[0].type in setOf(DataType.BYTE, DataType.UBYTE, DataType.WORD, DataType.UWORD))
// || (parameters.size == 2 && parameters.map { it.type }.all { it == DataType.BYTE || it == DataType.UBYTE }))
fun intoAsmSubroutine(): Subroutine {
// TODO turn subroutine into asm calling convention. Requires rethinking of how parameters are handled (conflicts with local vardefs now, see AstIdentifierChecker...)
return this // TODO
// println("TO ASM $this") // TODO
// val paramregs = if (parameters.size == 1 && parameters[0].type in setOf(DataType.BYTE, DataType.UBYTE))
// listOf(RegisterOrStatusflag(RegisterOrPair.Y, null, null))
// else if (parameters.size == 1 && parameters[0].type in setOf(DataType.WORD, DataType.UWORD))
// listOf(RegisterOrStatusflag(RegisterOrPair.AY, null, null))
// else if (parameters.size == 2 && parameters.map { it.type }.all { it == DataType.BYTE || it == DataType.UBYTE })
// listOf(RegisterOrStatusflag(RegisterOrPair.A, null, null), RegisterOrStatusflag(RegisterOrPair.Y, null, null))
// else throw FatalAstException("cannot convert subroutine to asm parameters")
//
// val asmsub=Subroutine(
// name,
// parameters,
// returntypes,
// paramregs,
// emptyList(),
// emptySet(),
// null,
// true,
// statements,
// position
// )
// asmsub.linkParents(parent)
// return asmsub
}
} }
open class SubroutineParameter(val name: String, open class SubroutineParameter(val name: String,

View File

@ -5,7 +5,7 @@ import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.statements.Directive import prog8.ast.statements.Directive
import prog8.compiler.target.c64.MachineDefinition import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.target.c64.codegen2.AsmGen2 import prog8.compiler.target.c64.codegen.AsmGen
import prog8.optimizer.constantFold import prog8.optimizer.constantFold
import prog8.optimizer.optimizeStatements import prog8.optimizer.optimizeStatements
import prog8.optimizer.simplifyExpressions import prog8.optimizer.simplifyExpressions
@ -69,7 +69,8 @@ fun compileProgram(filepath: Path,
//println(" time2: $time2") //println(" time2: $time2")
val time3 = measureTimeMillis { val time3 = measureTimeMillis {
programAst.removeNopsFlattenAnonScopes() programAst.removeNopsFlattenAnonScopes()
programAst.reorderStatements() // reorder statements and add type casts, to please the compiler later programAst.reorderStatements()
programAst.addTypecasts()
} }
//println(" time3: $time3") //println(" time3: $time3")
val time4 = measureTimeMillis { val time4 = measureTimeMillis {
@ -90,6 +91,7 @@ fun compileProgram(filepath: Path,
} }
} }
programAst.addTypecasts()
programAst.removeNopsFlattenAnonScopes() programAst.removeNopsFlattenAnonScopes()
programAst.checkValid(compilerOptions) // check if final tree is valid programAst.checkValid(compilerOptions) // check if final tree is valid
programAst.checkRecursion() // check if there are recursive subroutine calls programAst.checkRecursion() // check if there are recursive subroutine calls
@ -100,7 +102,7 @@ fun compileProgram(filepath: Path,
// asm generation directly from the Ast, no need for intermediate code // asm generation directly from the Ast, no need for intermediate code
val zeropage = MachineDefinition.C64Zeropage(compilerOptions) val zeropage = MachineDefinition.C64Zeropage(compilerOptions)
programAst.anonscopeVarsCleanup() programAst.anonscopeVarsCleanup()
val assembly = AsmGen2(programAst, compilerOptions, zeropage).compileToAssembly(optimize) val assembly = AsmGen(programAst, compilerOptions, zeropage).compileToAssembly(optimize)
assembly.assemble(compilerOptions) assembly.assemble(compilerOptions)
programName = assembly.name programName = assembly.name
} }

View File

@ -13,7 +13,7 @@ abstract class Zeropage(protected val options: CompilationOptions) {
val allowedDatatypes = NumericDatatypes val allowedDatatypes = NumericDatatypes
fun available() = free.size fun available() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size
fun allocate(scopedname: String, datatype: DataType, position: Position?): Int { fun allocate(scopedname: String, datatype: DataType, position: Position?): Int {
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"isSameAs scopedname can't be allocated twice"} assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"isSameAs scopedname can't be allocated twice"}

View File

@ -1,4 +1,4 @@
package prog8.compiler.target.c64.codegen2 package prog8.compiler.target.c64.codegen
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.AstException import prog8.ast.base.AstException
@ -21,7 +21,7 @@ class AnonymousScopeVarsCleanup(val program: Program): IAstModifyingVisitor {
super.visit(program) super.visit(program)
for((scope, decls) in varsToMove) { for((scope, decls) in varsToMove) {
val sub = scope.definingSubroutine()!! val sub = scope.definingSubroutine()!!
val existingVariables = sub.statements.filterIsInstance<VarDecl>().associate { it.name to it } val existingVariables = sub.statements.filterIsInstance<VarDecl>().associateBy { it.name }
var conflicts = false var conflicts = false
decls.forEach { decls.forEach {
val existing = existingVariables[it.name] val existing = existingVariables[it.name]

View File

@ -0,0 +1,879 @@
package prog8.compiler.target.c64.codegen
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.antlr.escape
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.*
import prog8.compiler.target.c64.AssemblyProgram
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.Petscii
import prog8.functions.BuiltinFunctions
import prog8.functions.FunctionSignature
import java.io.File
import java.math.RoundingMode
import java.util.*
import kotlin.math.absoluteValue
internal class AssemblyError(msg: String) : RuntimeException(msg)
internal class AsmGen(val program: Program,
val options: CompilationOptions,
val zeropage: Zeropage) {
private val assemblyLines = mutableListOf<String>()
private val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
private val allocatedZeropageVariables = mutableMapOf<String, Pair<Int, DataType>>()
private val breakpointLabels = mutableListOf<String>()
private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this)
private val forloopsAsmGen = ForLoopsAsmGen(program, this)
private val postincrdecrAsmGen = PostIncrDecrAsmGen(program, this)
private val functioncallAsmGen = FunctionCallAsmGen(program, this)
private val assignmentAsmGen = AssignmentAsmGen(program, this)
private val expressionsAsmGen = ExpressionsAsmGen(program, this)
internal val loopEndLabels = Stack<String>()
internal val loopContinueLabels = Stack<String>()
internal fun compileToAssembly(optimize: Boolean): AssemblyProgram {
assemblyLines.clear()
loopEndLabels.clear()
loopContinueLabels.clear()
println("Generating assembly code... ")
header()
val allBlocks = program.allBlocks()
if(allBlocks.first().name != "main")
throw AssemblyError("first block should be 'main'")
for(b in program.allBlocks())
block2asm(b)
footer()
if(optimize) {
var optimizationsDone = 1
while (optimizationsDone > 0) {
optimizationsDone = optimizeAssembly(assemblyLines)
}
}
File("${program.name}.asm").printWriter().use {
for (line in assemblyLines) { it.println(line) }
}
return AssemblyProgram(program.name)
}
private fun header() {
val ourName = this.javaClass.name
out("; 6502 assembly code for '${program.name}'")
out("; generated by $ourName on ${Date()}")
out("; assembler syntax is for the 64tasm cross-assembler")
out("; output options: output=${options.output} launcher=${options.launcher} zp=${options.zeropage}")
out("\n.cpu '6502'\n.enc 'none'\n")
program.actualLoadAddress = program.definedLoadAddress
if (program.actualLoadAddress == 0) // fix load address
program.actualLoadAddress = if (options.launcher == LauncherType.BASIC)
MachineDefinition.BASIC_LOAD_ADDRESS else MachineDefinition.RAW_LOAD_ADDRESS
when {
options.launcher == LauncherType.BASIC -> {
if (program.actualLoadAddress != 0x0801)
throw AssemblyError("BASIC output must have load address $0801")
out("; ---- basic program with sys call ----")
out("* = ${program.actualLoadAddress.toHex()}")
val year = Calendar.getInstance().get(Calendar.YEAR)
out(" .word (+), $year")
out(" .null $9e, format(' %d ', _prog8_entrypoint), $3a, $8f, ' prog8 by idj'")
out("+\t.word 0")
out("_prog8_entrypoint\t; assembly code starts here\n")
out(" jsr prog8_lib.init_system")
}
options.output == OutputType.PRG -> {
out("; ---- program without basic sys call ----")
out("* = ${program.actualLoadAddress.toHex()}\n")
out(" jsr prog8_lib.init_system")
}
options.output == OutputType.RAW -> {
out("; ---- raw assembler program ----")
out("* = ${program.actualLoadAddress.toHex()}\n")
}
}
if (zeropage.exitProgramStrategy != Zeropage.ExitProgramStrategy.CLEAN_EXIT) {
// disable shift-commodore charset switching and run/stop key
out(" lda #$80")
out(" lda #$80")
out(" sta 657\t; disable charset switching")
out(" lda #239")
out(" sta 808\t; disable run/stop key")
}
out(" ldx #\$ff\t; init estack pointer")
out(" ; initialize the variables in each block")
for (block in program.allBlocks()) {
val initVarsSub = block.statements.singleOrNull { it is Subroutine && it.name == initvarsSubName }
if(initVarsSub!=null)
out(" jsr ${block.name}.$initvarsSubName")
}
out(" clc")
when (zeropage.exitProgramStrategy) {
Zeropage.ExitProgramStrategy.CLEAN_EXIT -> {
out(" jmp main.start\t; jump to program entrypoint")
}
Zeropage.ExitProgramStrategy.SYSTEM_RESET -> {
out(" jsr main.start\t; call program entrypoint")
out(" jmp (c64.RESET_VEC)\t; cold reset")
}
}
out("")
}
private fun footer() {
// the global list of all floating point constants for the whole program
out("; global float constants")
for (flt in globalFloatConsts) {
val mflpt5 = MachineDefinition.Mflpt5.fromNumber(flt.key)
val floatFill = makeFloatFill(mflpt5)
val floatvalue = flt.key
out("${flt.value}\t.byte $floatFill ; float $floatvalue")
}
}
private fun block2asm(block: Block) {
out("\n; ---- block: '${block.name}' ----")
out("${block.name}\t" + (if("force_output" in block.options()) ".block\n" else ".proc\n"))
if(block.address!=null) {
out(".cerror * > ${block.address.toHex()}, 'block address overlaps by ', *-${block.address.toHex()},' bytes'")
out("* = ${block.address.toHex()}")
}
outputSourceLine(block)
zeropagevars2asm(block.statements)
memdefs2asm(block.statements)
vardecls2asm(block.statements)
out("\n; subroutines in this block")
// first translate regular statements, and then put the subroutines at the end.
val (subroutine, stmts) = block.statements.partition { it is Subroutine }
stmts.forEach { translate(it) }
subroutine.forEach { translateSubroutine(it as Subroutine) }
out(if("force_output" in block.options()) "\n\t.bend\n" else "\n\t.pend\n")
}
private var generatedLabelSequenceNumber: Int = 0
internal fun makeLabel(postfix: String): String {
generatedLabelSequenceNumber++
return "_prog8_label_${generatedLabelSequenceNumber}_$postfix"
}
private fun outputSourceLine(node: Node) {
out(" ;\tsrc line: ${node.position.file}:${node.position.line}")
}
internal fun out(str: String, splitlines: Boolean = true) {
val fragment = (if(" | " in str) str.replace("|", "\n") else str).trim('\n')
if (splitlines) {
for (line in fragment.split('\n')) {
val trimmed = if (line.startsWith(' ')) "\t" + line.trim() else line.trim()
// trimmed = trimmed.replace(Regex("^\\+\\s+"), "+\t") // sanitize local label indentation
assemblyLines.add(trimmed)
}
} else assemblyLines.add(fragment)
}
private fun makeFloatFill(flt: MachineDefinition.Mflpt5): String {
val b0 = "$" + flt.b0.toString(16).padStart(2, '0')
val b1 = "$" + flt.b1.toString(16).padStart(2, '0')
val b2 = "$" + flt.b2.toString(16).padStart(2, '0')
val b3 = "$" + flt.b3.toString(16).padStart(2, '0')
val b4 = "$" + flt.b4.toString(16).padStart(2, '0')
return "$b0, $b1, $b2, $b3, $b4"
}
private fun encodeStr(str: String, dt: DataType): List<Short> {
return when(dt) {
DataType.STR -> {
val bytes = Petscii.encodePetscii(str, true)
bytes.plus(0)
}
DataType.STR_S -> {
val bytes = Petscii.encodeScreencode(str, true)
bytes.plus(0)
}
else -> throw AssemblyError("invalid str type")
}
}
private fun zeropagevars2asm(statements: List<Statement>) {
out("; vars allocated on zeropage")
val variables = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR }
for(variable in variables) {
// should NOT allocate subroutine parameters on the zero page
val fullName = variable.scopedname
val zpVar = allocatedZeropageVariables[fullName]
if(zpVar==null) {
// This var is not on the ZP yet. Attempt to move it there (if it's not a float, those take up too much space)
if(variable.zeropage != ZeropageWish.NOT_IN_ZEROPAGE &&
variable.datatype in zeropage.allowedDatatypes
&& variable.datatype != DataType.FLOAT
&& options.zeropage != ZeropageType.DONTUSE) {
try {
val address = zeropage.allocate(fullName, variable.datatype, null)
out("${variable.name} = $address\t; auto zp ${variable.datatype}")
// make sure we add the var to the set of zpvars for this block
allocatedZeropageVariables[fullName] = Pair(address, variable.datatype)
} catch (x: ZeropageDepletedError) {
// leave it as it is.
}
}
}
}
}
private fun vardecl2asm(decl: VarDecl) {
when (decl.datatype) {
DataType.UBYTE -> out("${decl.name}\t.byte 0")
DataType.BYTE -> out("${decl.name}\t.char 0")
DataType.UWORD -> out("${decl.name}\t.word 0")
DataType.WORD -> out("${decl.name}\t.sint 0")
DataType.FLOAT -> out("${decl.name}\t.byte 0,0,0,0,0 ; float")
DataType.STRUCT -> {} // is flattened
DataType.STR, DataType.STR_S -> {
val string = (decl.value as StringLiteralValue).value
val encoded = encodeStr(string, decl.datatype)
outputStringvar(decl, encoded)
}
DataType.ARRAY_UB -> {
val data = makeArrayFillDataUnsigned(decl)
if (data.size <= 16)
out("${decl.name}\t.byte ${data.joinToString()}")
else {
out(decl.name)
for (chunk in data.chunked(16))
out(" .byte " + chunk.joinToString())
}
}
DataType.ARRAY_B -> {
val data = makeArrayFillDataSigned(decl)
if (data.size <= 16)
out("${decl.name}\t.char ${data.joinToString()}")
else {
out(decl.name)
for (chunk in data.chunked(16))
out(" .char " + chunk.joinToString())
}
}
DataType.ARRAY_UW -> {
val data = makeArrayFillDataUnsigned(decl)
if (data.size <= 16)
out("${decl.name}\t.word ${data.joinToString()}")
else {
out(decl.name)
for (chunk in data.chunked(16))
out(" .word " + chunk.joinToString())
}
}
DataType.ARRAY_W -> {
val data = makeArrayFillDataSigned(decl)
if (data.size <= 16)
out("${decl.name}\t.sint ${data.joinToString()}")
else {
out(decl.name)
for (chunk in data.chunked(16))
out(" .sint " + chunk.joinToString())
}
}
DataType.ARRAY_F -> {
val array = (decl.value as ArrayLiteralValue).value
val floatFills = array.map {
val number = (it as NumericLiteralValue).number
makeFloatFill(MachineDefinition.Mflpt5.fromNumber(number))
}
out(decl.name)
for (f in array.zip(floatFills))
out(" .byte ${f.second} ; float ${f.first}")
}
}
}
private fun memdefs2asm(statements: List<Statement>) {
out("\n; memdefs and kernel subroutines")
val memvars = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.MEMORY || it.type==VarDeclType.CONST }
for(m in memvars) {
out(" ${m.name} = ${(m.value as NumericLiteralValue).number.toHex()}")
}
val asmSubs = statements.filterIsInstance<Subroutine>().filter { it.isAsmSubroutine }
for(sub in asmSubs) {
if(sub.asmAddress!=null) {
if(sub.statements.isNotEmpty())
throw AssemblyError("kernel subroutine cannot have statements")
out(" ${sub.name} = ${sub.asmAddress.toHex()}")
}
}
}
private fun vardecls2asm(statements: List<Statement>) {
out("\n; non-zeropage variables")
val vars = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR }
// first output the flattened struct member variables *in order*
// after that, the other variables sorted by their datatype
val (structMembers, normalVars) = vars.partition { it.struct!=null }
structMembers.forEach { vardecl2asm(it) }
// special treatment for string types: merge strings that are identical
val encodedstringVars = normalVars
.filter {it.datatype in StringDatatypes }
.map { it to encodeStr((it.value as StringLiteralValue).value, it.datatype) }
.groupBy({it.second}, {it.first})
for((encoded, variables) in encodedstringVars) {
variables.dropLast(1).forEach { out(it.name) }
val lastvar = variables.last()
outputStringvar(lastvar, encoded)
}
// non-string variables
normalVars.filter{ it.datatype !in StringDatatypes}.sortedBy { it.datatype }.forEach {
if(it.scopedname !in allocatedZeropageVariables)
vardecl2asm(it)
}
}
private fun outputStringvar(lastvar: VarDecl, encoded: List<Short>) {
val string = (lastvar.value as StringLiteralValue).value
out("${lastvar.name}\t; ${lastvar.datatype} \"${escape(string).replace("\u0000", "<NULL>")}\"")
val outputBytes = encoded.map { "$" + it.toString(16).padStart(2, '0') }
for (chunk in outputBytes.chunked(16))
out(" .byte " + chunk.joinToString())
}
private fun makeArrayFillDataUnsigned(decl: VarDecl): List<String> {
val array = (decl.value as ArrayLiteralValue).value
return when {
decl.datatype == DataType.ARRAY_UB ->
// byte array can never contain pointer-to types, so treat values as all integers
array.map {
val number = (it as NumericLiteralValue).number.toInt()
"$"+number.toString(16).padStart(2, '0')
}
decl.datatype== DataType.ARRAY_UW -> array.map {
if(it is NumericLiteralValue) {
"$" + it.number.toInt().toString(16).padStart(4, '0')
} else {
(it as AddressOf).identifier.nameInSource.joinToString(".")
}
}
else -> throw AssemblyError("invalid arraysize type")
}
}
private fun makeArrayFillDataSigned(decl: VarDecl): List<String> {
val array = (decl.value as ArrayLiteralValue).value
return when {
decl.datatype == DataType.ARRAY_UB ->
// byte array can never contain pointer-to types, so treat values as all integers
array.map {
val number = (it as NumericLiteralValue).number.toInt()
val hexnum = number.toString(16).padStart(2, '0')
"$$hexnum"
}
decl.datatype == DataType.ARRAY_B ->
// byte array can never contain pointer-to types, so treat values as all integers
array.map {
val number = (it as NumericLiteralValue).number.toInt()
val hexnum = number.absoluteValue.toString(16).padStart(2, '0')
if(number>=0)
"$$hexnum"
else
"-$$hexnum"
}
decl.datatype== DataType.ARRAY_UW -> array.map {
val number = (it as NumericLiteralValue).number.toInt()
val hexnum = number.toString(16).padStart(4, '0')
"$$hexnum"
}
decl.datatype== DataType.ARRAY_W -> array.map {
val number = (it as NumericLiteralValue).number.toInt()
val hexnum = number.absoluteValue.toString(16).padStart(4, '0')
if(number>=0)
"$$hexnum"
else
"-$$hexnum"
}
else -> throw AssemblyError("invalid arraysize type ${decl.datatype}")
}
}
internal fun getFloatConst(number: Double): String {
// try to match the ROM float constants to save memory
val mflpt5 = MachineDefinition.Mflpt5.fromNumber(number)
val floatbytes = shortArrayOf(mflpt5.b0, mflpt5.b1, mflpt5.b2, mflpt5.b3, mflpt5.b4)
when {
floatbytes.contentEquals(shortArrayOf(0x00, 0x00, 0x00, 0x00, 0x00)) -> return "c64flt.FL_ZERO"
floatbytes.contentEquals(shortArrayOf(0x82, 0x49, 0x0f, 0xda, 0xa1)) -> return "c64flt.FL_PIVAL"
floatbytes.contentEquals(shortArrayOf(0x90, 0x80, 0x00, 0x00, 0x00)) -> return "c64flt.FL_N32768"
floatbytes.contentEquals(shortArrayOf(0x81, 0x00, 0x00, 0x00, 0x00)) -> return "c64flt.FL_FONE"
floatbytes.contentEquals(shortArrayOf(0x80, 0x35, 0x04, 0xf3, 0x34)) -> return "c64flt.FL_SQRHLF"
floatbytes.contentEquals(shortArrayOf(0x81, 0x35, 0x04, 0xf3, 0x34)) -> return "c64flt.FL_SQRTWO"
floatbytes.contentEquals(shortArrayOf(0x80, 0x80, 0x00, 0x00, 0x00)) -> return "c64flt.FL_NEGHLF"
floatbytes.contentEquals(shortArrayOf(0x80, 0x31, 0x72, 0x17, 0xf8)) -> return "c64flt.FL_LOG2"
floatbytes.contentEquals(shortArrayOf(0x84, 0x20, 0x00, 0x00, 0x00)) -> return "c64flt.FL_TENC"
floatbytes.contentEquals(shortArrayOf(0x9e, 0x6e, 0x6b, 0x28, 0x00)) -> return "c64flt.FL_NZMIL"
floatbytes.contentEquals(shortArrayOf(0x80, 0x00, 0x00, 0x00, 0x00)) -> return "c64flt.FL_FHALF"
floatbytes.contentEquals(shortArrayOf(0x81, 0x38, 0xaa, 0x3b, 0x29)) -> return "c64flt.FL_LOGEB2"
floatbytes.contentEquals(shortArrayOf(0x81, 0x49, 0x0f, 0xda, 0xa2)) -> return "c64flt.FL_PIHALF"
floatbytes.contentEquals(shortArrayOf(0x83, 0x49, 0x0f, 0xda, 0xa2)) -> return "c64flt.FL_TWOPI"
floatbytes.contentEquals(shortArrayOf(0x7f, 0x00, 0x00, 0x00, 0x00)) -> return "c64flt.FL_FR4"
else -> {
// attempt to correct for a few rounding issues
when (number.toBigDecimal().setScale(10, RoundingMode.HALF_DOWN).toDouble()) {
3.1415926536 -> return "c64flt.FL_PIVAL"
1.4142135624 -> return "c64flt.FL_SQRTWO"
0.7071067812 -> return "c64flt.FL_SQRHLF"
0.6931471806 -> return "c64flt.FL_LOG2"
else -> {}
}
// no ROM float const for this value, create our own
val name = globalFloatConsts[number]
if(name!=null)
return name
val newName = "prog8_float_const_${globalFloatConsts.size}"
globalFloatConsts[number] = newName
return newName
}
}
}
internal fun signExtendAtoMsb(destination: String) =
"""
ora #$7f
bmi +
lda #0
+ sta $destination
"""
internal fun asmIdentifierName(identifier: IdentifierReference): String {
val name = if(identifier.memberOfStruct(program.namespace)!=null) {
identifier.targetVarDecl(program.namespace)!!.name
} else {
identifier.nameInSource.joinToString(".")
}
return fixNameSymbols(name)
}
internal fun fixNameSymbols(name: String) = name.replace("<", "prog8_").replace(">", "") // take care of the autogenerated invalid (anon) label names
private fun branchInstruction(condition: BranchCondition, complement: Boolean) =
if(complement) {
when (condition) {
BranchCondition.CS -> "bcc"
BranchCondition.CC -> "bcs"
BranchCondition.EQ, BranchCondition.Z -> "beq"
BranchCondition.NE, BranchCondition.NZ -> "bne"
BranchCondition.VS -> "bvc"
BranchCondition.VC -> "bvs"
BranchCondition.MI, BranchCondition.NEG -> "bmi"
BranchCondition.PL, BranchCondition.POS -> "bpl"
}
} else {
when (condition) {
BranchCondition.CS -> "bcs"
BranchCondition.CC -> "bcc"
BranchCondition.EQ, BranchCondition.Z -> "beq"
BranchCondition.NE, BranchCondition.NZ -> "bne"
BranchCondition.VS -> "bvs"
BranchCondition.VC -> "bvc"
BranchCondition.MI, BranchCondition.NEG -> "bmi"
BranchCondition.PL, BranchCondition.POS -> "bpl"
}
}
internal fun readAndPushArrayvalueWithIndexA(arrayDt: DataType, variable: IdentifierReference) {
val variablename = asmIdentifierName(variable)
when (arrayDt) {
DataType.STR, DataType.STR_S, DataType.ARRAY_UB, DataType.ARRAY_B ->
out(" tay | lda $variablename,y | sta $ESTACK_LO_HEX,x | dex")
DataType.ARRAY_UW, DataType.ARRAY_W ->
out(" asl a | tay | lda $variablename,y | sta $ESTACK_LO_HEX,x | lda $variablename+1,y | sta $ESTACK_HI_HEX,x | dex")
DataType.ARRAY_F ->
// index * 5 is done in the subroutine that's called
out("""
sta $ESTACK_LO_HEX,x
dex
lda #<$variablename
ldy #>$variablename
jsr c64flt.push_float_from_indexed_var
""")
else ->
throw AssemblyError("weird array type")
}
}
internal fun saveRegister(register: Register) {
when(register) {
Register.A -> out(" pha")
Register.X -> out(" txa | pha")
Register.Y -> out(" tya | pha")
}
}
internal fun restoreRegister(register: Register) {
when(register) {
Register.A -> out(" pla")
Register.X -> out(" pla | tax")
Register.Y -> out(" pla | tay")
}
}
private fun translateSubroutine(sub: Subroutine) {
out("")
outputSourceLine(sub)
if(sub.isAsmSubroutine) {
if(sub.asmAddress!=null)
return // already done at the memvars section
// asmsub with most likely just an inline asm in it
out("${sub.name}\t.proc")
sub.statements.forEach{ translate(it) }
out(" .pend\n")
} else {
// regular subroutine
out("${sub.name}\t.proc")
zeropagevars2asm(sub.statements)
memdefs2asm(sub.statements)
out("; statements")
sub.statements.forEach{ translate(it) }
out("; variables")
vardecls2asm(sub.statements)
out(" .pend\n")
}
}
internal fun translate(stmt: Statement) {
outputSourceLine(stmt)
when(stmt) {
is VarDecl, is StructDecl, is NopStatement -> {}
is Directive -> translate(stmt)
is Return -> translate(stmt)
is Subroutine -> translateSubroutine(stmt)
is InlineAssembly -> translate(stmt)
is FunctionCallStatement -> {
val functionName = stmt.target.nameInSource.last()
val builtinFunc = BuiltinFunctions[functionName]
if(builtinFunc!=null) {
builtinFunctionsAsmGen.translateFunctioncallStatement(stmt, builtinFunc)
} else {
functioncallAsmGen.translateFunctionCall(stmt)
// discard any results from the stack:
val sub = stmt.target.targetSubroutine(program.namespace)!!
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
for((t, reg) in returns) {
if(reg.stack) {
if (t in IntegerDatatypes || t in PassByReferenceDatatypes) out(" inx")
else if (t == DataType.FLOAT) out(" inx | inx | inx")
}
}
}
}
is Assignment -> assignmentAsmGen.translate(stmt)
is Jump -> translate(stmt)
is PostIncrDecr -> postincrdecrAsmGen.translate(stmt)
is Label -> translate(stmt)
is BranchStatement -> translate(stmt)
is IfStatement -> translate(stmt)
is ForLoop -> forloopsAsmGen.translate(stmt)
is Continue -> out(" jmp ${loopContinueLabels.peek()}")
is Break -> out(" jmp ${loopEndLabels.peek()}")
is WhileLoop -> translate(stmt)
is RepeatLoop -> translate(stmt)
is WhenStatement -> translate(stmt)
is BuiltinFunctionStatementPlaceholder -> throw AssemblyError("builtin function should not have placeholder anymore?")
is AnonymousScope -> translate(stmt)
is Block -> throw AssemblyError("block should have been handled elsewhere")
}
}
private fun translate(stmt: IfStatement) {
expressionsAsmGen.translateExpression(stmt.condition)
translateTestStack(stmt.condition.inferType(program).typeOrElse(DataType.STRUCT))
val elseLabel = makeLabel("if_else")
val endLabel = makeLabel("if_end")
out(" beq $elseLabel")
translate(stmt.truepart)
out(" jmp $endLabel")
out(elseLabel)
translate(stmt.elsepart)
out(endLabel)
}
private fun translateTestStack(dataType: DataType) {
when(dataType) {
in ByteDatatypes -> out(" inx | lda $ESTACK_LO_HEX,x")
in WordDatatypes -> out(" inx | lda $ESTACK_LO_HEX,x | ora $ESTACK_HI_HEX,x")
DataType.FLOAT -> throw AssemblyError("conditional value should be an integer (boolean)")
else -> throw AssemblyError("non-numerical dt")
}
}
private fun translate(stmt: WhileLoop) {
val whileLabel = makeLabel("while")
val endLabel = makeLabel("whileend")
loopEndLabels.push(endLabel)
loopContinueLabels.push(whileLabel)
out(whileLabel)
// TODO optimize for the simple cases, can we avoid stack use?
expressionsAsmGen.translateExpression(stmt.condition)
val conditionDt = stmt.condition.inferType(program)
if(!conditionDt.isKnown)
throw AssemblyError("unknown condition dt")
if(conditionDt.typeOrElse(DataType.BYTE) in ByteDatatypes) {
out(" inx | lda $ESTACK_LO_HEX,x | beq $endLabel")
} else {
out("""
inx
lda $ESTACK_LO_HEX,x
bne +
lda $ESTACK_HI_HEX,x
beq $endLabel
+ """)
}
translate(stmt.body)
out(" jmp $whileLabel")
out(endLabel)
loopEndLabels.pop()
loopContinueLabels.pop()
}
private fun translate(stmt: RepeatLoop) {
val repeatLabel = makeLabel("repeat")
val endLabel = makeLabel("repeatend")
loopEndLabels.push(endLabel)
loopContinueLabels.push(repeatLabel)
out(repeatLabel)
// TODO optimize this for the simple cases, can we avoid stack use?
translate(stmt.body)
expressionsAsmGen.translateExpression(stmt.untilCondition)
val conditionDt = stmt.untilCondition.inferType(program)
if(!conditionDt.isKnown)
throw AssemblyError("unknown condition dt")
if(conditionDt.typeOrElse(DataType.BYTE) in ByteDatatypes) {
out(" inx | lda $ESTACK_LO_HEX,x | beq $repeatLabel")
} else {
out("""
inx
lda $ESTACK_LO_HEX,x
bne +
lda $ESTACK_HI_HEX,x
beq $repeatLabel
+ """)
}
out(endLabel)
loopEndLabels.pop()
loopContinueLabels.pop()
}
private fun translate(stmt: WhenStatement) {
expressionsAsmGen.translateExpression(stmt.condition)
val endLabel = makeLabel("choice_end")
val choiceBlocks = mutableListOf<Pair<String, AnonymousScope>>()
val conditionDt = stmt.condition.inferType(program)
if(!conditionDt.isKnown)
throw AssemblyError("unknown condition dt")
if(conditionDt.typeOrElse(DataType.BYTE) in ByteDatatypes)
out(" inx | lda $ESTACK_LO_HEX,x")
else
out(" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x")
for(choice in stmt.choices) {
val choiceLabel = makeLabel("choice")
if(choice.values==null) {
// the else choice
translate(choice.statements)
out(" jmp $endLabel")
} else {
choiceBlocks.add(Pair(choiceLabel, choice.statements))
for (cv in choice.values!!) {
val value = (cv as NumericLiteralValue).number.toInt()
if(conditionDt.typeOrElse(DataType.BYTE) in ByteDatatypes) {
out(" cmp #${value.toHex()} | beq $choiceLabel")
} else {
out("""
cmp #<${value.toHex()}
bne +
cpy #>${value.toHex()}
beq $choiceLabel
+
""")
}
}
}
}
for(choiceBlock in choiceBlocks) {
out(choiceBlock.first)
translate(choiceBlock.second)
out(" jmp $endLabel")
}
out(endLabel)
}
private fun translate(stmt: Label) {
out(stmt.name)
}
private fun translate(scope: AnonymousScope) {
// note: the variables defined in an anonymous scope have been moved to their defining subroutine's scope
scope.statements.forEach{ translate(it) }
}
private fun translate(stmt: BranchStatement) {
if(stmt.truepart.containsNoCodeNorVars() && stmt.elsepart.containsCodeOrVars())
throw AssemblyError("only else part contains code, shoud have been switched already")
val jump = stmt.truepart.statements.first() as? Jump
if(jump!=null) {
// branch with only a jump
val instruction = branchInstruction(stmt.condition, false)
out(" $instruction ${getJumpTarget(jump)}")
translate(stmt.elsepart)
} else {
if(stmt.elsepart.containsNoCodeNorVars()) {
val instruction = branchInstruction(stmt.condition, true)
val elseLabel = makeLabel("branch_else")
out(" $instruction $elseLabel")
translate(stmt.truepart)
out(elseLabel)
} else {
val instruction = branchInstruction(stmt.condition, false)
val trueLabel = makeLabel("branch_true")
val endLabel = makeLabel("branch_end")
out(" $instruction $trueLabel")
translate(stmt.elsepart)
out(" jmp $endLabel")
out(trueLabel)
translate(stmt.truepart)
out(endLabel)
}
}
}
private fun translate(stmt: Directive) {
when(stmt.directive) {
"%asminclude" -> {
val sourcecode = loadAsmIncludeFile(stmt.args[0].str!!, stmt.definingModule().source)
val scopeprefix = stmt.args[1].str ?: ""
if(!scopeprefix.isBlank())
out("$scopeprefix\t.proc")
assemblyLines.add(sourcecode.trimEnd().trimStart('\n'))
if(!scopeprefix.isBlank())
out(" .pend\n")
}
"%asmbinary" -> {
val offset = if(stmt.args.size>1) ", ${stmt.args[1].int}" else ""
val length = if(stmt.args.size>2) ", ${stmt.args[2].int}" else ""
out(" .binary \"${stmt.args[0].str}\" $offset $length")
}
"%breakpoint" -> {
val label = "_prog8_breakpoint_${breakpointLabels.size+1}"
breakpointLabels.add(label)
out("$label\tnop")
}
}
}
private fun translate(jmp: Jump) {
out(" jmp ${getJumpTarget(jmp)}")
}
private fun getJumpTarget(jmp: Jump): String {
return when {
jmp.identifier!=null -> asmIdentifierName(jmp.identifier)
jmp.generatedLabel!=null -> jmp.generatedLabel
jmp.address!=null -> jmp.address.toHex()
else -> "????"
}
}
private fun translate(ret: Return) {
ret.value?.let { expressionsAsmGen.translateExpression(it) }
out(" rts")
}
private fun translate(asm: InlineAssembly) {
val assembly = asm.assembly.trimEnd().trimStart('\n')
assemblyLines.add(assembly)
}
internal fun translateArrayIndexIntoA(expr: ArrayIndexedExpression) {
val index = expr.arrayspec.index
when (index) {
is NumericLiteralValue -> throw AssemblyError("this should be optimized directly")
is RegisterExpr -> {
when (index.register) {
Register.A -> {}
Register.X -> out(" txa")
Register.Y -> out(" tya")
}
}
is IdentifierReference -> {
val indexName = asmIdentifierName(index)
out(" lda $indexName")
}
else -> {
expressionsAsmGen.translateExpression(index)
out(" inx | lda $ESTACK_LO_HEX,x")
}
}
}
internal fun translateExpression(expression: Expression) =
expressionsAsmGen.translateExpression(expression)
internal fun translateFunctioncallExpression(functionCall: FunctionCall, signature: FunctionSignature) =
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature)
internal fun translateFunctionCall(functionCall: FunctionCall) =
functioncallAsmGen.translateFunctionCall(functionCall)
internal fun assignFromEvalResult(target: AssignTarget) =
assignmentAsmGen.assignFromEvalResult(target)
fun assignFromByteConstant(target: AssignTarget, value: Short) =
assignmentAsmGen.assignFromByteConstant(target, value)
fun assignFromWordConstant(target: AssignTarget, value: Int) =
assignmentAsmGen.assignFromWordConstant(target, value)
fun assignFromFloatConstant(target: AssignTarget, value: Double) =
assignmentAsmGen.assignFromFloatConstant(target, value)
fun assignFromByteVariable(target: AssignTarget, variable: IdentifierReference) =
assignmentAsmGen.assignFromByteVariable(target, variable)
fun assignFromWordVariable(target: AssignTarget, variable: IdentifierReference) =
assignmentAsmGen.assignFromWordVariable(target, variable)
fun assignFromFloatVariable(target: AssignTarget, variable: IdentifierReference) =
assignmentAsmGen.assignFromFloatVariable(target, variable)
fun assignFromRegister(target: AssignTarget, register: Register) =
assignmentAsmGen.assignFromRegister(target, register)
fun assignFromMemoryByte(target: AssignTarget, address: Int?, identifier: IdentifierReference?) =
assignmentAsmGen.assignFromMemoryByte(target, address, identifier)
}

View File

@ -1,4 +1,4 @@
package prog8.compiler.target.c64.codegen2 package prog8.compiler.target.c64.codegen
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_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_PLUS1_HEX

View File

@ -0,0 +1,745 @@
package prog8.compiler.target.c64.codegen
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.statements.DirectMemoryWrite
import prog8.ast.statements.VarDecl
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.toHex
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translate(assign: Assignment) {
if(assign.aug_op!=null)
throw AssemblyError("aug-op assignments should have been transformed to normal ones")
when(assign.value) {
is NumericLiteralValue -> {
val numVal = assign.value as NumericLiteralValue
when(numVal.type) {
DataType.UBYTE, DataType.BYTE -> assignFromByteConstant(assign.target, numVal.number.toShort())
DataType.UWORD, DataType.WORD -> assignFromWordConstant(assign.target, numVal.number.toInt())
DataType.FLOAT -> assignFromFloatConstant(assign.target, numVal.number.toDouble())
else -> throw AssemblyError("weird numval type")
}
}
is RegisterExpr -> {
assignFromRegister(assign.target, (assign.value as RegisterExpr).register)
}
is IdentifierReference -> {
val type = assign.target.inferType(program, assign).typeOrElse(DataType.STRUCT)
when(type) {
DataType.UBYTE, DataType.BYTE -> assignFromByteVariable(assign.target, assign.value as IdentifierReference)
DataType.UWORD, DataType.WORD -> assignFromWordVariable(assign.target, assign.value as IdentifierReference)
DataType.FLOAT -> assignFromFloatVariable(assign.target, assign.value as IdentifierReference)
else -> throw AssemblyError("unsupported assignment target type $type")
}
}
is AddressOf -> {
val identifier = (assign.value as AddressOf).identifier
assignFromAddressOf(assign.target, identifier)
}
is DirectMemoryRead -> {
val read = (assign.value as DirectMemoryRead)
when(read.addressExpression) {
is NumericLiteralValue -> {
val address = (read.addressExpression as NumericLiteralValue).number.toInt()
assignFromMemoryByte(assign.target, address, null)
}
is IdentifierReference -> {
assignFromMemoryByte(assign.target, null, read.addressExpression as IdentifierReference)
}
else -> {
asmgen.translateExpression(read.addressExpression)
TODO("read memory byte from result and put that in ${assign.target}")
}
}
}
is PrefixExpression -> {
// TODO optimize common cases
asmgen.translateExpression(assign.value as PrefixExpression)
assignFromEvalResult(assign.target)
}
is BinaryExpression -> {
// TODO optimize common cases
asmgen.translateExpression(assign.value as BinaryExpression)
assignFromEvalResult(assign.target)
}
is ArrayIndexedExpression -> {
// TODO optimize common cases
val arrayExpr = assign.value as ArrayIndexedExpression
val arrayDt = arrayExpr.identifier.targetVarDecl(program.namespace)!!.datatype
val index = arrayExpr.arrayspec.index
if(index is NumericLiteralValue) {
// constant array index value
val arrayVarName = asmgen.asmIdentifierName(arrayExpr.identifier)
val indexValue = index.number.toInt() * ArrayElementTypes.getValue(arrayDt).memorySize()
when (arrayDt) {
DataType.STR, DataType.STR_S, DataType.ARRAY_UB, DataType.ARRAY_B ->
asmgen.out(" lda $arrayVarName+$indexValue | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
DataType.ARRAY_UW, DataType.ARRAY_W ->
asmgen.out(" lda $arrayVarName+$indexValue | sta ${MachineDefinition.ESTACK_LO_HEX},x | lda $arrayVarName+$indexValue+1 | sta ${MachineDefinition.ESTACK_HI_HEX},x | dex")
DataType.ARRAY_F ->
asmgen.out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue | jsr c64flt.push_float")
else ->
throw AssemblyError("weird array type")
}
} else {
asmgen.translateArrayIndexIntoA(arrayExpr)
asmgen.readAndPushArrayvalueWithIndexA(arrayDt, arrayExpr.identifier)
}
assignFromEvalResult(assign.target)
}
is TypecastExpression -> {
val cast = assign.value as TypecastExpression
val sourceType = cast.expression.inferType(program)
val targetType = assign.target.inferType(program, assign)
if(sourceType.isKnown && targetType.isKnown &&
(sourceType.typeOrElse(DataType.STRUCT) in ByteDatatypes && targetType.typeOrElse(DataType.STRUCT) in ByteDatatypes) ||
(sourceType.typeOrElse(DataType.STRUCT) in WordDatatypes && targetType.typeOrElse(DataType.STRUCT) in WordDatatypes)) {
// no need for a type cast
assign.value = cast.expression
translate(assign)
} else {
asmgen.translateExpression(assign.value as TypecastExpression)
assignFromEvalResult(assign.target)
}
}
is FunctionCall -> {
asmgen.translateExpression(assign.value as FunctionCall)
assignFromEvalResult(assign.target)
}
is ArrayLiteralValue, is StringLiteralValue -> TODO("string/array/struct assignment?")
is StructLiteralValue -> throw AssemblyError("struct literal value assignment should have been flattened")
is RangeExpr -> throw AssemblyError("range expression should have been changed into array values")
}
}
internal fun assignFromEvalResult(target: AssignTarget) {
val targetIdent = target.identifier
when {
target.register!=null -> {
if(target.register== Register.X)
throw AssemblyError("can't pop into X register - use variable instead")
asmgen.out(" inx | ld${target.register.name.toLowerCase()} ${MachineDefinition.ESTACK_LO_HEX},x ")
}
targetIdent!=null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
val targetDt = targetIdent.inferType(program).typeOrElse(DataType.STRUCT)
when(targetDt) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.out(" inx | lda ${MachineDefinition.ESTACK_LO_HEX},x | sta $targetName")
}
DataType.UWORD, DataType.WORD -> {
asmgen.out("""
inx
lda ${MachineDefinition.ESTACK_LO_HEX},x
sta $targetName
lda ${MachineDefinition.ESTACK_HI_HEX},x
sta $targetName+1
""")
}
DataType.FLOAT -> {
asmgen.out("""
lda #<$targetName
ldy #>$targetName
jsr c64flt.pop_float
""")
}
else -> throw AssemblyError("weird target variable type $targetDt")
}
}
target.memoryAddress!=null -> {
asmgen.out(" inx | ldy ${MachineDefinition.ESTACK_LO_HEX},x")
storeRegisterInMemoryAddress(Register.Y, target.memoryAddress)
}
target.arrayindexed!=null -> {
val arrayDt = target.arrayindexed!!.identifier.targetVarDecl(program.namespace)!!.datatype
val arrayVarName = asmgen.asmIdentifierName(target.arrayindexed!!.identifier)
asmgen.translateExpression(target.arrayindexed!!.arrayspec.index)
asmgen.out(" inx | lda ${MachineDefinition.ESTACK_LO_HEX},x")
popAndWriteArrayvalueWithIndexA(arrayDt, arrayVarName)
}
else -> throw AssemblyError("weird assignment target $target")
}
}
internal fun assignFromAddressOf(target: AssignTarget, name: IdentifierReference) {
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
val struct = name.memberOfStruct(program.namespace)
val sourceName = if(struct!=null) {
// take the address of the first struct member instead
val decl = name.targetVarDecl(program.namespace)!!
val firstStructMember = struct.nameOfFirstMember()
// find the flattened var that belongs to this first struct member
val firstVarName = listOf(decl.name, firstStructMember)
val firstVar = name.definingScope().lookup(firstVarName, name) as VarDecl
firstVar.name
} else {
asmgen.fixNameSymbols(name.nameInSource.joinToString ("."))
}
when {
targetIdent!=null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
asmgen.out("""
lda #<$sourceName
ldy #>$sourceName
sta $targetName
sty $targetName+1
""")
}
target.memoryAddress!=null -> {
TODO("assign address $sourceName to memory word $target")
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
TODO("assign address $sourceName to array $targetName [ $index ]")
}
else -> TODO("assign address $sourceName to $target")
}
}
internal fun assignFromWordVariable(target: AssignTarget, variable: IdentifierReference) {
val sourceName = asmgen.asmIdentifierName(variable)
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
when {
targetIdent!=null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
asmgen.out("""
lda $sourceName
ldy $sourceName+1
sta $targetName
sty $targetName+1
""")
}
target.memoryAddress!=null -> {
TODO("assign wordvar $sourceName to memory ${target.memoryAddress}")
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
asmgen.out(" lda $sourceName | sta ${MachineDefinition.ESTACK_LO_HEX},x | lda $sourceName+1 | sta ${MachineDefinition.ESTACK_HI_HEX},x | dex")
asmgen.translateExpression(index)
asmgen.out(" inx | lda ${MachineDefinition.ESTACK_LO_HEX},x")
val arrayDt = targetArrayIdx.identifier.inferType(program).typeOrElse(DataType.STRUCT)
popAndWriteArrayvalueWithIndexA(arrayDt, targetName)
}
else -> TODO("assign wordvar to $target")
}
}
internal fun assignFromFloatVariable(target: AssignTarget, variable: IdentifierReference) {
val sourceName = asmgen.asmIdentifierName(variable)
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
when {
targetIdent!=null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
asmgen.out("""
lda $sourceName
sta $targetName
lda $sourceName+1
sta $targetName+1
lda $sourceName+2
sta $targetName+2
lda $sourceName+3
sta $targetName+3
lda $sourceName+4
sta $targetName+4
""")
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
asmgen.out(" lda #<$sourceName | ldy #>$sourceName | jsr c64flt.push_float")
asmgen.translateExpression(index)
asmgen.out(" lda #<$targetName | ldy #>$targetName | jsr c64flt.pop_float_to_indexed_var")
}
else -> TODO("assign floatvar to $target")
}
}
internal fun assignFromByteVariable(target: AssignTarget, variable: IdentifierReference) {
val sourceName = asmgen.asmIdentifierName(variable)
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
when {
target.register!=null -> {
asmgen.out(" ld${target.register.name.toLowerCase()} $sourceName")
}
targetIdent!=null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
asmgen.out("""
lda $sourceName
sta $targetName
""")
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
val arrayDt = targetArrayIdx.identifier.inferType(program).typeOrElse(DataType.STRUCT)
asmgen.out(" lda $sourceName | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
asmgen.translateExpression(index)
asmgen.out(" inx | lda ${MachineDefinition.ESTACK_LO_HEX},x")
popAndWriteArrayvalueWithIndexA(arrayDt, targetName)
}
target.memoryAddress != null -> {
val addressExpr = target.memoryAddress.addressExpression
val addressLv = addressExpr as? NumericLiteralValue
when {
addressLv != null -> asmgen.out(" lda $sourceName | sta ${addressLv.number.toHex()}")
addressExpr is IdentifierReference -> {
val targetName = asmgen.asmIdentifierName(addressExpr)
asmgen.out(" lda $sourceName | sta $targetName")
}
else -> {
asmgen.translateExpression(addressExpr)
asmgen.out("""
inx
lda ${MachineDefinition.ESTACK_LO_HEX},x
ldy ${MachineDefinition.ESTACK_HI_HEX},x
sta (+) +1
sty (+) +2
lda $sourceName
+ sta ${65535.toHex()} ; modified
""")
}
}
}
else -> TODO("assign bytevar to $target")
}
}
internal fun assignFromRegister(target: AssignTarget, register: Register) {
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
when {
targetIdent!=null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
asmgen.out(" st${register.name.toLowerCase()} $targetName")
}
target.register!=null -> {
when(register) {
Register.A -> when(target.register) {
Register.A -> {}
Register.X -> asmgen.out(" tax")
Register.Y -> asmgen.out(" tay")
}
Register.X -> when(target.register) {
Register.A -> asmgen.out(" txa")
Register.X -> {}
Register.Y -> asmgen.out(" txy")
}
Register.Y -> when(target.register) {
Register.A -> asmgen.out(" tya")
Register.X -> asmgen.out(" tyx")
Register.Y -> {}
}
}
}
target.memoryAddress!=null -> {
storeRegisterInMemoryAddress(register, target.memoryAddress)
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
when (index) {
is NumericLiteralValue -> {
val memindex = index.number.toInt()
when(register) {
Register.A -> asmgen.out(" sta $targetName+$memindex")
Register.X -> asmgen.out(" stx $targetName+$memindex")
Register.Y -> asmgen.out(" sty $targetName+$memindex")
}
}
is RegisterExpr -> {
when(register) {
Register.A -> asmgen.out(" sta ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
Register.X -> asmgen.out(" stx ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
Register.Y -> asmgen.out(" sty ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
}
when(index.register) {
Register.A -> {}
Register.X -> asmgen.out(" txa")
Register.Y -> asmgen.out(" tya")
}
asmgen.out("""
tay
lda ${MachineDefinition.C64Zeropage.SCRATCH_B1}
sta $targetName,y
""")
}
is IdentifierReference -> {
when(register) {
Register.A -> asmgen.out(" sta ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
Register.X -> asmgen.out(" stx ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
Register.Y -> asmgen.out(" sty ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
}
asmgen.out("""
lda ${asmgen.asmIdentifierName(index)}
tay
lda ${MachineDefinition.C64Zeropage.SCRATCH_B1}
sta $targetName,y
""")
}
else -> {
asmgen.saveRegister(register)
asmgen.translateExpression(index)
asmgen.restoreRegister(register)
when(register) {
Register.A -> asmgen.out(" sta ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
Register.X -> asmgen.out(" stx ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
Register.Y -> asmgen.out(" sty ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
}
asmgen.out("""
inx
lda ${MachineDefinition.ESTACK_LO_HEX},x
tay
lda ${MachineDefinition.C64Zeropage.SCRATCH_B1}
sta $targetName,y
""")
}
}
}
else -> TODO("assign register $register to $target")
}
}
private fun storeRegisterInMemoryAddress(register: Register, memoryAddress: DirectMemoryWrite) {
val addressExpr = memoryAddress.addressExpression
val addressLv = addressExpr as? NumericLiteralValue
val registerName = register.name.toLowerCase()
when {
addressLv != null -> asmgen.out(" st$registerName ${addressLv.number.toHex()}")
addressExpr is IdentifierReference -> {
val targetName = asmgen.asmIdentifierName(addressExpr)
when(register) {
Register.A -> asmgen.out("""
ldy $targetName
sty ${MachineDefinition.C64Zeropage.SCRATCH_W1}
ldy $targetName+1
sty ${MachineDefinition.C64Zeropage.SCRATCH_W1+1}
ldy #0
sta (${MachineDefinition.C64Zeropage.SCRATCH_W1}),y
""")
Register.X -> asmgen.out("""
txa
ldy $targetName
sty ${MachineDefinition.C64Zeropage.SCRATCH_W1}
ldy $targetName+1
sty ${MachineDefinition.C64Zeropage.SCRATCH_W1+1}
ldy #0
sta (${MachineDefinition.C64Zeropage.SCRATCH_W1}),y
""")
Register.Y -> asmgen.out("""
tya
ldy $targetName
sty ${MachineDefinition.C64Zeropage.SCRATCH_W1}
ldy $targetName+1
sty ${MachineDefinition.C64Zeropage.SCRATCH_W1+1}
ldy #0
sta (${MachineDefinition.C64Zeropage.SCRATCH_W1}),y
""")
}
}
else -> {
asmgen.saveRegister(register)
asmgen.translateExpression(addressExpr)
asmgen.restoreRegister(register)
when (register) {
Register.A -> asmgen.out(" tay")
Register.X -> throw AssemblyError("can't use X register here")
Register.Y -> {}
}
asmgen.out("""
inx
lda ${MachineDefinition.ESTACK_LO_HEX},x
sta (+) +1
lda ${MachineDefinition.ESTACK_HI_HEX},x
sta (+) +2
+ sty ${65535.toHex()} ; modified
""")
}
}
}
internal fun assignFromWordConstant(target: AssignTarget, word: Int) {
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
when {
targetIdent!=null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
if(word ushr 8 == word and 255) {
// lsb=msb
asmgen.out("""
lda #${(word and 255).toHex()}
sta $targetName
sta $targetName+1
""")
} else {
asmgen.out("""
lda #<${word.toHex()}
ldy #>${word.toHex()}
sta $targetName
sty $targetName+1
""")
}
}
target.memoryAddress!=null -> {
TODO("assign word $word to memory ${target.memoryAddress}")
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
// TODO optimize common cases
asmgen.translateExpression(index)
asmgen.out("""
inx
lda ${MachineDefinition.ESTACK_LO_HEX},x
asl a
tay
lda #<${word.toHex()}
sta $targetName,y
lda #>${word.toHex()}
sta $targetName+1,y
""")
}
else -> TODO("assign word $word to $target")
}
}
internal fun assignFromByteConstant(target: AssignTarget, byte: Short) {
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
when {
target.register!=null -> {
asmgen.out(" ld${target.register.name.toLowerCase()} #${byte.toHex()}")
}
targetIdent!=null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
asmgen.out(" lda #${byte.toHex()} | sta $targetName ")
}
target.memoryAddress!=null -> {
asmgen.out(" ldy #${byte.toHex()}")
storeRegisterInMemoryAddress(Register.Y, target.memoryAddress)
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
// TODO optimize common cases
asmgen.translateExpression(index)
asmgen.out("""
inx
ldy ${MachineDefinition.ESTACK_LO_HEX},x
lda #${byte.toHex()}
sta $targetName,y
""")
}
else -> TODO("assign byte $byte to $target")
}
}
internal fun assignFromFloatConstant(target: AssignTarget, float: Double) {
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
if(float==0.0) {
// optimized case for float zero
when {
targetIdent != null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
asmgen.out("""
lda #0
sta $targetName
sta $targetName+1
sta $targetName+2
sta $targetName+3
sta $targetName+4
""")
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
if(index is NumericLiteralValue) {
val indexValue = index.number.toInt() * MachineDefinition.Mflpt5.MemorySize
asmgen.out("""
lda #0
sta $targetName+$indexValue
sta $targetName+$indexValue+1
sta $targetName+$indexValue+2
sta $targetName+$indexValue+3
sta $targetName+$indexValue+4
""")
} else {
asmgen.translateExpression(index)
asmgen.out("""
inx
lda ${MachineDefinition.ESTACK_LO_HEX},x
asl a
asl a
clc
adc ${MachineDefinition.ESTACK_LO_HEX},x
tay
lda #0
sta $targetName,y
sta $targetName+1,y
sta $targetName+2,y
sta $targetName+3,y
sta $targetName+4,y
""") // TODO use a subroutine for this
}
}
else -> TODO("assign float 0.0 to $target")
}
} else {
// non-zero value
val constFloat = asmgen.getFloatConst(float)
when {
targetIdent != null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
asmgen.out("""
lda $constFloat
sta $targetName
lda $constFloat+1
sta $targetName+1
lda $constFloat+2
sta $targetName+2
lda $constFloat+3
sta $targetName+3
lda $constFloat+4
sta $targetName+4
""")
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val arrayVarName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
if(index is NumericLiteralValue) {
val indexValue = index.number.toInt() * MachineDefinition.Mflpt5.MemorySize
asmgen.out("""
lda $constFloat
sta $arrayVarName+$indexValue
lda $constFloat+1
sta $arrayVarName+$indexValue+1
lda $constFloat+2
sta $arrayVarName+$indexValue+2
lda $constFloat+3
sta $arrayVarName+$indexValue+3
lda $constFloat+4
sta $arrayVarName+$indexValue+4
""")
} else {
asmgen.translateArrayIndexIntoA(targetArrayIdx)
asmgen.out("""
sta ${MachineDefinition.C64Zeropage.SCRATCH_REG}
asl a
asl a
clc
adc ${MachineDefinition.C64Zeropage.SCRATCH_REG}
tay
lda $constFloat
sta $arrayVarName,y
lda $constFloat+1
sta $arrayVarName+1,y
lda $constFloat+2
sta $arrayVarName+2,y
lda $constFloat+3
sta $arrayVarName+3,y
lda $constFloat+4
sta $arrayVarName+4,y
""") // TODO use a subroutine for this
}
}
else -> TODO("assign float $float to $target")
}
}
}
internal fun assignFromMemoryByte(target: AssignTarget, address: Int?, identifier: IdentifierReference?) {
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
if(address!=null) {
when {
target.register!=null -> {
asmgen.out(" ld${target.register.name.toLowerCase()} ${address.toHex()}")
}
targetIdent!=null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
asmgen.out("""
lda ${address.toHex()}
sta $targetName
""")
}
target.memoryAddress!=null -> {
asmgen.out(" ldy ${address.toHex()}")
storeRegisterInMemoryAddress(Register.Y, target.memoryAddress)
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
TODO("assign memory byte at $address to array $targetName [ $index ]")
}
else -> TODO("assign memory byte $target")
}
}
else if(identifier!=null) {
val sourceName = asmgen.asmIdentifierName(identifier)
when {
target.register!=null -> {
asmgen.out("""
ldy #0
lda ($sourceName),y
""")
when(target.register){
Register.A -> {}
Register.X -> asmgen.out(" tax")
Register.Y -> asmgen.out(" tay")
}
}
targetIdent!=null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
asmgen.out("""
ldy #0
lda ($sourceName),y
sta $targetName
""")
}
target.memoryAddress!=null -> {
asmgen.out(" ldy $sourceName")
storeRegisterInMemoryAddress(Register.Y, target.memoryAddress)
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
TODO("assign memory byte $sourceName to array $targetName [ $index ]")
}
else -> TODO("assign memory byte $target")
}
}
}
private fun popAndWriteArrayvalueWithIndexA(arrayDt: DataType, variablename: String) {
when (arrayDt) {
DataType.STR, DataType.STR_S, DataType.ARRAY_UB, DataType.ARRAY_B ->
asmgen.out(" tay | inx | lda ${MachineDefinition.ESTACK_LO_HEX},x | sta $variablename,y")
DataType.ARRAY_UW, DataType.ARRAY_W ->
asmgen.out(" asl a | tay | inx | lda ${MachineDefinition.ESTACK_LO_HEX},x | sta $variablename,y | lda ${MachineDefinition.ESTACK_HI_HEX},x | sta $variablename+1,y")
DataType.ARRAY_F ->
// index * 5 is done in the subroutine that's called
asmgen.out("""
sta ${MachineDefinition.ESTACK_LO_HEX},x
dex
lda #<$variablename
ldy #>$variablename
jsr c64flt.pop_float_to_indexed_var
""")
else ->
throw AssemblyError("weird array type")
}
}
}

View File

@ -1,4 +1,4 @@
package prog8.compiler.target.c64.codegen2 package prog8.compiler.target.c64.codegen
import prog8.ast.IFunctionCall import prog8.ast.IFunctionCall
import prog8.ast.Program import prog8.ast.Program
@ -9,8 +9,6 @@ import prog8.ast.base.WordDatatypes
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget import prog8.ast.statements.AssignTarget
import prog8.ast.statements.FunctionCallStatement import prog8.ast.statements.FunctionCallStatement
import prog8.compiler.CompilationOptions
import prog8.compiler.Zeropage
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_HEX 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_HI_PLUS1_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX
@ -18,10 +16,7 @@ import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS1_HEX
import prog8.compiler.toHex import prog8.compiler.toHex
import prog8.functions.FunctionSignature import prog8.functions.FunctionSignature
internal class BuiltinFunctionsAsmGen(private val program: Program, internal class BuiltinFunctionsAsmGen(private val program: Program, private val asmgen: AsmGen) {
private val options: CompilationOptions,
private val zeropage: Zeropage,
private val asmgen: AsmGen2) {
internal fun translateFunctioncallExpression(fcall: FunctionCall, func: FunctionSignature) { internal fun translateFunctioncallExpression(fcall: FunctionCall, func: FunctionSignature) {
translateFunctioncall(fcall, func, false) translateFunctioncall(fcall, func, false)
@ -33,21 +28,21 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
private fun translateFunctioncall(fcall: IFunctionCall, func: FunctionSignature, discardResult: Boolean) { private fun translateFunctioncall(fcall: IFunctionCall, func: FunctionSignature, discardResult: Boolean) {
val functionName = fcall.target.nameInSource.last() val functionName = fcall.target.nameInSource.last()
if(discardResult) { if (discardResult) {
if(func.pure) if (func.pure)
return // can just ignore the whole function call altogether return // can just ignore the whole function call altogether
else if(func.returntype!=null) else if (func.returntype != null)
throw AssemblyError("discarding result of non-pure function $fcall") throw AssemblyError("discarding result of non-pure function $fcall")
} }
when(functionName) { when (functionName) {
"msb" -> { "msb" -> {
val arg = fcall.arglist.single() val arg = fcall.arglist.single()
if(arg.inferType(program) !in WordDatatypes) if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes)
throw AssemblyError("msb required word argument") throw AssemblyError("msb required word argument")
if(arg is NumericLiteralValue) if (arg is NumericLiteralValue)
throw AssemblyError("should have been const-folded") throw AssemblyError("should have been const-folded")
if(arg is IdentifierReference) { if (arg is IdentifierReference) {
val sourceName = asmgen.asmIdentifierName(arg) val sourceName = asmgen.asmIdentifierName(arg)
asmgen.out(" lda $sourceName+1 | sta $ESTACK_LO_HEX,x | dex") asmgen.out(" lda $sourceName+1 | sta $ESTACK_LO_HEX,x | dex")
} else { } else {
@ -61,8 +56,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
} }
"abs" -> { "abs" -> {
translateFunctionArguments(fcall.arglist, func) translateFunctionArguments(fcall.arglist, func)
val dt = fcall.arglist.single().inferType(program)!! val dt = fcall.arglist.single().inferType(program)
when (dt) { when (dt.typeOrElse(DataType.STRUCT)) {
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.abs_b") in ByteDatatypes -> asmgen.out(" jsr prog8_lib.abs_b")
in WordDatatypes -> asmgen.out(" jsr prog8_lib.abs_w") in WordDatatypes -> asmgen.out(" jsr prog8_lib.abs_w")
DataType.FLOAT -> asmgen.out(" jsr c64flt.abs_f") DataType.FLOAT -> asmgen.out(" jsr c64flt.abs_f")
@ -86,8 +81,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
} }
"min", "max", "sum" -> { "min", "max", "sum" -> {
outputPushAddressAndLenghtOfArray(fcall.arglist[0]) outputPushAddressAndLenghtOfArray(fcall.arglist[0])
val dt = fcall.arglist.single().inferType(program)!! val dt = fcall.arglist.single().inferType(program)
when(dt) { when (dt.typeOrElse(DataType.STRUCT)) {
DataType.ARRAY_UB, DataType.STR_S, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${functionName}_ub") DataType.ARRAY_UB, DataType.STR_S, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${functionName}_ub")
DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_${functionName}_b") DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_${functionName}_b")
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_${functionName}_uw") DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_${functionName}_uw")
@ -98,14 +93,26 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
} }
"any", "all" -> { "any", "all" -> {
outputPushAddressAndLenghtOfArray(fcall.arglist[0]) outputPushAddressAndLenghtOfArray(fcall.arglist[0])
val dt = fcall.arglist.single().inferType(program)!! val dt = fcall.arglist.single().inferType(program)
when(dt) { when (dt.typeOrElse(DataType.STRUCT)) {
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR_S, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${functionName}_b") DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR_S, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${functionName}_b")
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${functionName}_w") DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${functionName}_w")
DataType.ARRAY_F -> asmgen.out(" jsr c64flt.func_${functionName}_f") DataType.ARRAY_F -> asmgen.out(" jsr c64flt.func_${functionName}_f")
else -> throw AssemblyError("weird type $dt") else -> throw AssemblyError("weird type $dt")
} }
} }
"sgn" -> {
translateFunctionArguments(fcall.arglist, func)
val dt = fcall.arglist.single().inferType(program)
when(dt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> asmgen.out(" jsr math.sign_ub")
DataType.BYTE -> asmgen.out(" jsr math.sign_b")
DataType.UWORD -> asmgen.out(" jsr math.sign_uw")
DataType.WORD -> asmgen.out(" jsr math.sign_w")
DataType.FLOAT -> asmgen.out(" jsr c64flt.sign_f")
else -> throw AssemblyError("weird type $dt")
}
}
"sin", "cos", "tan", "atan", "sin", "cos", "tan", "atan",
"ln", "log2", "sqrt", "rad", "ln", "log2", "sqrt", "rad",
"deg", "round", "floor", "ceil", "deg", "round", "floor", "ceil",
@ -134,12 +141,12 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
"lsl" -> { "lsl" -> {
// in-place // in-place
val what = fcall.arglist.single() val what = fcall.arglist.single()
val dt = what.inferType(program)!! val dt = what.inferType(program)
when(dt) { when (dt.typeOrElse(DataType.STRUCT)) {
in ByteDatatypes -> { in ByteDatatypes -> {
when(what) { when (what) {
is RegisterExpr -> { is RegisterExpr -> {
when(what.register) { when (what.register) {
Register.A -> asmgen.out(" asl a") Register.A -> asmgen.out(" asl a")
Register.X -> asmgen.out(" txa | asl a | tax") Register.X -> asmgen.out(" txa | asl a | tax")
Register.Y -> asmgen.out(" tya | asl a | tay") Register.Y -> asmgen.out(" tya | asl a | tay")
@ -147,7 +154,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
} }
is IdentifierReference -> asmgen.out(" asl ${asmgen.asmIdentifierName(what)}") is IdentifierReference -> asmgen.out(" asl ${asmgen.asmIdentifierName(what)}")
is DirectMemoryRead -> { is DirectMemoryRead -> {
if(what.addressExpression is NumericLiteralValue) { if (what.addressExpression is NumericLiteralValue) {
asmgen.out(" asl ${(what.addressExpression as NumericLiteralValue).number.toHex()}") asmgen.out(" asl ${(what.addressExpression as NumericLiteralValue).number.toHex()}")
} else { } else {
TODO("lsl memory byte $what") TODO("lsl memory byte $what")
@ -160,7 +167,14 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
} }
} }
in WordDatatypes -> { in WordDatatypes -> {
TODO("lsl word $what") when (what) {
is ArrayIndexedExpression -> TODO("lsl sbyte $what")
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" asl $variable | rol $variable+1")
}
else -> throw AssemblyError("weird type")
}
} }
else -> throw AssemblyError("weird type") else -> throw AssemblyError("weird type")
} }
@ -168,12 +182,12 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
"lsr" -> { "lsr" -> {
// in-place // in-place
val what = fcall.arglist.single() val what = fcall.arglist.single()
val dt = what.inferType(program)!! val dt = what.inferType(program)
when(dt) { when (dt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> { DataType.UBYTE -> {
when(what) { when (what) {
is RegisterExpr -> { is RegisterExpr -> {
when(what.register) { when (what.register) {
Register.A -> asmgen.out(" lsr a") Register.A -> asmgen.out(" lsr a")
Register.X -> asmgen.out(" txa | lsr a | tax") Register.X -> asmgen.out(" txa | lsr a | tax")
Register.Y -> asmgen.out(" tya | lsr a | tay") Register.Y -> asmgen.out(" tya | lsr a | tay")
@ -181,7 +195,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
} }
is IdentifierReference -> asmgen.out(" lsr ${asmgen.asmIdentifierName(what)}") is IdentifierReference -> asmgen.out(" lsr ${asmgen.asmIdentifierName(what)}")
is DirectMemoryRead -> { is DirectMemoryRead -> {
if(what.addressExpression is NumericLiteralValue) { if (what.addressExpression is NumericLiteralValue) {
asmgen.out(" lsr ${(what.addressExpression as NumericLiteralValue).number.toHex()}") asmgen.out(" lsr ${(what.addressExpression as NumericLiteralValue).number.toHex()}")
} else { } else {
TODO("lsr memory byte $what") TODO("lsr memory byte $what")
@ -194,13 +208,36 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
} }
} }
DataType.BYTE -> { DataType.BYTE -> {
TODO("lsr sbyte $what") when (what) {
is ArrayIndexedExpression -> TODO("lsr sbyte $what")
is DirectMemoryRead -> TODO("lsr sbyte $what")
is RegisterExpr -> TODO("lsr sbyte $what")
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" lda $variable | asl a | ror $variable")
}
else -> throw AssemblyError("weird type")
}
} }
DataType.UWORD -> { DataType.UWORD -> {
TODO("lsr sword $what") when (what) {
is ArrayIndexedExpression -> TODO("lsr uword $what")
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" lsr $variable+1 | ror $variable")
}
else -> throw AssemblyError("weird type")
}
} }
DataType.WORD -> { DataType.WORD -> {
TODO("lsr word $what") when (what) {
is ArrayIndexedExpression -> TODO("lsr sword $what")
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" lda $variable+1 | asl a | ror $variable+1 | ror $variable")
}
else -> throw AssemblyError("weird type")
}
} }
else -> throw AssemblyError("weird type") else -> throw AssemblyError("weird type")
} }
@ -208,8 +245,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
"rol" -> { "rol" -> {
// in-place // in-place
val what = fcall.arglist.single() val what = fcall.arglist.single()
val dt = what.inferType(program)!! val dt = what.inferType(program)
when(dt) { when (dt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> { DataType.UBYTE -> {
TODO("rol ubyte") TODO("rol ubyte")
} }
@ -222,8 +259,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
"rol2" -> { "rol2" -> {
// in-place // in-place
val what = fcall.arglist.single() val what = fcall.arglist.single()
val dt = what.inferType(program)!! val dt = what.inferType(program)
when(dt) { when (dt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> { DataType.UBYTE -> {
TODO("rol2 ubyte") TODO("rol2 ubyte")
} }
@ -236,8 +273,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
"ror" -> { "ror" -> {
// in-place // in-place
val what = fcall.arglist.single() val what = fcall.arglist.single()
val dt = what.inferType(program)!! val dt = what.inferType(program)
when(dt) { when (dt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> { DataType.UBYTE -> {
TODO("ror ubyte") TODO("ror ubyte")
} }
@ -250,8 +287,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
"ror2" -> { "ror2" -> {
// in-place // in-place
val what = fcall.arglist.single() val what = fcall.arglist.single()
val dt = what.inferType(program)!! val dt = what.inferType(program)
when(dt) { when (dt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> { DataType.UBYTE -> {
TODO("ror2 ubyte") TODO("ror2 ubyte")
} }
@ -302,4 +339,3 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
} }
} }

View File

@ -0,0 +1,430 @@
package prog8.compiler.target.c64.codegen
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.toHex
import prog8.functions.BuiltinFunctions
import kotlin.math.absoluteValue
internal class ExpressionsAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translateExpression(expression: Expression) {
when(expression) {
is PrefixExpression -> translateExpression(expression)
is BinaryExpression -> translateExpression(expression)
is ArrayIndexedExpression -> translatePushFromArray(expression)
is TypecastExpression -> translateExpression(expression)
is AddressOf -> translateExpression(expression)
is DirectMemoryRead -> translateExpression(expression)
is NumericLiteralValue -> translateExpression(expression)
is RegisterExpr -> translateExpression(expression)
is IdentifierReference -> translateExpression(expression)
is FunctionCall -> {
val functionName = expression.target.nameInSource.last()
val builtinFunc = BuiltinFunctions[functionName]
if(builtinFunc!=null) {
asmgen.translateFunctioncallExpression(expression, builtinFunc)
} else {
asmgen.translateFunctionCall(expression)
val sub = expression.target.targetSubroutine(program.namespace)!!
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
for((_, reg) in returns) {
if(!reg.stack) {
// result value in cpu or status registers, put it on the stack
if(reg.registerOrPair!=null) {
when(reg.registerOrPair) {
RegisterOrPair.A -> asmgen.out(" sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
RegisterOrPair.Y -> asmgen.out(" tya | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
RegisterOrPair.AY -> asmgen.out(" sta ${MachineDefinition.ESTACK_LO_HEX},x | tya | sta ${MachineDefinition.ESTACK_HI_HEX},x | dex")
RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY -> throw AssemblyError("can't push X register - use a variable")
}
}
// return value from a statusregister is not put on the stack, it should be acted on via a conditional branch such as if_cc
}
}
}
}
is ArrayLiteralValue, is StringLiteralValue -> TODO("string/array/struct assignment?")
is StructLiteralValue -> throw AssemblyError("struct literal value assignment should have been flattened")
is RangeExpr -> throw AssemblyError("range expression should have been changed into array values")
}
}
private fun translateExpression(expr: TypecastExpression) {
translateExpression(expr.expression)
when(expr.expression.inferType(program).typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> {
when(expr.type) {
DataType.UBYTE, DataType.BYTE -> {}
DataType.UWORD, DataType.WORD -> asmgen.out(" lda #0 | sta ${MachineDefinition.ESTACK_HI_PLUS1_HEX},x")
DataType.FLOAT -> asmgen.out(" jsr c64flt.stack_ub2float")
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
else -> throw AssemblyError("weird type")
}
}
DataType.BYTE -> {
when(expr.type) {
DataType.UBYTE, DataType.BYTE -> {}
DataType.UWORD, DataType.WORD -> asmgen.out(" lda ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x | ${asmgen.signExtendAtoMsb("${MachineDefinition.ESTACK_HI_PLUS1_HEX},x")}")
DataType.FLOAT -> asmgen.out(" jsr c64flt.stack_b2float")
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
else -> throw AssemblyError("weird type")
}
}
DataType.UWORD -> {
when(expr.type) {
DataType.BYTE, DataType.UBYTE -> {}
DataType.WORD, DataType.UWORD -> {}
DataType.FLOAT -> asmgen.out(" jsr c64flt.stack_uw2float")
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
else -> throw AssemblyError("weird type")
}
}
DataType.WORD -> {
when(expr.type) {
DataType.BYTE, DataType.UBYTE -> {}
DataType.WORD, DataType.UWORD -> {}
DataType.FLOAT -> asmgen.out(" jsr c64flt.stack_w2float")
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
else -> throw AssemblyError("weird type")
}
}
DataType.FLOAT -> {
when(expr.type) {
DataType.UBYTE -> asmgen.out(" jsr c64flt.stack_float2uw")
DataType.BYTE -> asmgen.out(" jsr c64flt.stack_float2w")
DataType.UWORD -> asmgen.out(" jsr c64flt.stack_float2uw")
DataType.WORD -> asmgen.out(" jsr c64flt.stack_float2w")
DataType.FLOAT -> {}
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
else -> throw AssemblyError("weird type")
}
}
in PassByReferenceDatatypes -> throw AssemblyError("cannot case a pass-by-reference datatypes into something else")
else -> throw AssemblyError("weird type")
}
}
private fun translateExpression(expr: AddressOf) {
val name = asmgen.asmIdentifierName(expr.identifier)
asmgen.out(" lda #<$name | sta ${MachineDefinition.ESTACK_LO_HEX},x | lda #>$name | sta ${MachineDefinition.ESTACK_HI_HEX},x | dex")
}
private fun translateExpression(expr: DirectMemoryRead) {
when(expr.addressExpression) {
is NumericLiteralValue -> {
val address = (expr.addressExpression as NumericLiteralValue).number.toInt()
asmgen.out(" lda ${address.toHex()} | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
}
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(expr.addressExpression as IdentifierReference)
asmgen.out(" lda $sourceName | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
}
else -> {
translateExpression(expr.addressExpression)
asmgen.out(" jsr prog8_lib.read_byte_from_address")
asmgen.out(" sta ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x")
}
}
}
private fun translateExpression(expr: NumericLiteralValue) {
when(expr.type) {
DataType.UBYTE, DataType.BYTE -> asmgen.out(" lda #${expr.number.toHex()} | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
DataType.UWORD, DataType.WORD -> asmgen.out("""
lda #<${expr.number.toHex()}
sta ${MachineDefinition.ESTACK_LO_HEX},x
lda #>${expr.number.toHex()}
sta ${MachineDefinition.ESTACK_HI_HEX},x
dex
""")
DataType.FLOAT -> {
val floatConst = asmgen.getFloatConst(expr.number.toDouble())
asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr c64flt.push_float")
}
else -> throw AssemblyError("weird type")
}
}
private fun translateExpression(expr: RegisterExpr) {
when(expr.register) {
Register.A -> asmgen.out(" sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
Register.X -> throw AssemblyError("cannot push X - use a variable instead of the X register")
Register.Y -> asmgen.out(" tya | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
}
}
private fun translateExpression(expr: IdentifierReference) {
val varname = asmgen.asmIdentifierName(expr)
when(expr.inferType(program).typeOrElse(DataType.STRUCT)) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.out(" lda $varname | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
}
DataType.UWORD, DataType.WORD, in ArrayDatatypes, in StringDatatypes -> {
// (for arrays and strings, push their address)
asmgen.out(" lda $varname | sta ${MachineDefinition.ESTACK_LO_HEX},x | lda $varname+1 | sta ${MachineDefinition.ESTACK_HI_HEX},x | dex")
}
DataType.FLOAT -> {
asmgen.out(" lda #<$varname | ldy #>$varname| jsr c64flt.push_float")
}
else -> throw AssemblyError("stack push weird variable type $expr")
}
}
private val optimizedByteMultiplications = setOf(3,5,6,7,9,10,11,12,13,14,15,20,25,40)
private val optimizedWordMultiplications = setOf(3,5,6,7,9,10,12,15,20,25,40)
private val powerOfTwos = setOf(0,1,2,4,8,16,32,64,128,256)
private fun translateExpression(expr: BinaryExpression) {
val leftIDt = expr.left.inferType(program)
val rightIDt = expr.right.inferType(program)
if(!leftIDt.isKnown || !rightIDt.isKnown)
throw AssemblyError("can't infer type of both expression operands")
val leftDt = leftIDt.typeOrElse(DataType.STRUCT)
val rightDt = rightIDt.typeOrElse(DataType.STRUCT)
// see if we can apply some optimized routines
when(expr.operator) {
">>" -> {
// bit-shifts are always by a constant number (for now)
translateExpression(expr.left)
val amount = expr.right.constValue(program)!!.number.toInt()
when (leftDt) {
DataType.UBYTE -> repeat(amount) { asmgen.out(" lsr ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x") }
DataType.BYTE -> repeat(amount) { asmgen.out(" lda ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x | asl a | ror ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x") }
DataType.UWORD -> repeat(amount) { asmgen.out(" lsr ${MachineDefinition.ESTACK_HI_PLUS1_HEX},x | ror ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x") }
DataType.WORD -> repeat(amount) { asmgen.out(" lda ${MachineDefinition.ESTACK_HI_PLUS1_HEX},x | asl a | ror ${MachineDefinition.ESTACK_HI_PLUS1_HEX},x | ror ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x") }
else -> throw AssemblyError("weird type")
}
return
}
"<<" -> {
// bit-shifts are always by a constant number (for now)
translateExpression(expr.left)
val amount = expr.right.constValue(program)!!.number.toInt()
if (leftDt in ByteDatatypes)
repeat(amount) { asmgen.out(" asl ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x") }
else
repeat(amount) { asmgen.out(" asl ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x | rol ${MachineDefinition.ESTACK_HI_PLUS1_HEX},x") }
return
}
"*" -> {
val value = expr.right.constValue(program)
if(value!=null) {
if(rightDt in IntegerDatatypes) {
val amount = value.number.toInt()
if(amount in powerOfTwos)
printWarning("${expr.right.position} multiplication by power of 2 should have been optimized into a left shift instruction: $amount")
when(rightDt) {
DataType.UBYTE -> {
if(amount in optimizedByteMultiplications) {
translateExpression(expr.left)
asmgen.out(" jsr math.mul_byte_$amount")
return
}
}
DataType.BYTE -> {
if(amount in optimizedByteMultiplications) {
translateExpression(expr.left)
asmgen.out(" jsr math.mul_byte_$amount")
return
}
if(amount.absoluteValue in optimizedByteMultiplications) {
translateExpression(expr.left)
asmgen.out(" jsr prog8_lib.neg_b | jsr math.mul_byte_${amount.absoluteValue}")
return
}
}
DataType.UWORD -> {
if(amount in optimizedWordMultiplications) {
translateExpression(expr.left)
asmgen.out(" jsr math.mul_word_$amount")
return
}
}
DataType.WORD -> {
if(amount in optimizedWordMultiplications) {
translateExpression(expr.left)
asmgen.out(" jsr math.mul_word_$amount")
return
}
if(amount.absoluteValue in optimizedWordMultiplications) {
translateExpression(expr.left)
asmgen.out(" jsr prog8_lib.neg_w | jsr math.mul_word_${amount.absoluteValue}")
return
}
}
else -> {}
}
}
}
}
}
// the general, non-optimized cases
translateExpression(expr.left)
translateExpression(expr.right)
if(leftDt!=rightDt)
throw AssemblyError("binary operator ${expr.operator} left/right dt not identical") // is this strictly required always?
when (leftDt) {
in ByteDatatypes -> translateBinaryOperatorBytes(expr.operator, leftDt)
in WordDatatypes -> translateBinaryOperatorWords(expr.operator, leftDt)
DataType.FLOAT -> translateBinaryOperatorFloats(expr.operator)
else -> throw AssemblyError("non-numerical datatype")
}
}
private fun translateExpression(expr: PrefixExpression) {
translateExpression(expr.expression)
val type = expr.inferType(program).typeOrElse(DataType.STRUCT)
when(expr.operator) {
"+" -> {}
"-" -> {
when(type) {
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.neg_b")
in WordDatatypes -> asmgen.out(" jsr prog8_lib.neg_w")
DataType.FLOAT -> asmgen.out(" jsr c64flt.neg_f")
else -> throw AssemblyError("weird type")
}
}
"~" -> {
when(type) {
in ByteDatatypes ->
asmgen.out("""
lda ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x
eor #255
sta ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x
""")
in WordDatatypes -> asmgen.out(" jsr prog8_lib.inv_word")
else -> throw AssemblyError("weird type")
}
}
"not" -> {
when(type) {
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.not_byte")
in WordDatatypes -> asmgen.out(" jsr prog8_lib.not_word")
else -> throw AssemblyError("weird type")
}
}
else -> throw AssemblyError("invalid prefix operator ${expr.operator}")
}
}
private fun translatePushFromArray(arrayExpr: ArrayIndexedExpression) {
// assume *reading* from an array
val index = arrayExpr.arrayspec.index
val arrayDt = arrayExpr.identifier.targetVarDecl(program.namespace)!!.datatype
val arrayVarName = asmgen.asmIdentifierName(arrayExpr.identifier)
if(index is NumericLiteralValue) {
val elementDt = ArrayElementTypes.getValue(arrayDt)
val indexValue = index.number.toInt() * elementDt.memorySize()
when(elementDt) {
in ByteDatatypes -> {
asmgen.out(" lda $arrayVarName+$indexValue | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
}
in WordDatatypes -> {
asmgen.out(" lda $arrayVarName+$indexValue | sta ${MachineDefinition.ESTACK_LO_HEX},x | lda $arrayVarName+$indexValue+1 | sta ${MachineDefinition.ESTACK_HI_HEX},x | dex")
}
DataType.FLOAT -> {
asmgen.out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue | jsr c64flt.push_float")
}
else -> throw AssemblyError("weird type")
}
} else {
asmgen.translateArrayIndexIntoA(arrayExpr)
asmgen.readAndPushArrayvalueWithIndexA(arrayDt, arrayExpr.identifier)
}
}
private fun translateBinaryOperatorBytes(operator: String, types: DataType) {
when(operator) {
"**" -> throw AssemblyError("** operator requires floats")
"*" -> asmgen.out(" jsr prog8_lib.mul_byte") // the optimized routines should have been checked earlier
"/" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.idiv_ub" else " jsr prog8_lib.idiv_b")
"%" -> {
if(types==DataType.BYTE)
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
asmgen.out(" jsr prog8_lib.remainder_ub")
}
"+" -> asmgen.out("""
lda ${MachineDefinition.ESTACK_LO_PLUS2_HEX},x
clc
adc ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x
inx
sta ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x
""")
"-" -> asmgen.out("""
lda ${MachineDefinition.ESTACK_LO_PLUS2_HEX},x
sec
sbc ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x
inx
sta ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x
""")
"<<", ">>" -> throw AssemblyError("bit-shifts not via stack")
"<" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.less_ub" else " jsr prog8_lib.less_b")
">" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.greater_ub" else " jsr prog8_lib.greater_b")
"<=" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.lesseq_ub" else " jsr prog8_lib.lesseq_b")
">=" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.greatereq_ub" else " jsr prog8_lib.greatereq_b")
"==" -> asmgen.out(" jsr prog8_lib.equal_b")
"!=" -> asmgen.out(" jsr prog8_lib.notequal_b")
"&" -> asmgen.out(" jsr prog8_lib.bitand_b")
"^" -> asmgen.out(" jsr prog8_lib.bitxor_b")
"|" -> asmgen.out(" jsr prog8_lib.bitor_b")
"and" -> asmgen.out(" jsr prog8_lib.and_b")
"or" -> asmgen.out(" jsr prog8_lib.or_b")
"xor" -> asmgen.out(" jsr prog8_lib.xor_b")
else -> throw AssemblyError("invalid operator $operator")
}
}
private fun translateBinaryOperatorWords(operator: String, types: DataType) {
when(operator) {
"**" -> throw AssemblyError("** operator requires floats")
"*" -> asmgen.out(" jsr prog8_lib.mul_word")
"/" -> asmgen.out(if(types==DataType.UWORD) " jsr prog8_lib.idiv_uw" else " jsr prog8_lib.idiv_w")
"%" -> {
if(types==DataType.WORD)
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
asmgen.out(" jsr prog8_lib.remainder_uw")
}
"+" -> asmgen.out(" jsr prog8_lib.add_w")
"-" -> asmgen.out(" jsr prog8_lib.sub_w")
"<<" -> throw AssemblyError("<< should not operate via stack")
">>" -> throw AssemblyError(">> should not operate via stack")
"<" -> asmgen.out(if(types==DataType.UWORD) " jsr prog8_lib.less_uw" else " jsr prog8_lib.less_w")
">" -> asmgen.out(if(types==DataType.UWORD) " jsr prog8_lib.greater_uw" else " jsr prog8_lib.greater_w")
"<=" -> asmgen.out(if(types==DataType.UWORD) " jsr prog8_lib.lesseq_uw" else " jsr prog8_lib.lesseq_w")
">=" -> asmgen.out(if(types==DataType.UWORD) " jsr prog8_lib.greatereq_uw" else " jsr prog8_lib.greatereq_w")
"==" -> asmgen.out(" jsr prog8_lib.equal_w")
"!=" -> asmgen.out(" jsr prog8_lib.notequal_w")
"&" -> asmgen.out(" jsr prog8_lib.bitand_w")
"^" -> asmgen.out(" jsr prog8_lib.bitxor_w")
"|" -> asmgen.out(" jsr prog8_lib.bitor_w")
"and" -> asmgen.out(" jsr prog8_lib.and_w")
"or" -> asmgen.out(" jsr prog8_lib.or_w")
"xor" -> asmgen.out(" jsr prog8_lib.xor_w")
else -> throw AssemblyError("invalid operator $operator")
}
}
private fun translateBinaryOperatorFloats(operator: String) {
when(operator) {
"**" -> asmgen.out(" jsr c64flt.pow_f")
"*" -> asmgen.out(" jsr c64flt.mul_f")
"/" -> asmgen.out(" jsr c64flt.div_f")
"+" -> asmgen.out(" jsr c64flt.add_f")
"-" -> asmgen.out(" jsr c64flt.sub_f")
"<" -> asmgen.out(" jsr c64flt.less_f")
">" -> asmgen.out(" jsr c64flt.greater_f")
"<=" -> asmgen.out(" jsr c64flt.lesseq_f")
">=" -> asmgen.out(" jsr c64flt.greatereq_f")
"==" -> asmgen.out(" jsr c64flt.equal_f")
"!=" -> asmgen.out(" jsr c64flt.notequal_f")
"%", "<<", ">>", "&", "^", "|", "and", "or", "xor" -> throw AssemblyError("requires integer datatype")
else -> throw AssemblyError("invalid operator $operator")
}
}
}

View File

@ -0,0 +1,561 @@
package prog8.compiler.target.c64.codegen
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.Register
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.RangeExpr
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.statements.ForLoop
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.toHex
import kotlin.math.absoluteValue
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translate(stmt: ForLoop) {
val iterableDt = stmt.iterable.inferType(program)
if(!iterableDt.isKnown)
throw AssemblyError("can't determine iterable dt")
when(stmt.iterable) {
is RangeExpr -> {
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange()
if(range==null) {
translateForOverNonconstRange(stmt, iterableDt.typeOrElse(DataType.STRUCT), stmt.iterable as RangeExpr)
} else {
if (range.isEmpty())
throw AssemblyError("empty range")
translateForOverConstRange(stmt, iterableDt.typeOrElse(DataType.STRUCT), range)
}
}
is IdentifierReference -> {
translateForOverIterableVar(stmt, iterableDt.typeOrElse(DataType.STRUCT), stmt.iterable as IdentifierReference)
}
else -> throw AssemblyError("can't iterate over ${stmt.iterable}")
}
}
private fun translateForOverNonconstRange(stmt: ForLoop, iterableDt: DataType, range: RangeExpr) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
val continueLabel = asmgen.makeLabel("for_continue")
val counterLabel = asmgen.makeLabel("for_counter")
asmgen.loopEndLabels.push(endLabel)
asmgen.loopContinueLabels.push(continueLabel)
val stepsize=range.step.constValue(program)?.number
when (stepsize) {
1 -> {
when(iterableDt) {
DataType.ARRAY_B, DataType.ARRAY_UB -> {
if (stmt.loopRegister != null) {
// loop register over range
if(stmt.loopRegister!= Register.A)
throw AssemblyError("can only use A")
asmgen.translateExpression(range.to)
asmgen.translateExpression(range.from)
asmgen.out("""
inx
lda ${MachineDefinition.ESTACK_LO_HEX},x
sta $loopLabel+1
inx
lda ${MachineDefinition.ESTACK_LO_HEX},x
sec
sbc $loopLabel+1
adc #0
sta $counterLabel
$loopLabel lda #0 ; modified""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel dec $counterLabel
beq $endLabel
inc $loopLabel+1
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
} else {
// loop over byte range via loopvar
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
asmgen.translateExpression(range.to)
asmgen.translateExpression(range.from)
asmgen.out("""
inx
lda ${MachineDefinition.ESTACK_LO_HEX},x
sta $varname
inx
lda ${MachineDefinition.ESTACK_LO_HEX},x
sec
sbc $varname
adc #0
sta $counterLabel
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel dec $counterLabel
beq $endLabel
inc $varname
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
}
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
asmgen.translateExpression(range.to)
asmgen.out(" inc ${MachineDefinition.ESTACK_LO_HEX}+1,x | bne + | inc ${MachineDefinition.ESTACK_HI_HEX}+1,x |+ ")
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
val assignLoopvar = Assignment(AssignTarget(null, stmt.loopVar, null, null, stmt.loopVar!!.position),
null, range.from, range.position)
assignLoopvar.linkParents(stmt)
asmgen.translate(assignLoopvar)
asmgen.out(loopLabel)
asmgen.translate(stmt.body)
asmgen.out("""
inc $varname
bne +
inc $varname+1
+ lda ${MachineDefinition.ESTACK_HI_HEX}+1,x
cmp $varname+1
bne +
lda ${MachineDefinition.ESTACK_LO_HEX}+1,x
cmp $varname
beq $endLabel
+ jmp $loopLabel
$endLabel inx""")
}
else -> throw AssemblyError("range expression can only be byte or word")
}
}
-1 -> {
when(iterableDt){
DataType.ARRAY_B, DataType.ARRAY_UB -> {
if (stmt.loopRegister != null) {
// loop register over range
if(stmt.loopRegister!= Register.A)
throw AssemblyError("can only use A")
asmgen.translateExpression(range.from)
asmgen.translateExpression(range.to)
asmgen.out("""
inx
lda ${MachineDefinition.ESTACK_LO_HEX}+1,x
sta $loopLabel+1
sec
sbc ${MachineDefinition.ESTACK_LO_HEX},x
adc #0
sta $counterLabel
inx
$loopLabel lda #0 ; modified""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel dec $counterLabel
beq $endLabel
dec $loopLabel+1
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
} else {
// loop over byte range via loopvar
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
asmgen.translateExpression(range.from)
asmgen.translateExpression(range.to)
asmgen.out("""
inx
lda ${MachineDefinition.ESTACK_LO_HEX}+1,x
sta $varname
sec
sbc ${MachineDefinition.ESTACK_LO_HEX},x
adc #0
sta $counterLabel
inx
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel dec $counterLabel
beq $endLabel
dec $varname
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
}
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
asmgen.translateExpression(range.to)
asmgen.out("""
lda ${MachineDefinition.ESTACK_LO_HEX}+1,x
bne +
dec ${MachineDefinition.ESTACK_HI_HEX}+1,x
+ dec ${MachineDefinition.ESTACK_LO_HEX}+1,x
""")
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
val assignLoopvar = Assignment(AssignTarget(null, stmt.loopVar, null, null, stmt.loopVar!!.position),
null, range.from, range.position)
assignLoopvar.linkParents(stmt)
asmgen.translate(assignLoopvar)
asmgen.out(loopLabel)
asmgen.translate(stmt.body)
asmgen.out("""
lda $varname
bne +
dec $varname+1
+ dec $varname
lda ${MachineDefinition.ESTACK_HI_HEX}+1,x
cmp $varname+1
bne +
lda ${MachineDefinition.ESTACK_LO_HEX}+1,x
cmp $varname
beq $endLabel
+ jmp $loopLabel
$endLabel inx""")
}
else -> throw AssemblyError("range expression can only be byte or word")
}
}
else -> when (iterableDt) {
DataType.ARRAY_UB, DataType.ARRAY_B -> TODO("non-const forloop bytes, step >1: $stepsize")
DataType.ARRAY_UW, DataType.ARRAY_W -> TODO("non-const forloop words, step >1: $stepsize")
else -> throw AssemblyError("range expression can only be byte or word")
}
}
asmgen.loopEndLabels.pop()
asmgen.loopContinueLabels.pop()
}
private fun translateForOverIterableVar(stmt: ForLoop, iterableDt: DataType, ident: IdentifierReference) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
val continueLabel = asmgen.makeLabel("for_continue")
asmgen.loopEndLabels.push(endLabel)
asmgen.loopContinueLabels.push(continueLabel)
val iterableName = asmgen.asmIdentifierName(ident)
val decl = ident.targetVarDecl(program.namespace)!!
when(iterableDt) {
DataType.STR, DataType.STR_S -> {
if(stmt.loopRegister!=null && stmt.loopRegister!= Register.A)
throw AssemblyError("can only use A")
asmgen.out("""
lda #<$iterableName
ldy #>$iterableName
sta $loopLabel+1
sty $loopLabel+2
$loopLabel lda ${65535.toHex()} ; modified
beq $endLabel""")
if(stmt.loopVar!=null)
asmgen.out(" sta ${asmgen.asmIdentifierName(stmt.loopVar!!)}")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel inc $loopLabel+1
bne $loopLabel
inc $loopLabel+2
bne $loopLabel
$endLabel""")
}
DataType.ARRAY_UB, DataType.ARRAY_B -> {
// TODO: optimize loop code when the length of the array is < 256, don't need a separate counter in such cases
val length = decl.arraysize!!.size()!!
if(stmt.loopRegister!=null && stmt.loopRegister!= Register.A)
throw AssemblyError("can only use A")
val counterLabel = asmgen.makeLabel("for_counter")
val modifiedLabel = asmgen.makeLabel("for_modified")
asmgen.out("""
lda #<$iterableName
ldy #>$iterableName
sta $modifiedLabel+1
sty $modifiedLabel+2
ldy #0
$loopLabel sty $counterLabel
$modifiedLabel lda ${65535.toHex()},y ; modified""")
if(stmt.loopVar!=null)
asmgen.out(" sta ${asmgen.asmIdentifierName(stmt.loopVar!!)}")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel ldy $counterLabel
iny
cpy #${length and 255}
beq $endLabel
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
}
DataType.ARRAY_W, DataType.ARRAY_UW -> {
// TODO: optimize loop code when the length of the array is < 256, don't need a separate counter in such cases
val length = decl.arraysize!!.size()!! * 2
if(stmt.loopRegister!=null)
throw AssemblyError("can't use register to loop over words")
val counterLabel = asmgen.makeLabel("for_counter")
val modifiedLabel = asmgen.makeLabel("for_modified")
val modifiedLabel2 = asmgen.makeLabel("for_modified2")
val loopvarName = asmgen.asmIdentifierName(stmt.loopVar!!)
asmgen.out("""
lda #<$iterableName
ldy #>$iterableName
sta $modifiedLabel+1
sty $modifiedLabel+2
lda #<$iterableName+1
ldy #>$iterableName+1
sta $modifiedLabel2+1
sty $modifiedLabel2+2
ldy #0
$loopLabel sty $counterLabel
$modifiedLabel lda ${65535.toHex()},y ; modified
sta $loopvarName
$modifiedLabel2 lda ${65535.toHex()},y ; modified
sta $loopvarName+1""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel ldy $counterLabel
iny
iny
cpy #${length and 255}
beq $endLabel
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
}
DataType.ARRAY_F -> {
throw AssemblyError("for loop with floating point variables is not supported")
}
else -> throw AssemblyError("can't iterate over $iterableDt")
}
asmgen.loopEndLabels.pop()
asmgen.loopContinueLabels.pop()
}
private fun translateForOverConstRange(stmt: ForLoop, iterableDt: DataType, range: IntProgression) {
// TODO: optimize loop code when the range is < 256 iterations, don't need a separate counter in such cases
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
val continueLabel = asmgen.makeLabel("for_continue")
asmgen.loopEndLabels.push(endLabel)
asmgen.loopContinueLabels.push(continueLabel)
when(iterableDt) {
DataType.ARRAY_B, DataType.ARRAY_UB -> {
val counterLabel = asmgen.makeLabel("for_counter")
if(stmt.loopRegister!=null) {
// loop register over range
if(stmt.loopRegister!= Register.A)
throw AssemblyError("can only use A")
when {
range.step==1 -> {
// step = 1
asmgen.out("""
lda #${range.first}
sta $loopLabel+1
lda #${range.last-range.first+1 and 255}
sta $counterLabel
$loopLabel lda #0 ; modified""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel dec $counterLabel
beq $endLabel
inc $loopLabel+1
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
}
range.step==-1 -> {
// step = -1
asmgen.out("""
lda #${range.first}
sta $loopLabel+1
lda #${range.first-range.last+1 and 255}
sta $counterLabel
$loopLabel lda #0 ; modified """)
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel dec $counterLabel
beq $endLabel
dec $loopLabel+1
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
}
range.step >= 2 -> {
// step >= 2
asmgen.out("""
lda #${(range.last-range.first) / range.step + 1}
sta $counterLabel
lda #${range.first}
$loopLabel pha""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel pla
dec $counterLabel
beq $endLabel
clc
adc #${range.step}
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
}
else -> {
// step <= -2
asmgen.out("""
lda #${(range.first-range.last) / range.step.absoluteValue + 1}
sta $counterLabel
lda #${range.first}
$loopLabel pha""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel pla
dec $counterLabel
beq $endLabel
sec
sbc #${range.step.absoluteValue}
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
}
}
} else {
// loop over byte range via loopvar
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
when {
range.step==1 -> {
// step = 1
asmgen.out("""
lda #${range.first}
sta $varname
lda #${range.last-range.first+1 and 255}
sta $counterLabel
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel dec $counterLabel
beq $endLabel
inc $varname
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
}
range.step==-1 -> {
// step = -1
asmgen.out("""
lda #${range.first}
sta $varname
lda #${range.first-range.last+1 and 255}
sta $counterLabel
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel dec $counterLabel
beq $endLabel
dec $varname
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
}
range.step >= 2 -> {
// step >= 2
asmgen.out("""
lda #${(range.last-range.first) / range.step + 1}
sta $counterLabel
lda #${range.first}
sta $varname
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel dec $counterLabel
beq $endLabel
lda $varname
clc
adc #${range.step}
sta $varname
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
}
else -> {
// step <= -2
asmgen.out("""
lda #${(range.first-range.last) / range.step.absoluteValue + 1}
sta $counterLabel
lda #${range.first}
sta $varname
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel dec $counterLabel
beq $endLabel
lda $varname
sec
sbc #${range.step.absoluteValue}
sta $varname
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
}
}
}
}
DataType.ARRAY_W, DataType.ARRAY_UW -> {
// loop over word range via loopvar
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
when {
range.step == 1 -> {
// word, step = 1
val lastValue = range.last+1
asmgen.out("""
lda #<${range.first}
ldy #>${range.first}
sta $varname
sty $varname+1
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel inc $varname
bne +
inc $varname+1
+ lda $varname
cmp #<$lastValue
bne +
lda $varname+1
cmp #>$lastValue
beq $endLabel
+ jmp $loopLabel
$endLabel""")
}
range.step == -1 -> {
// word, step = 1
val lastValue = range.last-1
asmgen.out("""
lda #<${range.first}
ldy #>${range.first}
sta $varname
sty $varname+1
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel lda $varname
bne +
dec $varname+1
+ dec $varname
lda $varname
cmp #<$lastValue
bne +
lda $varname+1
cmp #>$lastValue
beq $endLabel
+ jmp $loopLabel
$endLabel""")
}
range.step >= 2 -> {
// word, step >= 2
TODO("for, word, step>=2")
}
else -> {
// step <= -2
TODO("for, word, step<=-2")
}
}
}
else -> throw AssemblyError("range expression can only be byte or word")
}
asmgen.loopEndLabels.pop()
asmgen.loopContinueLabels.pop()
}
}

View File

@ -0,0 +1,235 @@
package prog8.compiler.target.c64.codegen
import prog8.ast.IFunctionCall
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Subroutine
import prog8.ast.statements.SubroutineParameter
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.toHex
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translateFunctionCall(stmt: IFunctionCall) {
// output the code to setup the parameters and perform the actual call
// does NOT output the code to deal with the result values!
val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
if(Register.X in sub.asmClobbers)
asmgen.out(" stx c64.SCRATCH_ZPREGX") // we only save X for now (required! is the eval stack pointer), screw A and Y...
val subName = asmgen.asmIdentifierName(stmt.target)
if(stmt.arglist.isNotEmpty()) {
for(arg in sub.parameters.withIndex().zip(stmt.arglist)) {
translateFuncArguments(arg.first, arg.second, sub)
}
}
asmgen.out(" jsr $subName")
if(Register.X in sub.asmClobbers)
asmgen.out(" ldx c64.SCRATCH_ZPREGX") // restore X again
}
private fun translateFuncArguments(parameter: IndexedValue<SubroutineParameter>, value: Expression, sub: Subroutine) {
val sourceIDt = value.inferType(program)
if(!sourceIDt.isKnown)
throw AssemblyError("arg type unknown")
val sourceDt = sourceIDt.typeOrElse(DataType.STRUCT)
if(!argumentTypeCompatible(sourceDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
if(sub.asmParameterRegisters.isEmpty()) {
// pass parameter via a variable
val paramVar = parameter.value
val scopedParamVar = (sub.scopedname+"."+paramVar.name).split(".")
val target = AssignTarget(null, IdentifierReference(scopedParamVar, sub.position), null, null, sub.position)
target.linkParents(value.parent)
when (value) {
is NumericLiteralValue -> {
// optimize when the argument is a constant literal
when(parameter.value.type) {
in ByteDatatypes -> asmgen.assignFromByteConstant(target, value.number.toShort())
in WordDatatypes -> asmgen.assignFromWordConstant(target, value.number.toInt())
DataType.FLOAT -> asmgen.assignFromFloatConstant(target, value.number.toDouble())
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as arguments?")
else -> throw AssemblyError("weird parameter datatype")
}
}
is IdentifierReference -> {
// optimize when the argument is a variable
when (parameter.value.type) {
in ByteDatatypes -> asmgen.assignFromByteVariable(target, value)
in WordDatatypes -> asmgen.assignFromWordVariable(target, value)
DataType.FLOAT -> asmgen.assignFromFloatVariable(target, value)
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as arguments?")
else -> throw AssemblyError("weird parameter datatype")
}
}
is RegisterExpr -> {
asmgen.assignFromRegister(target, value.register)
}
is DirectMemoryRead -> {
when(value.addressExpression) {
is NumericLiteralValue -> {
val address = (value.addressExpression as NumericLiteralValue).number.toInt()
asmgen.assignFromMemoryByte(target, address, null)
}
is IdentifierReference -> {
asmgen.assignFromMemoryByte(target, null, value.addressExpression as IdentifierReference)
}
else -> {
asmgen.translateExpression(value.addressExpression)
asmgen.out(" jsr prog8_lib.read_byte_from_address | inx")
asmgen.assignFromRegister(target, Register.A)
}
}
}
else -> {
asmgen.translateExpression(value)
asmgen.assignFromEvalResult(target)
}
}
} else {
// pass parameter via a register parameter
val paramRegister = sub.asmParameterRegisters[parameter.index]
val statusflag = paramRegister.statusflag
val register = paramRegister.registerOrPair
val stack = paramRegister.stack
when {
stack -> {
// push arg onto the stack
// note: argument order is reversed (first argument will be deepest on the stack)
asmgen.translateExpression(value)
}
statusflag!=null -> {
if (statusflag == Statusflag.Pc) {
// this param needs to be set last, right before the jsr
// for now, this is already enforced on the subroutine definition by the Ast Checker
when(value) {
is NumericLiteralValue -> {
val carrySet = value.number.toInt() != 0
asmgen.out(if(carrySet) " sec" else " clc")
}
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(value)
asmgen.out("""
lda $sourceName
beq +
sec
bcs ++
+ clc
+
""")
}
is RegisterExpr -> {
when(value.register) {
Register.A -> asmgen.out(" cmp #0")
Register.X -> asmgen.out(" txa")
Register.Y -> asmgen.out(" tya")
}
asmgen.out("""
beq +
sec
bcs ++
+ clc
+
""")
}
else -> {
asmgen.translateExpression(value)
asmgen.out("""
inx
lda ${MachineDefinition.ESTACK_LO_HEX},x
beq +
sec
bcs ++
+ clc
+
""")
}
}
}
else throw AssemblyError("can only use Carry as status flag parameter")
}
register!=null && register.name.length==1 -> {
when (value) {
is NumericLiteralValue -> {
val target = AssignTarget(Register.valueOf(register.name), null, null, null, sub.position)
target.linkParents(value.parent)
asmgen.assignFromByteConstant(target, value.number.toShort())
}
is IdentifierReference -> {
val target = AssignTarget(Register.valueOf(register.name), null, null, null, sub.position)
target.linkParents(value.parent)
asmgen.assignFromByteVariable(target, value)
}
else -> {
asmgen.translateExpression(value)
when(register) {
RegisterOrPair.A -> asmgen.out(" inx | lda ${MachineDefinition.ESTACK_LO_HEX},x")
RegisterOrPair.X -> throw AssemblyError("can't pop into X register - use a variable instead")
RegisterOrPair.Y -> asmgen.out(" inx | ldy ${MachineDefinition.ESTACK_LO_HEX},x")
else -> throw AssemblyError("cannot assign to register pair")
}
}
}
}
register!=null && register.name.length==2 -> {
// register pair as a 16-bit value (only possible for subroutine parameters)
when (value) {
is NumericLiteralValue -> {
// optimize when the argument is a constant literal
val hex = value.number.toHex()
when (register) {
RegisterOrPair.AX -> asmgen.out(" lda #<$hex | ldx #>$hex")
RegisterOrPair.AY -> asmgen.out(" lda #<$hex | ldy #>$hex")
RegisterOrPair.XY -> asmgen.out(" ldx #<$hex | ldy #>$hex")
else -> {}
}
}
is AddressOf -> {
// optimize when the argument is an address of something
val sourceName = asmgen.asmIdentifierName(value.identifier)
when (register) {
RegisterOrPair.AX -> asmgen.out(" lda #<$sourceName | ldx #>$sourceName")
RegisterOrPair.AY -> asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
RegisterOrPair.XY -> asmgen.out(" ldx #<$sourceName | ldy #>$sourceName")
else -> {}
}
}
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(value)
when (register) {
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx $sourceName+1")
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy $sourceName+1")
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy $sourceName+1")
else -> {}
}
}
else -> {
asmgen.translateExpression(value)
if (register == RegisterOrPair.AX || register == RegisterOrPair.XY)
throw AssemblyError("can't use X register here - use a variable")
else if (register == RegisterOrPair.AY)
asmgen.out(" inx | lda ${MachineDefinition.ESTACK_LO_HEX},x | ldy ${MachineDefinition.ESTACK_HI_HEX},x")
}
}
}
}
}
}
private fun argumentTypeCompatible(argType: DataType, paramType: DataType): Boolean {
if(argType isAssignableTo paramType)
return true
// we have a special rule for some types.
// strings are assignable to UWORD, for example, and vice versa
if(argType in StringDatatypes && paramType==DataType.UWORD)
return true
if(argType==DataType.UWORD && paramType in StringDatatypes)
return true
return false
}
}

View File

@ -0,0 +1,149 @@
package prog8.compiler.target.c64.codegen
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.RegisterExpr
import prog8.ast.statements.PostIncrDecr
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.toHex
internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translate(stmt: PostIncrDecr) {
val incr = stmt.operator=="++"
val targetIdent = stmt.target.identifier
val targetMemory = stmt.target.memoryAddress
val targetArrayIdx = stmt.target.arrayindexed
val targetRegister = stmt.target.register
when {
targetRegister!=null -> {
when(targetRegister) {
Register.A -> {
if(incr)
asmgen.out(" clc | adc #1 ")
else
asmgen.out(" sec | sbc #1 ")
}
Register.X -> {
if(incr) asmgen.out(" inx") else asmgen.out(" dex")
}
Register.Y -> {
if(incr) asmgen.out(" iny") else asmgen.out(" dey")
}
}
}
targetIdent!=null -> {
val what = asmgen.asmIdentifierName(targetIdent)
val dt = stmt.target.inferType(program, stmt).typeOrElse(DataType.STRUCT)
when (dt) {
in ByteDatatypes -> asmgen.out(if (incr) " inc $what" else " dec $what")
in WordDatatypes -> {
if(incr)
asmgen.out(" inc $what | bne + | inc $what+1 |+")
else
asmgen.out("""
lda $what
bne +
dec $what+1
+ dec $what
""")
}
DataType.FLOAT -> {
asmgen.out(" lda #<$what | ldy #>$what")
asmgen.out(if(incr) " jsr c64flt.inc_var_f" else " jsr c64flt.dec_var_f")
}
else -> throw AssemblyError("need numeric type")
}
}
targetMemory!=null -> {
val addressExpr = targetMemory.addressExpression
when (addressExpr) {
is NumericLiteralValue -> {
val what = addressExpr.number.toHex()
asmgen.out(if(incr) " inc $what" else " dec $what")
}
is IdentifierReference -> {
val what = asmgen.asmIdentifierName(addressExpr)
asmgen.out(if(incr) " inc $what" else " dec $what")
}
else -> throw AssemblyError("weird target type $targetMemory")
}
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val what = asmgen.asmIdentifierName(targetArrayIdx.identifier)
val arrayDt = targetArrayIdx.identifier.inferType(program).typeOrElse(DataType.STRUCT)
val elementDt = ArrayElementTypes.getValue(arrayDt)
when(index) {
is NumericLiteralValue -> {
val indexValue = index.number.toInt() * elementDt.memorySize()
when(elementDt) {
in ByteDatatypes -> asmgen.out(if (incr) " inc $what+$indexValue" else " dec $what+$indexValue")
in WordDatatypes -> {
if(incr)
asmgen.out(" inc $what+$indexValue | bne + | inc $what+$indexValue+1 |+")
else
asmgen.out("""
lda $what+$indexValue
bne +
dec $what+$indexValue+1
+ dec $what+$indexValue
""")
}
DataType.FLOAT -> {
asmgen.out(" lda #<$what+$indexValue | ldy #>$what+$indexValue")
asmgen.out(if(incr) " jsr c64flt.inc_var_f" else " jsr c64flt.dec_var_f")
}
else -> throw AssemblyError("need numeric type")
}
}
is RegisterExpr -> {
// TODO optimize common cases
asmgen.translateArrayIndexIntoA(targetArrayIdx)
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
}
is IdentifierReference -> {
// TODO optimize common cases
asmgen.translateArrayIndexIntoA(targetArrayIdx)
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
}
else -> {
// TODO optimize common cases
asmgen.translateArrayIndexIntoA(targetArrayIdx)
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
}
}
}
else -> throw AssemblyError("weird target type ${stmt.target}")
}
}
private fun incrDecrArrayvalueWithIndexA(incr: Boolean, arrayDt: DataType, arrayVarName: String) {
asmgen.out(" stx ${MachineDefinition.C64Zeropage.SCRATCH_REG_X} | tax")
when(arrayDt) {
DataType.STR, DataType.STR_S,
DataType.ARRAY_UB, DataType.ARRAY_B -> {
asmgen.out(if(incr) " inc $arrayVarName,x" else " dec $arrayVarName,x")
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
if(incr)
asmgen.out(" inc $arrayVarName,x | bne + | inc $arrayVarName+1,x |+")
else
asmgen.out("""
lda $arrayVarName,x
bne +
dec $arrayVarName+1,x
+ dec $arrayVarName
""")
}
DataType.ARRAY_F -> {
asmgen.out(" lda #<$arrayVarName | ldy #>$arrayVarName")
asmgen.out(if(incr) " jsr c64flt.inc_indexed_var_f" else " jsr c64flt.dec_indexed_var_f")
}
else -> throw AssemblyError("weird array dt")
}
asmgen.out(" ldx ${MachineDefinition.C64Zeropage.SCRATCH_REG_X}")
}
}

File diff suppressed because it is too large Load Diff

View File

@ -34,6 +34,7 @@ val BuiltinFunctions = mapOf(
"abs" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument "abs" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
"len" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length "len" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
// normal functions follow: // normal functions follow:
"sgn" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
"sin" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) }, "sin" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) },
"sin8" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ), "sin8" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
"sin8u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ), "sin8u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
@ -112,25 +113,29 @@ val BuiltinFunctions = mapOf(
) )
fun builtinFunctionReturnType(function: String, args: List<Expression>, program: Program): DataType? { fun builtinFunctionReturnType(function: String, args: List<Expression>, program: Program): InferredTypes.InferredType {
fun datatypeFromIterableArg(arglist: Expression): DataType { fun datatypeFromIterableArg(arglist: Expression): DataType {
if(arglist is ReferenceLiteralValue) { if(arglist is ArrayLiteralValue) {
if(arglist.type== DataType.ARRAY_UB || arglist.type== DataType.ARRAY_UW || arglist.type== DataType.ARRAY_F) { if(arglist.type== DataType.ARRAY_UB || arglist.type== DataType.ARRAY_UW || arglist.type== DataType.ARRAY_F) {
val dt = arglist.array!!.map {it.inferType(program)} val dt = arglist.value.map {it.inferType(program)}
if(dt.any { it!= DataType.UBYTE && it!= DataType.UWORD && it!= DataType.FLOAT}) { if(dt.any { !(it istype DataType.UBYTE) && !(it istype DataType.UWORD) && !(it istype DataType.FLOAT)}) {
throw FatalAstException("fuction $function only accepts arraysize of numeric values") throw FatalAstException("fuction $function only accepts arraysize of numeric values")
} }
if(dt.any { it== DataType.FLOAT }) return DataType.FLOAT if(dt.any { it istype DataType.FLOAT }) return DataType.FLOAT
if(dt.any { it== DataType.UWORD }) return DataType.UWORD if(dt.any { it istype DataType.UWORD }) return DataType.UWORD
return DataType.UBYTE return DataType.UBYTE
} }
} }
if(arglist is IdentifierReference) { if(arglist is IdentifierReference) {
return when(val dt = arglist.inferType(program)) { val idt = arglist.inferType(program)
in NumericDatatypes -> dt!! if(!idt.isKnown)
in StringDatatypes -> dt!! throw FatalAstException("couldn't determine type of iterable $arglist")
in ArrayDatatypes -> ArrayElementTypes.getValue(dt!!) val dt = idt.typeOrElse(DataType.STRUCT)
return when(dt) {
in NumericDatatypes -> dt
in StringDatatypes -> dt
in ArrayDatatypes -> ArrayElementTypes.getValue(dt)
else -> throw FatalAstException("function '$function' requires one argument which is an iterable") else -> throw FatalAstException("function '$function' requires one argument which is an iterable")
} }
} }
@ -139,48 +144,48 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
val func = BuiltinFunctions.getValue(function) val func = BuiltinFunctions.getValue(function)
if(func.returntype!=null) if(func.returntype!=null)
return func.returntype return InferredTypes.knownFor(func.returntype)
// function has return values, but the return type depends on the arguments // function has return values, but the return type depends on the arguments
return when (function) { return when (function) {
"abs" -> { "abs" -> {
val dt = args.single().inferType(program) val dt = args.single().inferType(program)
if(dt in NumericDatatypes) if(dt.typeOrElse(DataType.STRUCT) in NumericDatatypes)
return dt return dt
else else
throw FatalAstException("weird datatype passed to abs $dt") throw FatalAstException("weird datatype passed to abs $dt")
} }
"max", "min" -> { "max", "min" -> {
when(val dt = datatypeFromIterableArg(args.single())) { when(val dt = datatypeFromIterableArg(args.single())) {
in NumericDatatypes -> dt in NumericDatatypes -> InferredTypes.knownFor(dt)
in StringDatatypes -> DataType.UBYTE in StringDatatypes -> InferredTypes.knownFor(DataType.UBYTE)
in ArrayDatatypes -> ArrayElementTypes.getValue(dt) in ArrayDatatypes -> InferredTypes.knownFor(ArrayElementTypes.getValue(dt))
else -> null else -> InferredTypes.unknown()
} }
} }
"sum" -> { "sum" -> {
when(datatypeFromIterableArg(args.single())) { when(datatypeFromIterableArg(args.single())) {
DataType.UBYTE, DataType.UWORD -> DataType.UWORD DataType.UBYTE, DataType.UWORD -> InferredTypes.knownFor(DataType.UWORD)
DataType.BYTE, DataType.WORD -> DataType.WORD DataType.BYTE, DataType.WORD -> InferredTypes.knownFor(DataType.WORD)
DataType.FLOAT -> DataType.FLOAT DataType.FLOAT -> InferredTypes.knownFor(DataType.FLOAT)
DataType.ARRAY_UB, DataType.ARRAY_UW -> DataType.UWORD DataType.ARRAY_UB, DataType.ARRAY_UW -> InferredTypes.knownFor(DataType.UWORD)
DataType.ARRAY_B, DataType.ARRAY_W -> DataType.WORD DataType.ARRAY_B, DataType.ARRAY_W -> InferredTypes.knownFor(DataType.WORD)
DataType.ARRAY_F -> DataType.FLOAT DataType.ARRAY_F -> InferredTypes.knownFor(DataType.FLOAT)
in StringDatatypes -> DataType.UWORD in StringDatatypes -> InferredTypes.knownFor(DataType.UWORD)
else -> null else -> InferredTypes.unknown()
} }
} }
"len" -> { "len" -> {
// a length can be >255 so in that case, the result is an UWORD instead of an UBYTE // a length can be >255 so in that case, the result is an UWORD instead of an UBYTE
// but to avoid a lot of code duplication we simply assume UWORD in all cases for now // but to avoid a lot of code duplication we simply assume UWORD in all cases for now
return DataType.UWORD return InferredTypes.knownFor(DataType.UWORD)
} }
else -> return null else -> return InferredTypes.unknown()
} }
} }
class NotConstArgumentException: AstException("not a const argument to a built-in function") class NotConstArgumentException: AstException("not a const argument to a built-in function") // TODO: ugly, remove throwing exceptions for control flow
private fun oneDoubleArg(args: List<Expression>, position: Position, program: Program, function: (arg: Double)->Number): NumericLiteralValue { private fun oneDoubleArg(args: List<Expression>, position: Position, program: Program, function: (arg: Double)->Number): NumericLiteralValue {
@ -271,10 +276,10 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
NumericLiteralValue.optimalInteger(arraySize, args[0].position) NumericLiteralValue.optimalInteger(arraySize, args[0].position)
} }
in StringDatatypes -> { in StringDatatypes -> {
val refLv = target.value as ReferenceLiteralValue val refLv = target.value as StringLiteralValue
if(refLv.str!!.length>255) if(refLv.value.length>255)
throw CompilerException("string length exceeds byte limit ${refLv.position}") throw CompilerException("string length exceeds byte limit ${refLv.position}")
NumericLiteralValue.optimalInteger(refLv.str.length, args[0].position) NumericLiteralValue.optimalInteger(refLv.value.length, args[0].position)
} }
in NumericDatatypes -> throw SyntaxError("len of weird argument ${args[0]}", position) in NumericDatatypes -> throw SyntaxError("len of weird argument ${args[0]}", position)
else -> throw CompilerException("weird datatype") else -> throw CompilerException("weird datatype")
@ -355,6 +360,13 @@ private fun builtinCos16u(args: List<Expression>, position: Position, program: P
return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * cos(rad)).toInt(), position) return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * cos(rad)).toInt(), position)
} }
private fun builtinSgn(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("sgn requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
return NumericLiteralValue(DataType.BYTE, constval.number.toDouble().sign.toShort(), position)
}
private fun numericLiteral(value: Number, position: Position): NumericLiteralValue { private fun numericLiteral(value: Number, position: Position): NumericLiteralValue {
val floatNum=value.toDouble() val floatNum=value.toDouble()
val tweakedValue: Number = val tweakedValue: Number =

View File

@ -9,7 +9,7 @@ import prog8.ast.processing.fixupArrayDatatype
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_NEGATIVE import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_POSITIVE import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_POSITIVE
import prog8.compiler.target.c64.codegen2.AssemblyError import prog8.compiler.target.c64.codegen.AssemblyError
import prog8.functions.BuiltinFunctions import prog8.functions.BuiltinFunctions
import kotlin.math.floor import kotlin.math.floor
@ -39,11 +39,9 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
if(decl.isArray){ if(decl.isArray){
if(decl.arraysize==null) { if(decl.arraysize==null) {
// for arrays that have no size specifier (or a non-constant one) attempt to deduce the size // for arrays that have no size specifier (or a non-constant one) attempt to deduce the size
val arrayval = (decl.value as? ReferenceLiteralValue)?.array val arrayval = (decl.value as ArrayLiteralValue).value
if(arrayval!=null) { decl.arraysize = ArrayIndex(NumericLiteralValue.optimalInteger(arrayval.size, decl.position), decl.position)
decl.arraysize = ArrayIndex(NumericLiteralValue.optimalInteger(arrayval.size, decl.position), decl.position) optimizationsDone++
optimizationsDone++
}
} }
else if(decl.arraysize?.size()==null) { else if(decl.arraysize?.size()==null) {
val size = decl.arraysize!!.index.accept(this) val size = decl.arraysize!!.index.accept(this)
@ -81,15 +79,15 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
errors.add(ExpressionError("range expression size doesn't match declared array size", decl.value?.position!!)) errors.add(ExpressionError("range expression size doesn't match declared array size", decl.value?.position!!))
val constRange = rangeExpr.toConstantIntegerRange() val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) { if(constRange!=null) {
val eltType = rangeExpr.inferType(program)!! val eltType = rangeExpr.inferType(program).typeOrElse(DataType.UBYTE)
if(eltType in ByteDatatypes) { if(eltType in ByteDatatypes) {
decl.value = ReferenceLiteralValue(decl.datatype, decl.value = ArrayLiteralValue(decl.datatype,
array = constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) } constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) }.toTypedArray(),
.toTypedArray(), position = decl.value!!.position) position = decl.value!!.position)
} else { } else {
decl.value = ReferenceLiteralValue(decl.datatype, decl.value = ArrayLiteralValue(decl.datatype,
array = constRange.map { NumericLiteralValue(eltType, it, decl.value!!.position) } constRange.map { NumericLiteralValue(eltType, it, decl.value!!.position) }.toTypedArray(),
.toTypedArray(), position = decl.value!!.position) position = decl.value!!.position)
} }
decl.value!!.linkParents(decl) decl.value!!.linkParents(decl)
optimizationsDone++ optimizationsDone++
@ -123,7 +121,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
} }
// create the array itself, filled with the fillvalue. // create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteralValue.optimalInteger(it, numericLv.position) as Expression}.toTypedArray() val array = Array(size) {fillvalue}.map { NumericLiteralValue.optimalInteger(it, numericLv.position) as Expression}.toTypedArray()
val refValue = ReferenceLiteralValue(decl.datatype, array = array, position = numericLv.position) val refValue = ArrayLiteralValue(decl.datatype, array, position = numericLv.position)
refValue.addToHeap(program.heap) refValue.addToHeap(program.heap)
decl.value = refValue decl.value = refValue
refValue.parent=decl refValue.parent=decl
@ -145,7 +143,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
else { else {
// create the array itself, filled with the fillvalue. // create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteralValue(DataType.FLOAT, it, litval.position) as Expression}.toTypedArray() 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) val refValue = ArrayLiteralValue(DataType.ARRAY_F, array, position = litval.position)
refValue.addToHeap(program.heap) refValue.addToHeap(program.heap)
decl.value = refValue decl.value = refValue
refValue.parent=decl refValue.parent=decl
@ -210,7 +208,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
val possibleDts = arg.second.possibleDatatypes val possibleDts = arg.second.possibleDatatypes
val argConst = arg.first.value.constValue(program) val argConst = arg.first.value.constValue(program)
if(argConst!=null && argConst.type !in possibleDts) { if(argConst!=null && argConst.type !in possibleDts) {
val convertedValue = argConst.cast(possibleDts.first()) // TODO can throw exception val convertedValue = argConst.cast(possibleDts.first())
functionCall.arglist[arg.first.index] = convertedValue functionCall.arglist[arg.first.index] = convertedValue
optimizationsDone++ optimizationsDone++
} }
@ -226,7 +224,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
val expectedDt = arg.second.type val expectedDt = arg.second.type
val argConst = arg.first.value.constValue(program) val argConst = arg.first.value.constValue(program)
if(argConst!=null && argConst.type!=expectedDt) { if(argConst!=null && argConst.type!=expectedDt) {
val convertedValue = argConst.cast(expectedDt) // TODO can throw exception val convertedValue = argConst.cast(expectedDt)
functionCall.arglist[arg.first.index] = convertedValue functionCall.arglist[arg.first.index] = convertedValue
optimizationsDone++ optimizationsDone++
} }
@ -311,7 +309,8 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
return try { return try {
super.visit(expr) super.visit(expr)
if(expr.left is ReferenceLiteralValue || expr.right is ReferenceLiteralValue) if(expr.left is StringLiteralValue || expr.left is ArrayLiteralValue
|| expr.right is StringLiteralValue || expr.right is ArrayLiteralValue)
throw FatalAstException("binexpr with reference litval instead of numeric") throw FatalAstException("binexpr with reference litval instead of numeric")
val leftconst = expr.left.constValue(program) val leftconst = expr.left.constValue(program)
@ -356,7 +355,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
subleftIsConst: Boolean, subleftIsConst: Boolean,
subrightIsConst: Boolean): Expression subrightIsConst: Boolean): Expression
{ {
// @todo this implements only a small set of possible reorderings for now // todo: this implements only a small set of possible reorderings at this time
if(expr.operator==subExpr.operator) { if(expr.operator==subExpr.operator) {
// both operators are the isSameAs. // both operators are the isSameAs.
// If + or *, we can simply swap the const of expr and Var in subexpr. // If + or *, we can simply swap the const of expr and Var in subexpr.
@ -544,11 +543,15 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
override fun visit(forLoop: ForLoop): Statement { override fun visit(forLoop: ForLoop): Statement {
fun adjustRangeDt(rangeFrom: NumericLiteralValue, targetDt: DataType, rangeTo: NumericLiteralValue, stepLiteral: NumericLiteralValue?, range: RangeExpr): RangeExpr { fun adjustRangeDt(rangeFrom: NumericLiteralValue, targetDt: DataType, rangeTo: NumericLiteralValue, stepLiteral: NumericLiteralValue?, range: RangeExpr): RangeExpr {
// TODO casts can throw exception val newFrom: NumericLiteralValue
val newFrom = rangeFrom.cast(targetDt) val newTo: NumericLiteralValue
val newTo = rangeTo.cast(targetDt) try {
val newStep: Expression = newFrom = rangeFrom.cast(targetDt)
stepLiteral?.cast(targetDt) ?: range.step newTo = rangeTo.cast(targetDt)
} catch (x: ExpressionError) {
return range
}
val newStep: Expression = stepLiteral?.cast(targetDt) ?: range.step
return RangeExpr(newFrom, newTo, newStep, range.position) return RangeExpr(newFrom, newTo, newStep, range.position)
} }
@ -593,17 +596,15 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
return resultStmt return resultStmt
} }
override fun visit(refLiteral: ReferenceLiteralValue): Expression { override fun visit(arrayLiteral: ArrayLiteralValue): Expression {
val litval = super.visit(refLiteral) val array = super.visit(arrayLiteral)
if(litval is ReferenceLiteralValue) { if(array is ArrayLiteralValue) {
if (litval.isArray) { val vardecl = array.parent as? VarDecl
val vardecl = litval.parent as? VarDecl if (vardecl!=null) {
if (vardecl!=null) { return fixupArrayDatatype(array, vardecl, program.heap)
return fixupArrayDatatype(litval, vardecl, program.heap)
}
} }
} }
return litval return array
} }
override fun visit(assignment: Assignment): Statement { override fun visit(assignment: Assignment): Statement {
@ -611,7 +612,10 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
val lv = assignment.value as? NumericLiteralValue val lv = assignment.value as? NumericLiteralValue
if(lv!=null) { if(lv!=null) {
// see if we can promote/convert a literal value to the required datatype // see if we can promote/convert a literal value to the required datatype
when(assignment.target.inferType(program, assignment)) { val idt = assignment.target.inferType(program, assignment)
if(!idt.isKnown)
return assignment
when(idt.typeOrElse(DataType.STRUCT)) {
DataType.UWORD -> { DataType.UWORD -> {
// we can convert to UWORD: any UBYTE, BYTE/WORD that are >=0, FLOAT that's an integer 0..65535, // we can convert to UWORD: any UBYTE, BYTE/WORD that are >=0, FLOAT that's an integer 0..65535,
if(lv.type== DataType.UBYTE) if(lv.type== DataType.UBYTE)

View File

@ -1,16 +1,14 @@
package prog8.optimizer package prog8.optimizer
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.AstException import prog8.ast.base.*
import prog8.ast.base.DataType
import prog8.ast.base.IntegerDatatypes
import prog8.ast.base.NumericDatatypes
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.processing.IAstModifyingVisitor import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.statements.Assignment import prog8.ast.statements.Assignment
import prog8.ast.statements.Statement import prog8.ast.statements.Statement
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.log2 import kotlin.math.log2
import kotlin.math.pow
/* /*
todo advanced expression optimization: common (sub) expression elimination (turn common expressions into single subroutine call + introduce variable to hold it) todo advanced expression optimization: common (sub) expression elimination (turn common expressions into single subroutine call + introduce variable to hold it)
@ -136,9 +134,14 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
val constTrue = NumericLiteralValue.fromBoolean(true, expr.position) val constTrue = NumericLiteralValue.fromBoolean(true, expr.position)
val constFalse = NumericLiteralValue.fromBoolean(false, expr.position) val constFalse = NumericLiteralValue.fromBoolean(false, expr.position)
val leftDt = expr.left.inferType(program) val leftIDt = expr.left.inferType(program)
val rightDt = expr.right.inferType(program) val rightIDt = expr.right.inferType(program)
if (leftDt != null && rightDt != null && leftDt != rightDt) { if(!leftIDt.isKnown || !rightIDt.isKnown)
throw FatalAstException("can't determine datatype of both expression operands $expr")
val leftDt = leftIDt.typeOrElse(DataType.STRUCT)
val rightDt = rightIDt.typeOrElse(DataType.STRUCT)
if (leftDt != rightDt) {
// try to convert a datatype into the other (where ddd // try to convert a datatype into the other (where ddd
if (adjustDatatypes(expr, leftVal, leftDt, rightVal, rightDt)) { if (adjustDatatypes(expr, leftVal, leftDt, rightVal, rightDt)) {
optimizationsDone++ optimizationsDone++
@ -226,7 +229,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
val x = expr.right val x = expr.right
val y = determineY(x, leftBinExpr) val y = determineY(x, leftBinExpr)
if(y!=null) { if(y!=null) {
val yPlus1 = BinaryExpression(y, "+", NumericLiteralValue(leftDt!!, 1, y.position), y.position) val yPlus1 = BinaryExpression(y, "+", NumericLiteralValue(leftDt, 1, y.position), y.position)
return BinaryExpression(x, "*", yPlus1, x.position) return BinaryExpression(x, "*", yPlus1, x.position)
} }
} else { } else {
@ -235,7 +238,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
val x = expr.right val x = expr.right
val y = determineY(x, leftBinExpr) val y = determineY(x, leftBinExpr)
if(y!=null) { if(y!=null) {
val yMinus1 = BinaryExpression(y, "-", NumericLiteralValue(leftDt!!, 1, y.position), y.position) val yMinus1 = BinaryExpression(y, "-", NumericLiteralValue(leftDt, 1, y.position), y.position)
return BinaryExpression(x, "*", yMinus1, x.position) return BinaryExpression(x, "*", yMinus1, x.position)
} }
} }
@ -338,6 +341,8 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
"-" -> return optimizeSub(expr, leftVal, rightVal) "-" -> return optimizeSub(expr, leftVal, rightVal)
"**" -> return optimizePower(expr, leftVal, rightVal) "**" -> return optimizePower(expr, leftVal, rightVal)
"%" -> return optimizeRemainder(expr, leftVal, rightVal) "%" -> return optimizeRemainder(expr, leftVal, rightVal)
">>" -> return optimizeShiftRight(expr, rightVal)
"<<" -> return optimizeShiftLeft(expr, rightVal)
} }
return expr return expr
} }
@ -590,7 +595,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
"%" -> { "%" -> {
if (cv == 1.0) { if (cv == 1.0) {
optimizationsDone++ optimizationsDone++
return NumericLiteralValue(expr.inferType(program)!!, 0, expr.position) return NumericLiteralValue(expr.inferType(program).typeOrElse(DataType.STRUCT), 0, expr.position)
} else if (cv == 2.0) { } else if (cv == 2.0) {
optimizationsDone++ optimizationsDone++
expr.operator = "&" expr.operator = "&"
@ -603,17 +608,24 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
} }
private val powersOfTwo = (1 .. 16).map { (2.0).pow(it) }
private val negativePowersOfTwo = powersOfTwo.map { -it }
private fun optimizeDivision(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression { private fun optimizeDivision(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression {
if(leftVal==null && rightVal==null) if(leftVal==null && rightVal==null)
return expr return expr
// cannot shuffle assiciativity with division! // TODO fix bug in this routine!
// cannot shuffle assiciativity with division!
if(rightVal!=null) { if(rightVal!=null) {
// right value is a constant, see if we can optimize // right value is a constant, see if we can optimize
val rightConst: NumericLiteralValue = rightVal val rightConst: NumericLiteralValue = rightVal
val cv = rightConst.number.toDouble() val cv = rightConst.number.toDouble()
val leftDt = expr.left.inferType(program) val leftIDt = expr.left.inferType(program)
if(!leftIDt.isKnown)
return expr
val leftDt = leftIDt.typeOrElse(DataType.STRUCT)
when(cv) { when(cv) {
-1.0 -> { -1.0 -> {
// '/' -> -left // '/' -> -left
@ -629,7 +641,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
return expr.left return expr.left
} }
} }
2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0, 256.0, 512.0, 1024.0, 2048.0, 4096.0, 8192.0, 16384.0, 32768.0, 65536.0 -> { in powersOfTwo -> {
if(leftDt in IntegerDatatypes) { if(leftDt in IntegerDatatypes) {
// divided by a power of two => shift right // divided by a power of two => shift right
optimizationsDone++ optimizationsDone++
@ -637,7 +649,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
return BinaryExpression(expr.left, ">>", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position) return BinaryExpression(expr.left, ">>", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
} }
} }
-2.0, -4.0, -8.0, -16.0, -32.0, -64.0, -128.0, -256.0, -512.0, -1024.0, -2048.0, -4096.0, -8192.0, -16384.0, -32768.0, -65536.0 -> { in negativePowersOfTwo -> {
if(leftDt in IntegerDatatypes) { if(leftDt in IntegerDatatypes) {
// divided by a negative power of two => negate, then shift right // divided by a negative power of two => negate, then shift right
optimizationsDone++ optimizationsDone++
@ -701,7 +713,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
return expr.left return expr.left
} }
2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0, 256.0, 512.0, 1024.0, 2048.0, 4096.0, 8192.0, 16384.0, 32768.0, 65536.0 -> { 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 -> {
if(leftValue.inferType(program) in IntegerDatatypes) { if(leftValue.inferType(program).typeOrElse(DataType.STRUCT) in IntegerDatatypes) {
// times a power of two => shift left // times a power of two => shift left
optimizationsDone++ optimizationsDone++
val numshifts = log2(cv).toInt() val numshifts = log2(cv).toInt()
@ -709,7 +721,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
} }
} }
-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 -> { -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 -> {
if(leftValue.inferType(program) in IntegerDatatypes) { if(leftValue.inferType(program).typeOrElse(DataType.STRUCT) in IntegerDatatypes) {
// times a negative power of two => negate, then shift left // times a negative power of two => negate, then shift left
optimizationsDone++ optimizationsDone++
val numshifts = log2(-cv).toInt() val numshifts = log2(-cv).toInt()
@ -722,4 +734,97 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
return expr return expr
} }
private fun optimizeShiftLeft(expr: BinaryExpression, amountLv: NumericLiteralValue?): Expression {
if(amountLv==null)
return expr
val amount=amountLv.number.toInt()
if(amount==0) {
optimizationsDone++
return expr.left
}
val targetDt = expr.left.inferType(program).typeOrElse(DataType.STRUCT)
when(targetDt) {
DataType.UBYTE, DataType.BYTE -> {
if(amount>=8) {
optimizationsDone++
return NumericLiteralValue.optimalInteger(0, expr.position)
}
}
DataType.UWORD, DataType.WORD -> {
if(amount>=16) {
optimizationsDone++
return NumericLiteralValue.optimalInteger(0, expr.position)
}
else if(amount>=8) {
optimizationsDone++
val lsb=TypecastExpression(expr.left, DataType.UBYTE, true, expr.position)
if(amount==8) {
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(NumericLiteralValue.optimalInteger(0, expr.position), lsb), expr.position)
}
val shifted = BinaryExpression(lsb, "<<", NumericLiteralValue.optimalInteger(amount-8, expr.position), expr.position)
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(NumericLiteralValue.optimalInteger(0, expr.position), shifted), expr.position)
}
}
else -> {}
}
return expr
}
private fun optimizeShiftRight(expr: BinaryExpression, amountLv: NumericLiteralValue?): Expression {
if(amountLv==null)
return expr
val amount=amountLv.number.toInt()
if(amount==0) {
optimizationsDone++
return expr.left
}
val targetDt = expr.left.inferType(program).typeOrElse(DataType.STRUCT)
when(targetDt) {
DataType.UBYTE -> {
if(amount>=8) {
optimizationsDone++
return NumericLiteralValue.optimalInteger(0, expr.position)
}
}
DataType.BYTE -> {
if(amount>8) {
expr.right = NumericLiteralValue.optimalInteger(8, expr.right.position)
return expr
}
}
DataType.UWORD -> {
if(amount>=16) {
optimizationsDone++
return NumericLiteralValue.optimalInteger(0, expr.position)
}
else if(amount>=8) {
optimizationsDone++
val msb=FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
if(amount==8)
return msb
return BinaryExpression(msb, ">>", NumericLiteralValue.optimalInteger(amount-8, expr.position), expr.position)
}
}
DataType.WORD -> {
if(amount>16) {
expr.right = NumericLiteralValue.optimalInteger(16, expr.right.position)
return expr
} else if(amount>=8) {
optimizationsDone++
val msbAsByte = TypecastExpression(
FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position),
DataType.BYTE,
true, expr.position)
if(amount==8)
return msbAsByte
return BinaryExpression(msbAsByte, ">>", NumericLiteralValue.optimalInteger(amount-8, expr.position), expr.position)
}
}
else -> {}
}
return expr
}
} }

View File

@ -1,20 +1,22 @@
package prog8.optimizer package prog8.optimizer
import prog8.ast.* import prog8.ast.INameScope
import prog8.ast.Module
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.processing.IAstModifyingVisitor import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.processing.IAstVisitor import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.target.c64.Petscii import prog8.compiler.target.c64.Petscii
import prog8.compiler.target.c64.codegen.AssemblyError
import prog8.functions.BuiltinFunctions import prog8.functions.BuiltinFunctions
import kotlin.math.floor import kotlin.math.floor
/* /*
todo: subroutines with 1 or 2 byte args or 1 word arg can be converted to asm sub calling convention (args in registers) TODO: analyse for unreachable code and remove that (f.i. code after goto or return that has no label so can never be jumped to) + print warning about this
todo analyse for unreachable code and remove that (f.i. code after goto or return that has no label so can never be jumped to) + print warning about this
TODO: proper inlining of small subroutines (correctly renaming/relocating all variables in them and refs to those as well, or restrict to subs without variables?) TODO: proper inlining of small subroutines (correctly renaming/relocating all variables in them and refs to those as well, or restrict to subs without variables?)
*/ */
@ -104,11 +106,6 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
linesToRemove.reversed().forEach{subroutine.statements.removeAt(it)} linesToRemove.reversed().forEach{subroutine.statements.removeAt(it)}
} }
if(subroutine.canBeAsmSubroutine) {
optimizationsDone++
return subroutine.intoAsmSubroutine() // TODO this doesn't work yet due to parameter vardecl issue
}
if(subroutine !in callgraph.usedSymbols && !forceOutput) { if(subroutine !in callgraph.usedSymbols && !forceOutput) {
printWarning("removing unused subroutine '${subroutine.name}'", subroutine.position) printWarning("removing unused subroutine '${subroutine.name}'", subroutine.position)
optimizationsDone++ optimizationsDone++
@ -419,7 +416,10 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
return NopStatement.insteadOf(assignment) return NopStatement.insteadOf(assignment)
} }
} }
val targetDt = assignment.target.inferType(program, assignment) val targetIDt = assignment.target.inferType(program, assignment)
if(!targetIDt.isKnown)
throw AssemblyError("can't infer type of assignment target")
val targetDt = targetIDt.typeOrElse(DataType.STRUCT)
val bexpr=assignment.value as? BinaryExpression val bexpr=assignment.value as? BinaryExpression
if(bexpr!=null) { if(bexpr!=null) {
val cv = bexpr.right.constValue(program)?.number?.toDouble() val cv = bexpr.right.constValue(program)?.number?.toDouble()
@ -516,8 +516,7 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
optimizationsDone++ optimizationsDone++
return NopStatement.insteadOf(assignment) return NopStatement.insteadOf(assignment)
} }
if (((targetDt == DataType.UWORD || targetDt == DataType.WORD) && cv > 15.0) || if ((targetDt == DataType.UWORD && cv > 15.0) || (targetDt == DataType.UBYTE && cv > 7.0)) {
((targetDt == DataType.UBYTE || targetDt == DataType.BYTE) && cv > 7.0)) {
assignment.value = NumericLiteralValue.optimalInteger(0, assignment.value.position) assignment.value = NumericLiteralValue.optimalInteger(0, assignment.value.position)
assignment.value.linkParents(assignment) assignment.value.linkParents(assignment)
optimizationsDone++ optimizationsDone++

View File

@ -1,8 +1,9 @@
package prog8.vm package prog8.vm
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.ArrayLiteralValue
import prog8.ast.expressions.NumericLiteralValue import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.ReferenceLiteralValue import prog8.ast.expressions.StringLiteralValue
import prog8.compiler.HeapValues import prog8.compiler.HeapValues
import prog8.compiler.target.c64.Petscii import prog8.compiler.target.c64.Petscii
import java.util.* import java.util.*
@ -28,13 +29,8 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
return RuntimeValue(literalValue.type, num = literalValue.number) return RuntimeValue(literalValue.type, num = literalValue.number)
} }
fun fromLv(literalValue: ReferenceLiteralValue, heap: HeapValues): RuntimeValue { fun fromLv(string: StringLiteralValue, heap: HeapValues): RuntimeValue = fromHeapId(string.heapId!!, heap)
return when(literalValue.type) { fun fromLv(array: ArrayLiteralValue, heap: HeapValues): RuntimeValue = fromHeapId(array.heapId!!, heap)
in StringDatatypes -> fromHeapId(literalValue.heapId!!, heap)
in ArrayDatatypes -> fromHeapId(literalValue.heapId!!, heap)
else -> throw IllegalArgumentException("weird source value $literalValue")
}
}
fun fromHeapId(heapId: Int, heap: HeapValues): RuntimeValue { fun fromHeapId(heapId: Int, heap: HeapValues): RuntimeValue {
val value = heap.get(heapId) val value = heap.get(heapId)

View File

@ -166,10 +166,10 @@ class AstVm(val program: Program) {
fun memwrite(address: Int, value: Short): Short { fun memwrite(address: Int, value: Short): Short {
if(address==0xa0 || address==0xa1 || address==0xa2) { if(address==0xa0 || address==0xa1 || address==0xa2) {
// a write to the jiffy clock, update the clock offset for the irq // a write to the jiffy clock, update the clock offset for the irq
val time_hi = if(address==0xa0) value else mem.getUByte_DMA(0xa0) val timeHi = if(address==0xa0) value else mem.getUByte_DMA(0xa0)
val time_mid = if(address==0xa1) value else mem.getUByte_DMA(0xa1) val timeMid = if(address==0xa1) value else mem.getUByte_DMA(0xa1)
val time_lo = if(address==0xa2) value else mem.getUByte_DMA(0xa2) val timeLo = if(address==0xa2) value else mem.getUByte_DMA(0xa2)
val jiffies = (time_hi.toInt() shl 16) + (time_mid.toInt() shl 8) + time_lo val jiffies = (timeHi.toInt() shl 16) + (timeMid.toInt() shl 8) + timeLo
rtcOffset = bootTime - (jiffies*1000/60) rtcOffset = bootTime - (jiffies*1000/60)
} }
if(address in 1024..2023) { if(address in 1024..2023) {
@ -412,9 +412,11 @@ class AstVm(val program: Program) {
stmt.target.arrayindexed != null -> { stmt.target.arrayindexed != null -> {
val arrayvar = stmt.target.arrayindexed!!.identifier.targetVarDecl(program.namespace)!! val arrayvar = stmt.target.arrayindexed!!.identifier.targetVarDecl(program.namespace)!!
val arrayvalue = runtimeVariables.get(arrayvar.definingScope(), arrayvar.name) val arrayvalue = runtimeVariables.get(arrayvar.definingScope(), arrayvar.name)
val elementType = stmt.target.arrayindexed!!.inferType(program)!!
val index = evaluate(stmt.target.arrayindexed!!.arrayspec.index, evalCtx).integerValue() val index = evaluate(stmt.target.arrayindexed!!.arrayspec.index, evalCtx).integerValue()
var value = RuntimeValue(elementType, arrayvalue.array!![index].toInt()) val elementType = stmt.target.arrayindexed!!.inferType(program)
if(!elementType.isKnown)
throw VmExecutionException("unknown/void elt type")
var value = RuntimeValue(elementType.typeOrElse(DataType.BYTE), arrayvalue.array!![index].toInt())
value = when { value = when {
stmt.operator == "++" -> value.inc() stmt.operator == "++" -> value.inc()
stmt.operator == "--" -> value.dec() stmt.operator == "--" -> value.dec()
@ -472,7 +474,8 @@ class AstVm(val program: Program) {
loopvarDt = DataType.UBYTE loopvarDt = DataType.UBYTE
loopvar = IdentifierReference(listOf(stmt.loopRegister.name), stmt.position) loopvar = IdentifierReference(listOf(stmt.loopRegister.name), stmt.position)
} else { } else {
loopvarDt = stmt.loopVar!!.inferType(program)!! val dt = stmt.loopVar!!.inferType(program)
loopvarDt = dt.typeOrElse(DataType.UBYTE)
loopvar = stmt.loopVar!! loopvar = stmt.loopVar!!
} }
val iterator = iterable.iterator() val iterator = iterable.iterator()
@ -619,8 +622,10 @@ class AstVm(val program: Program) {
else { else {
val address = (vardecl.value as NumericLiteralValue).number.toInt() val address = (vardecl.value as NumericLiteralValue).number.toInt()
val index = evaluate(targetArrayIndexed.arrayspec.index, evalCtx).integerValue() val index = evaluate(targetArrayIndexed.arrayspec.index, evalCtx).integerValue()
val elementType = targetArrayIndexed.inferType(program)!! val elementType = targetArrayIndexed.inferType(program)
when(elementType) { if(!elementType.isKnown)
throw VmExecutionException("unknown/void array elt type $targetArrayIndexed")
when(elementType.typeOrElse(DataType.UBYTE)) {
DataType.UBYTE -> mem.setUByte(address+index, value.byteval!!) DataType.UBYTE -> mem.setUByte(address+index, value.byteval!!)
DataType.BYTE -> mem.setSByte(address+index, value.byteval!!) DataType.BYTE -> mem.setSByte(address+index, value.byteval!!)
DataType.UWORD -> mem.setUWord(address+index*2, value.wordval!!) DataType.UWORD -> mem.setUWord(address+index*2, value.wordval!!)

View File

@ -12,7 +12,6 @@ import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDecl import prog8.ast.statements.VarDecl
import prog8.vm.RuntimeValue import prog8.vm.RuntimeValue
import prog8.vm.RuntimeValueRange import prog8.vm.RuntimeValueRange
import kotlin.math.abs
typealias BuiltinfunctionCaller = (name: String, args: List<RuntimeValue>, flags: StatusFlags) -> RuntimeValue? typealias BuiltinfunctionCaller = (name: String, args: List<RuntimeValue>, flags: StatusFlags) -> RuntimeValue?
@ -30,12 +29,9 @@ fun evaluate(expr: Expression, ctx: EvalContext): RuntimeValue {
return RuntimeValue.fromLv(constval) return RuntimeValue.fromLv(constval)
when(expr) { when(expr) {
is NumericLiteralValue -> { is NumericLiteralValue -> return RuntimeValue.fromLv(expr)
return RuntimeValue.fromLv(expr) is StringLiteralValue -> return RuntimeValue.fromLv(expr, ctx.program.heap)
} is ArrayLiteralValue -> return RuntimeValue.fromLv(expr, ctx.program.heap)
is ReferenceLiteralValue -> {
return RuntimeValue.fromLv(expr, ctx.program.heap)
}
is PrefixExpression -> { is PrefixExpression -> {
return when(expr.operator) { return when(expr.operator) {
"-" -> evaluate(expr.expression, ctx).neg() "-" -> evaluate(expr.expression, ctx).neg()
@ -150,24 +146,22 @@ fun evaluate(expr: Expression, ctx: EvalContext): RuntimeValue {
} }
is RangeExpr -> { is RangeExpr -> {
val cRange = expr.toConstantIntegerRange() val cRange = expr.toConstantIntegerRange()
if(cRange!=null) if(cRange!=null) {
return RuntimeValueRange(expr.inferType(ctx.program)!!, cRange) val dt = expr.inferType(ctx.program)
if(dt.isKnown)
return RuntimeValueRange(dt.typeOrElse(DataType.UBYTE), cRange)
else
throw VmExecutionException("couldn't determine datatype")
}
val fromVal = evaluate(expr.from, ctx).integerValue() val fromVal = evaluate(expr.from, ctx).integerValue()
val toVal = evaluate(expr.to, ctx).integerValue() val toVal = evaluate(expr.to, ctx).integerValue()
val stepVal = evaluate(expr.step, ctx).integerValue() val stepVal = evaluate(expr.step, ctx).integerValue()
val range = when { val range = makeRange(fromVal, toVal, stepVal)
fromVal <= toVal -> when { val dt = expr.inferType(ctx.program)
stepVal <= 0 -> IntRange.EMPTY if(dt.isKnown)
stepVal == 1 -> fromVal..toVal return RuntimeValueRange(dt.typeOrElse(DataType.UBYTE), range)
else -> fromVal..toVal step stepVal else
} throw VmExecutionException("couldn't determine datatype")
else -> when {
stepVal >= 0 -> IntRange.EMPTY
stepVal == -1 -> fromVal downTo toVal
else -> fromVal downTo toVal step abs(stepVal)
}
}
return RuntimeValueRange(expr.inferType(ctx.program)!!, range)
} }
else -> { else -> {
throw VmExecutionException("unimplemented expression node $expr") throw VmExecutionException("unimplemented expression node $expr")

View File

@ -5,8 +5,9 @@ import prog8.ast.base.DataType
import prog8.ast.base.Position import prog8.ast.base.Position
import prog8.ast.base.Register import prog8.ast.base.Register
import prog8.ast.base.VarDeclType import prog8.ast.base.VarDeclType
import prog8.ast.expressions.ArrayLiteralValue
import prog8.ast.expressions.NumericLiteralValue import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.ReferenceLiteralValue import prog8.ast.expressions.StringLiteralValue
import prog8.ast.processing.IAstModifyingVisitor import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.statements.Statement import prog8.ast.statements.Statement
import prog8.ast.statements.StructDecl import prog8.ast.statements.StructDecl
@ -50,8 +51,10 @@ class VariablesCreator(private val runtimeVariables: RuntimeVariables, private v
val value = if(numericLv!=null) { val value = if(numericLv!=null) {
RuntimeValue.fromLv(numericLv) RuntimeValue.fromLv(numericLv)
} else { } else {
val referenceLv = decl.value as ReferenceLiteralValue if(decl.value is StringLiteralValue)
RuntimeValue.fromLv(referenceLv, heap) RuntimeValue.fromLv(decl.value as StringLiteralValue, heap)
else
RuntimeValue.fromLv(decl.value as ArrayLiteralValue, heap)
} }
runtimeVariables.define(decl.definingScope(), decl.name, value) runtimeVariables.define(decl.definingScope(), decl.name, value)
} }

View File

@ -4,8 +4,9 @@ import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
import prog8.ast.base.DataType import prog8.ast.base.DataType
import prog8.ast.base.Position import prog8.ast.base.Position
import prog8.ast.expressions.ArrayLiteralValue
import prog8.ast.expressions.NumericLiteralValue import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.ReferenceLiteralValue import prog8.ast.expressions.StringLiteralValue
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFalse import kotlin.test.assertFalse
import kotlin.test.assertNotEquals import kotlin.test.assertNotEquals
@ -16,10 +17,6 @@ private fun sameValueAndType(lv1: NumericLiteralValue, lv2: NumericLiteralValue)
return lv1.type==lv2.type && lv1==lv2 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) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestParserNumericLiteralValue { class TestParserNumericLiteralValue {
@ -86,8 +83,8 @@ class TestParserNumericLiteralValue {
@Test @Test
fun testEqualsRef() { fun testEqualsRef() {
assertTrue(sameValueAndType(ReferenceLiteralValue(DataType.STR, str = "hello", position = dummyPos), ReferenceLiteralValue(DataType.STR, str = "hello", position = dummyPos))) assertTrue(StringLiteralValue(DataType.STR, "hello", position = dummyPos) == StringLiteralValue(DataType.STR, "hello", position = dummyPos))
assertFalse(sameValueAndType(ReferenceLiteralValue(DataType.STR, str = "hello", position = dummyPos), ReferenceLiteralValue(DataType.STR, str = "bye", position = dummyPos))) assertFalse(StringLiteralValue(DataType.STR, "hello", position = dummyPos) == StringLiteralValue(DataType.STR, "bye", position = dummyPos))
val lvOne = NumericLiteralValue(DataType.UBYTE, 1, dummyPos) val lvOne = NumericLiteralValue(DataType.UBYTE, 1, dummyPos)
val lvTwo = NumericLiteralValue(DataType.UBYTE, 2, dummyPos) val lvTwo = NumericLiteralValue(DataType.UBYTE, 2, dummyPos)
@ -96,9 +93,9 @@ class TestParserNumericLiteralValue {
val lvTwoR = NumericLiteralValue(DataType.UBYTE, 2, dummyPos) val lvTwoR = NumericLiteralValue(DataType.UBYTE, 2, dummyPos)
val lvThreeR = NumericLiteralValue(DataType.UBYTE, 3, dummyPos) val lvThreeR = NumericLiteralValue(DataType.UBYTE, 3, dummyPos)
val lvFour= NumericLiteralValue(DataType.UBYTE, 4, dummyPos) val lvFour= NumericLiteralValue(DataType.UBYTE, 4, dummyPos)
val lv1 = ReferenceLiteralValue(DataType.ARRAY_UB, array = arrayOf(lvOne, lvTwo, lvThree), position = dummyPos) val lv1 = ArrayLiteralValue(DataType.ARRAY_UB, arrayOf(lvOne, lvTwo, lvThree), position = dummyPos)
val lv2 = ReferenceLiteralValue(DataType.ARRAY_UB, array = arrayOf(lvOneR, lvTwoR, lvThreeR), position = dummyPos) val lv2 = ArrayLiteralValue(DataType.ARRAY_UB, arrayOf(lvOneR, lvTwoR, lvThreeR), position = dummyPos)
val lv3 = ReferenceLiteralValue(DataType.ARRAY_UB, array = arrayOf(lvOneR, lvTwoR, lvFour), position = dummyPos) val lv3 = ArrayLiteralValue(DataType.ARRAY_UB, arrayOf(lvOneR, lvTwoR, lvFour), position = dummyPos)
assertEquals(lv1, lv2) assertEquals(lv1, lv2)
assertNotEquals(lv1, lv3) assertNotEquals(lv1, lv3)
} }

View File

@ -8,7 +8,7 @@ import org.junit.jupiter.api.TestInstance
import prog8.ast.base.DataType import prog8.ast.base.DataType
import prog8.ast.base.Position import prog8.ast.base.Position
import prog8.ast.expressions.NumericLiteralValue import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.ReferenceLiteralValue import prog8.ast.expressions.StringLiteralValue
import prog8.compiler.* import prog8.compiler.*
import prog8.compiler.target.c64.MachineDefinition.C64Zeropage import prog8.compiler.target.c64.MachineDefinition.C64Zeropage
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_NEGATIVE import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_NEGATIVE
@ -169,7 +169,15 @@ class TestZeropage {
} }
} }
// TODO test dontuse option @Test
fun testZpDontuse() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false))
println(zp.free)
assertEquals(0, zp.available())
assertFailsWith<CompilerException> {
zp.allocate("", DataType.BYTE, null)
}
}
@Test @Test
fun testFreeSpaces() { fun testFreeSpaces() {
@ -363,8 +371,8 @@ class TestPetscii {
assertTrue(ten <= ten) assertTrue(ten <= ten)
assertFalse(ten < ten) assertFalse(ten < ten)
val abc = ReferenceLiteralValue(DataType.STR, str = "abc", position = Position("", 0, 0, 0)) val abc = StringLiteralValue(DataType.STR, "abc", position = Position("", 0, 0, 0))
val abd = ReferenceLiteralValue(DataType.STR, str = "abd", position = Position("", 0, 0, 0)) val abd = StringLiteralValue(DataType.STR, "abd", position = Position("", 0, 0, 0))
assertEquals(abc, abc) assertEquals(abc, abc)
assertTrue(abc!=abd) assertTrue(abc!=abd)
assertFalse(abc!=abc) assertFalse(abc!=abc)

View File

@ -158,6 +158,7 @@ Design principles and features
- The compiler tries to optimize the program and generated code, but hand-tuning of the - The compiler tries to optimize the program and generated code, but hand-tuning of the
performance or space-critical parts will likely still be required. This is supported by performance or space-critical parts will likely still be required. This is supported by
the ability to easily write embedded assembly code directly in the program source code. the ability to easily write embedded assembly code directly in the program source code.
- There are many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``
.. _requirements: .. _requirements:

View File

@ -727,6 +727,9 @@ lsb(x)
msb(x) msb(x)
Get the most significant byte of the word x. Get the most significant byte of the word x.
sgn(x)
Get the sign of the value. Result is -1, 0 or 1 (negative, zero, positive).
mkword(lsb, msb) mkword(lsb, msb)
Efficiently create a word value from two bytes (the lsb and the msb). Avoids multiplication and shifting. Efficiently create a word value from two bytes (the lsb and the msb). Avoids multiplication and shifting.

View File

@ -19,17 +19,6 @@ these should call optimized pieces of assembly code, so they run as fast as poss
For now, we have the ``memcopy``, ``memset`` and ``strlen`` builtin functions. For now, we have the ``memcopy``, ``memset`` and ``strlen`` builtin functions.
Fixes
^^^^^
fix asmsub parameters so this works::
asmsub aa(byte arg @ Y) -> clobbers() -> () {
byte local = arg ; @todo fix 'undefined symbol arg' that occurs here
A=44
}
More optimizations More optimizations
^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^
@ -38,11 +27,6 @@ Add more compiler optimizations to the existing ones.
- on the language AST level - on the language AST level
- on the final assembly source level - on the final assembly source level
- can the parameter passing to subroutines be optimized to avoid copying? - can the parameter passing to subroutines be optimized to avoid copying?
- subroutines with 1 or 2 byte args (or 1 word arg) should be converted to asm calling convention with the args in A/Y register
this requires rethinking the way parameters are represented, simply injecting vardecls to
declare local variables for them is not always correct anymore
- working subroutine inlining (taking care of vars and identifier refs to them) - working subroutine inlining (taking care of vars and identifier refs to them)
Also some library routines and code patterns could perhaps be optimized further Also some library routines and code patterns could perhaps be optimized further
@ -57,11 +41,16 @@ It could then even be moved into the zeropage to greatly reduce code size and sl
Or just move the LSB portion into a slab of the zeropage. Or just move the LSB portion into a slab of the zeropage.
Allocate a fixed word in ZP that is the TOS so we can operate on TOS directly Allocate a fixed word in ZP that is the TOS so we can always operate on TOS directly
without having to to index into the stack? without having to to index into the stack?
Misc Misc
^^^^ ^^^^
- are there any other missing instructions in the code generator? Add sort() function that can sort an array (ascending and descending)
Several ideas were discussed on my reddit post
https://www.reddit.com/r/programming/comments/alhj59/creating_a_programming_language_and_cross/

View File

@ -1,6 +1,8 @@
%import c64utils %import c64utils
%zeropage basicsafe %zeropage basicsafe
; TODO implement asm generation for all operation in here
main { main {
byte bb byte bb

74
examples/bdmusic-irq.p8 Normal file
View File

@ -0,0 +1,74 @@
%zeropage basicsafe
%import c64lib
main {
sub start() {
c64scr.print("playing the music from boulderdash,\nmade in 1984 by peter liepa.\n\n")
c64utils.set_rasterirq(60) ; enable raster irq
}
}
irq {
const ubyte waveform = %0001 ; triangle
ubyte note_index = 0
ubyte delay = 0
sub irq() {
c64.EXTCOL++
delay++
if delay >= 8 {
delay = 0
c64.AD1 = %00011010
c64.SR1 = %00000000
c64.AD2 = %00011010
c64.SR2 = %00000000
c64.MVOL = 15
uword note = notes[note_index]
note_index++
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
c64.CR2 = waveform <<4 | 0
c64.CR1 = waveform <<4 | 1
c64.CR2 = waveform <<4 | 1
}
c64.EXTCOL--
}
; details about the boulderdash music can be found here:
; https://www.elmerproductions.com/sp/peterb/sounds.html#Theme%20tune
uword[] notes = [
$1622, $1d26, $2229, $252e, $1424, $1f27, $2029, $2730,
$122a, $122c, $1e2e, $1231, $202c, $3337, $212d, $3135,
$1622, $162e, $161d, $1624, $1420, $1430, $1424, $1420,
$1622, $162e, $161d, $1624, $1e2a, $1e3a, $1e2e, $1e2a,
$142c, $142c, $141b, $1422, $1c28, $1c38, $1c2c, $1c28,
$111d, $292d, $111f, $292e, $0f27, $0f27, $1633, $1627,
$162e, $162e, $162e, $162e, $222e, $222e, $162e, $162e,
$142e, $142e, $142e, $142e, $202e, $202e, $142e, $142e,
$162e, $322e, $162e, $332e, $222e, $322e, $162e, $332e,
$142e, $322e, $142e, $332e, $202c, $302c, $142c, $312c,
$162e, $163a, $162e, $3538, $222e, $2237, $162e, $3135,
$142c, $1438, $142c, $1438, $202c, $2033, $142c, $1438,
$162e, $322e, $162e, $332e, $222e, $322e, $162e, $332e,
$142e, $322e, $142e, $332e, $202c, $302c, $142c, $312c,
$2e32, $292e, $2629, $2226, $2c30, $272c, $2427, $1420,
$3532, $322e, $2e29, $2926, $2730, $242c, $2027, $1420
]
uword[] music_freq_table = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
732, 778, 826, 876, 928, 978, 1042, 1100, 1170, 1238, 1312, 1390, 1464, 1556,
1652, 1752, 1856, 1956, 2084, 2200, 2340, 2476, 2624, 2780, 2928, 3112, 3304,
3504, 3712, 3912, 4168, 4400, 4680, 4952, 5248, 5560, 5856, 6224, 6608, 7008,
7424, 7824, 8336, 8800, 9360, 9904, 10496, 11120, 11712
]
}

View File

@ -1,6 +1,8 @@
%import c64lib %import c64lib
%import c64utils %import c64utils
; TODO: some optimizer breaks this.. runs fine without optimization
spritedata $2000 { spritedata $2000 {
; this memory block contains the sprite data ; this memory block contains the sprite data
; it must start on an address aligned to 64 bytes. ; it must start on an address aligned to 64 bytes.
@ -100,12 +102,12 @@ main {
; rotate around origin (0,0,0) ; rotate around origin (0,0,0)
; set up the 3d rotation matrix values ; set up the 3d rotation matrix values
word wcosa = cos8(ax) as word word wcosa = cos8(ax)
word wsina = sin8(ax) as word word wsina = sin8(ax)
word wcosb = cos8(ay) as word word wcosb = cos8(ay)
word wsinb = sin8(ay) as word word wsinb = sin8(ay)
word wcosc = cos8(az) as word word wcosc = cos8(az)
word wsinc = sin8(az) as word word wsinc = sin8(az)
word wcosa_sinb = wcosa*wsinb / 128 word wcosa_sinb = wcosa*wsinb / 128
word wsina_sinb = wsina*wsinb / 128 word wsina_sinb = wsina*wsinb / 128
@ -134,7 +136,8 @@ main {
; set each of the 8 sprites to the correct vertex of the cube ; set each of the 8 sprites to the correct vertex of the cube
; first sort vertices to sprite order so the back/front order is correct as well ; first sort vertices to sprite order so the back/front order is correct as well
; (chose to do a simple bubble sort it's only 8 items to sort) ; (simple bubble sort as it's only 8 items to sort)
; TODO make a builtin function sort()
for ubyte sorti in 6 to 0 step -1 { for ubyte sorti in 6 to 0 step -1 {
for ubyte i1 in 0 to sorti { for ubyte i1 in 0 to sorti {
ubyte i2 = i1+1 ubyte i2 = i1+1
@ -161,7 +164,7 @@ main {
else else
c64.SPRPTR[i] = $2000/64 ; small ball c64.SPRPTR[i] = $2000/64 ; small ball
c64.SPCOL[i] = spritecolors[zc>>13 as byte + 4] ; further away=darker color c64.SPCOL[i] = spritecolors[(zc>>13) as byte + 4] ; further away=darker color
} }
} }
} }

View File

@ -41,12 +41,12 @@ main {
; rotate around origin (0,0,0) ; rotate around origin (0,0,0)
; set up the 3d rotation matrix values ; set up the 3d rotation matrix values
word wcosa = cos8(ax) as word word wcosa = cos8(ax)
word wsina = sin8(ax) as word word wsina = sin8(ax)
word wcosb = cos8(ay) as word word wcosb = cos8(ay)
word wsinb = sin8(ay) as word word wsinb = sin8(ay)
word wcosc = cos8(az) as word word wcosc = cos8(az)
word wsinc = sin8(az) as word word wsinc = sin8(az)
word wcosa_sinb = wcosa*wsinb / 128 word wcosa_sinb = wcosa*wsinb / 128
word wsina_sinb = wsina*wsinb / 128 word wsina_sinb = wsina*wsinb / 128
@ -62,14 +62,13 @@ main {
word Azz = wcosb*wcosc / 128 word Azz = wcosb*wcosc / 128
for ubyte i in 0 to len(xcoor)-1 { for ubyte i in 0 to len(xcoor)-1 {
rotatedx[i] = (Axx*xcoor[i] + Axy*ycoor[i] + Axz*zcoor[i]) / 128 ; don't normalize by dividing by 128, instead keep some precision for perspective calc later
rotatedy[i] = (Ayx*xcoor[i] + Ayy*ycoor[i] + Ayz*zcoor[i]) / 128 rotatedx[i] = (Axx*xcoor[i] + Axy*ycoor[i] + Axz*zcoor[i])
rotatedz[i] = (Azx*xcoor[i] + Azy*ycoor[i] + Azz*zcoor[i]) / 128 rotatedy[i] = (Ayx*xcoor[i] + Ayy*ycoor[i] + Ayz*zcoor[i])
rotatedz[i] = (Azx*xcoor[i] + Azy*ycoor[i] + Azz*zcoor[i])
} }
} }
ubyte[] vertexcolors = [1,7,7,12,11,6]
sub draw_edges() { sub draw_edges() {
; plot the points of the 3d cube ; plot the points of the 3d cube
@ -80,24 +79,25 @@ main {
word persp word persp
byte sx byte sx
byte sy byte sy
ubyte color
for i in 0 to len(xcoor)-1 { for i in 0 to len(xcoor)-1 {
rz = rotatedz[i] rz = rotatedz[i]
if rz >= 10 { if rz >= 10 {
persp = (rz+200) / height persp = 900 + rz/32
sx = rotatedx[i] / persp as byte + width/2 sx = rotatedx[i] / persp as byte + width/2
sy = rotatedy[i] / persp as byte + height/2 sy = rotatedy[i] / persp as byte + height/2
c64scr.setcc(sx as ubyte, sy as ubyte, 46, vertexcolors[(rz as byte >>5) + 3]) c64scr.setcc(sx as ubyte, sy as ubyte, 46, 7)
} }
} }
for i in 0 to len(xcoor)-1 { for i in 0 to len(xcoor)-1 {
rz = rotatedz[i] rz = rotatedz[i]
if rz < 10 { if rz < 10 {
persp = (rz+200) / height persp = 900 + rz/32
sx = rotatedx[i] / persp as byte + width/2 sx = rotatedx[i] / persp as byte + width/2
sy = rotatedy[i] / persp as byte + height/2 sy = rotatedy[i] / persp as byte + height/2
c64scr.setcc(sx as ubyte, sy as ubyte, 81, vertexcolors[(rz as byte >>5) + 3]) c64scr.setcc(sx as ubyte, sy as ubyte, 81, 7)
} }
} }
} }

View File

@ -6,42 +6,78 @@
main { main {
sub start() { sub start() {
byte[] barr = [-100, 0, 99, -122, 22] byte ub = 100
ubyte[] ubarr = [100, 0, 99, 199, 22] byte ub2
word[] warr = [-1000, 0, 999, -4444, 222] word uw = 22222
uword[] uwarr = [1000, 0, 222, 4444, 999] word uw2
float[] farr = [-1000.1, 0, 999.9, -4444.4, 222.2]
str name = "irmen"
ubyte ub
byte bb
word ww
uword uw
float ff
; LEN/STRLEN ub = -100
ubyte length = len(name) c64scr.print_b(ub >> 1)
if(length!=5) c64scr.print("error len1\n") c64.CHROUT('\n')
length = len(uwarr) c64scr.print_b(ub >> 2)
if(length!=5) c64scr.print("error len2\n") c64.CHROUT('\n')
length=strlen(name) c64scr.print_b(ub >> 7)
if(length!=5) c64scr.print("error strlen1\n") c64.CHROUT('\n')
name[3] = 0 c64scr.print_b(ub >> 8)
length=strlen(name) c64.CHROUT('\n')
if(length!=3) c64scr.print("error strlen2\n") c64scr.print_b(ub >> 9)
c64.CHROUT('\n')
c64scr.print_b(ub >> 16)
c64.CHROUT('\n')
c64scr.print_b(ub >> 26)
c64.CHROUT('\n')
c64.CHROUT('\n')
; MAX ub = 100
; ub = max(ubarr) c64scr.print_b(ub >> 1)
; bb = max(barr) c64.CHROUT('\n')
; ww = max(warr) c64scr.print_b(ub >> 2)
; uw = max(uwarr) c64.CHROUT('\n')
; ff = max(farr) c64scr.print_b(ub >> 7)
c64.CHROUT('\n')
c64scr.print_b(ub >> 8)
c64.CHROUT('\n')
c64scr.print_b(ub >> 9)
c64.CHROUT('\n')
c64scr.print_b(ub >> 16)
c64.CHROUT('\n')
c64scr.print_b(ub >> 26)
c64.CHROUT('\n')
c64.CHROUT('\n')
; word ww = sum(barr)
; uword uw = sum(ubarr)
; ww = sum(warr)
; uw = sum(uwarr)
; float ff = sum(farr)
c64scr.print("\nyou should see no errors above.") uw = -22222
c64scr.print_w(uw >> 1)
c64.CHROUT('\n')
c64scr.print_w(uw >> 7)
c64.CHROUT('\n')
c64scr.print_w(uw >> 8)
c64.CHROUT('\n')
c64scr.print_w(uw >> 9)
c64.CHROUT('\n')
c64scr.print_w(uw >> 15)
c64.CHROUT('\n')
c64scr.print_w(uw >> 16)
c64.CHROUT('\n')
c64scr.print_w(uw >> 26)
c64.CHROUT('\n')
c64.CHROUT('\n')
uw = 22222
c64scr.print_w(uw >> 1)
c64.CHROUT('\n')
c64scr.print_w(uw >> 7)
c64.CHROUT('\n')
c64scr.print_w(uw >> 8)
c64.CHROUT('\n')
c64scr.print_w(uw >> 9)
c64.CHROUT('\n')
c64scr.print_w(uw >> 15)
c64.CHROUT('\n')
c64scr.print_w(uw >> 16)
c64.CHROUT('\n')
c64scr.print_w(uw >> 26)
c64.CHROUT('\n')
} }
} }

View File

@ -1,5 +1,7 @@
%zeropage basicsafe %zeropage basicsafe
; TODO implement asm generation for all loops here
main { main {
sub start() { sub start() {

View File

@ -7,6 +7,7 @@ repositories {
mavenCentral() mavenCentral()
} }
configurations { configurations {
// strange antlr plugin issue, see https://github.com/gradle/gradle/issues/820 // strange antlr plugin issue, see https://github.com/gradle/gradle/issues/820
// this avoids linking in the complete antlr binary jar // this avoids linking in the complete antlr binary jar

View File

@ -6,7 +6,7 @@
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/build" /> <excludeFolder url="file://$MODULE_DIR$/build" />
</content> </content>
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" /> <orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="antlr-4.7.2-complete" level="project" /> <orderEntry type="library" name="antlr-4.7.2-complete" level="project" />
</component> </component>