mirror of
https://github.com/irmen/prog8.git
synced 2025-06-16 09:23:43 +00:00
Compare commits
96 Commits
Author | SHA1 | Date | |
---|---|---|---|
6ed5f04970 | |||
b459b09b2f | |||
3f5877dbcc | |||
e659b91c4d | |||
e09f054058 | |||
b646f50265 | |||
0a48ef3030 | |||
ba614801ee | |||
fd6eb47e68 | |||
e69aeb8b98 | |||
26ea1da146 | |||
c9e8c7a290 | |||
5e4eb92443 | |||
461b6499ef | |||
c769920b6e | |||
181b98ef9e | |||
4e1184a400 | |||
e52d9e3210 | |||
dc6475c91b | |||
52f9956e92 | |||
0bf00d1ca4 | |||
d1a707df57 | |||
4dc9b45297 | |||
6e31eebfb5 | |||
a7df828932 | |||
517cf61d11 | |||
4be7bc8323 | |||
74c05d00a9 | |||
677613d30a | |||
bacba629a5 | |||
14e36f1362 | |||
d43ad849d1 | |||
627aa61184 | |||
dad5b17ac8 | |||
fef52c0112 | |||
8c4765b386 | |||
7c121bfc01 | |||
942c5cc04b | |||
348b3036ff | |||
09d3451d9d | |||
b1a49e5f29 | |||
da01a5b4dc | |||
3f9cdd9b56 | |||
0f9e87d7bb | |||
0869789214 | |||
10c8cc35c5 | |||
30c2e3e8ff | |||
86cc2f1075 | |||
fa357a450b | |||
b32641db87 | |||
0ee790969d | |||
7844ace934 | |||
f4993d6e5d | |||
0fab806f36 | |||
be2113d291 | |||
625d5b2313 | |||
6471c0c536 | |||
47c53fa60a | |||
cf50e4f6ec | |||
7eea97d741 | |||
88b55ab93e | |||
ee36d47c27 | |||
6f2fdbe447 | |||
0f36be0001 | |||
0f4a197e34 | |||
7dbff5b9e6 | |||
220246278a | |||
349e5a15e9 | |||
bf7f4bba7b | |||
ab1766a559 | |||
51bf33040a | |||
a2c7273801 | |||
ec6ac5bf24 | |||
ec7501782d | |||
890b1c2d52 | |||
c25d07259a | |||
c960246eee | |||
a01aee3111 | |||
e2e951efdf | |||
3f6393f732 | |||
b6eb343234 | |||
207a7e5160 | |||
a0face4a28 | |||
a8cf9f5cc4 | |||
461b38e653 | |||
8e4c0f7c22 | |||
d78bfcc35c | |||
2b7c09e6ee | |||
036d9dbe59 | |||
1d342cc6af | |||
62b32b2211 | |||
ae45ce517e | |||
5b3ccab7dc | |||
95f16c38a9 | |||
d616cb283b | |||
9874fe2c23 |
10
.idea/libraries/antlr_antlr4.xml
generated
10
.idea/libraries/antlr_antlr4.xml
generated
@ -1,15 +1,15 @@
|
||||
<component name="libraryTable">
|
||||
<library name="antlr.antlr4" type="repository">
|
||||
<properties maven-id="org.antlr:antlr4:4.9.3">
|
||||
<properties maven-id="org.antlr:antlr4:4.10.1">
|
||||
<exclude>
|
||||
<dependency maven-id="com.ibm.icu:icu4j" />
|
||||
</exclude>
|
||||
</properties>
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.9.3/antlr4-4.9.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.9.3/antlr4-runtime-4.9.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr-runtime/3.5.2/antlr-runtime-3.5.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/ST4/4.3.1/ST4-4.3.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.10.1/antlr4-4.10.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.10.1/antlr4-runtime-4.10.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr-runtime/3.5.3/antlr-runtime-3.5.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/ST4/4.3.3/ST4-4.3.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/abego/treelayout/org.abego.treelayout.core/1.0.3/org.abego.treelayout.core-1.0.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/javax.json/1.0.4/javax.json-1.0.4.jar!/" />
|
||||
</CLASSES>
|
||||
|
5
LICENSE
5
LICENSE
@ -1,6 +1,9 @@
|
||||
|
||||
This sofware license is for Prog8 the compiler + associated libraries.
|
||||
The software generated by running the compiler is excluded from this.
|
||||
|
||||
Any and all outputs generated by the compiler (intermediary
|
||||
source code and compiled binary programs) are excluded from that
|
||||
and you can do with them whatever you want.
|
||||
|
||||
|
||||
|
||||
|
@ -30,6 +30,10 @@ sealed class PtNode(val position: Position) {
|
||||
children.add(index, child)
|
||||
child.parent = this
|
||||
}
|
||||
|
||||
fun definingBlock() = findParentNode<PtBlock>(this)
|
||||
fun definingSub() = findParentNode<PtSub>(this)
|
||||
fun definingAsmSub() = findParentNode<PtAsmSub>(this)
|
||||
}
|
||||
|
||||
|
||||
@ -76,10 +80,18 @@ class PtProgram(
|
||||
class PtBlock(name: String,
|
||||
val address: UInt?,
|
||||
val library: Boolean,
|
||||
val forceOutput: Boolean,
|
||||
val alignment: BlockAlignment,
|
||||
position: Position
|
||||
) : PtNamedNode(name, position) {
|
||||
override fun printProperties() {
|
||||
print("$name addr=$address library=$library")
|
||||
print("$name addr=$address library=$library forceOutput=$forceOutput alignment=$alignment")
|
||||
}
|
||||
|
||||
enum class BlockAlignment {
|
||||
NONE,
|
||||
WORD,
|
||||
PAGE
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,3 +128,17 @@ class PtNop(position: Position): PtNode(position) {
|
||||
class PtScopeVarsDecls(position: Position): PtNode(position) {
|
||||
override fun printProperties() {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// find the parent node of a specific type or interface
|
||||
// (useful to figure out in what namespace/block something is defined, etc.)
|
||||
inline fun <reified T> findParentNode(node: PtNode): T? {
|
||||
var candidate = node.parent
|
||||
while(candidate !is T && candidate !is PtProgram)
|
||||
candidate = candidate.parent
|
||||
return if(candidate is PtProgram)
|
||||
null
|
||||
else
|
||||
candidate as T
|
||||
}
|
@ -3,14 +3,32 @@ package prog8.code.ast
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.Encoding
|
||||
import prog8.code.core.Position
|
||||
import java.util.*
|
||||
import kotlin.math.round
|
||||
|
||||
|
||||
sealed class PtExpression(val type: DataType, position: Position) : PtNode(position) {
|
||||
override fun printProperties() {
|
||||
print(type)
|
||||
}
|
||||
}
|
||||
|
||||
infix fun isSameAs(other: PtExpression): Boolean {
|
||||
return when(this) {
|
||||
is PtAddressOf -> other is PtAddressOf && other.type==type && other.identifier isSameAs identifier
|
||||
is PtArrayIndexer -> other is PtArrayIndexer && other.type==type && other.variable isSameAs variable && other.index isSameAs index
|
||||
is PtBinaryExpression -> other is PtBinaryExpression && other.left isSameAs left && other.right isSameAs right
|
||||
is PtContainmentCheck -> other is PtContainmentCheck && other.type==type && other.element isSameAs element && other.iterable isSameAs iterable
|
||||
is PtIdentifier -> other is PtIdentifier && other.type==type && other.targetName==targetName
|
||||
is PtMachineRegister -> other is PtMachineRegister && other.type==type && other.register==register
|
||||
is PtMemoryByte -> other is PtMemoryByte && other.address isSameAs address
|
||||
is PtNumber -> other is PtNumber && other.type==type && other.number==number
|
||||
is PtPrefix -> other is PtPrefix && other.type==type && other.operator==operator && other.value isSameAs value
|
||||
is PtRange -> other is PtRange && other.type==type && other.from==from && other.to==to && other.step==step
|
||||
is PtTypeCast -> other is PtTypeCast && other.type==type && other.value isSameAs value
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PtAddressOf(position: Position) : PtExpression(DataType.UWORD, position) {
|
||||
val identifier: PtIdentifier
|
||||
@ -26,10 +44,21 @@ class PtArrayIndexer(type: DataType, position: Position): PtExpression(type, pos
|
||||
}
|
||||
|
||||
|
||||
class PtArray(type: DataType, position: Position): PtExpression(type, position)
|
||||
class PtArray(type: DataType, position: Position): PtExpression(type, position) {
|
||||
override fun hashCode(): Int = Objects.hash(children, type)
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if(other==null || other !is PtArray)
|
||||
return false
|
||||
return type==other.type && children == other.children
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PtBuiltinFunctionCall(val name: String, val void: Boolean, type: DataType, position: Position) : PtExpression(type, position) {
|
||||
class PtBuiltinFunctionCall(val name: String,
|
||||
val void: Boolean,
|
||||
val hasNoSideEffects: Boolean,
|
||||
type: DataType,
|
||||
position: Position) : PtExpression(type, position) {
|
||||
init {
|
||||
if(!void)
|
||||
require(type!=DataType.UNDEFINED)
|
||||
@ -38,7 +67,7 @@ class PtBuiltinFunctionCall(val name: String, val void: Boolean, type: DataType,
|
||||
val args: List<PtExpression>
|
||||
get() = children.map { it as PtExpression }
|
||||
override fun printProperties() {
|
||||
print("$name void=$void")
|
||||
print("$name void=$void noSideFx=$hasNoSideEffects")
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,9 +124,28 @@ class PtMemoryByte(position: Position) : PtExpression(DataType.UBYTE, position)
|
||||
|
||||
|
||||
class PtNumber(type: DataType, val number: Double, position: Position) : PtExpression(type, position) {
|
||||
|
||||
init {
|
||||
if(type!=DataType.FLOAT) {
|
||||
val rounded = round(number)
|
||||
if (rounded != number)
|
||||
throw IllegalArgumentException("refused rounding of float to avoid loss of precision")
|
||||
}
|
||||
}
|
||||
|
||||
override fun printProperties() {
|
||||
print("$number ($type)")
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = Objects.hash(type, number)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if(other==null || other !is PtNumber)
|
||||
return false
|
||||
return number==other.number
|
||||
}
|
||||
|
||||
operator fun compareTo(other: PtNumber): Int = number.compareTo(other.number)
|
||||
}
|
||||
|
||||
|
||||
@ -107,6 +155,9 @@ class PtPipe(type: DataType, val void: Boolean, position: Position) : PtExpressi
|
||||
require(type!=DataType.UNDEFINED)
|
||||
}
|
||||
|
||||
val segments: List<PtExpression>
|
||||
get() = children.map { it as PtExpression }
|
||||
|
||||
override fun printProperties() {}
|
||||
}
|
||||
|
||||
@ -137,6 +188,13 @@ class PtString(val value: String, val encoding: Encoding, position: Position) :
|
||||
override fun printProperties() {
|
||||
print("$encoding:\"$value\"")
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = Objects.hash(value, encoding)
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if(other==null || other !is PtString)
|
||||
return false
|
||||
return value==other.value && encoding == other.encoding
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -144,3 +202,15 @@ class PtTypeCast(type: DataType, position: Position) : PtExpression(type, positi
|
||||
val value: PtExpression
|
||||
get() = children.single() as PtExpression
|
||||
}
|
||||
|
||||
|
||||
// special node that isn't created from compiling user code, but used internally
|
||||
class PtMachineRegister(val register: Int, type: DataType, position: Position) : PtExpression(type, position) {
|
||||
override fun printProperties() {
|
||||
print("reg=$register $type")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun constValue(expr: PtExpression): Double? = if(expr is PtNumber) expr.number else null
|
||||
fun constIntValue(expr: PtExpression): Int? = if(expr is PtNumber) expr.number.toInt() else null
|
||||
|
@ -38,18 +38,43 @@ class PtSubroutineParameter(val name: String, val type: DataType, position: Posi
|
||||
}
|
||||
|
||||
|
||||
class PtAssignment(val augmentable: Boolean, position: Position) : PtNode(position) {
|
||||
class PtAssignment(position: Position) : PtNode(position) {
|
||||
val target: PtAssignTarget
|
||||
get() = children[0] as PtAssignTarget
|
||||
val value: PtExpression
|
||||
get() = children[1] as PtExpression
|
||||
|
||||
override fun printProperties() {
|
||||
print("aug=$augmentable")
|
||||
override fun printProperties() { }
|
||||
|
||||
val isInplaceAssign: Boolean by lazy {
|
||||
val target = target.children.single() as PtExpression
|
||||
when(val source = value) {
|
||||
is PtArrayIndexer -> {
|
||||
if(target is PtArrayIndexer && source.type==target.type) {
|
||||
if(target.variable isSameAs source.variable) {
|
||||
target.index isSameAs source.index
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
is PtIdentifier -> target is PtIdentifier && target.type==source.type && target.targetName==source.targetName
|
||||
is PtMachineRegister -> target is PtMachineRegister && target.register==source.register
|
||||
is PtMemoryByte -> target is PtMemoryByte && target.address isSameAs source.address
|
||||
is PtNumber -> target is PtNumber && target.type == source.type && target.number==source.number
|
||||
is PtAddressOf -> target is PtAddressOf && target.identifier isSameAs source.identifier
|
||||
is PtPrefix -> {
|
||||
(target is PtPrefix && target.operator==source.operator && target.value isSameAs source.value)
|
||||
||
|
||||
(target is PtIdentifier && (source.value as? PtIdentifier)?.targetName==target.targetName)
|
||||
}
|
||||
is PtTypeCast -> target is PtTypeCast && target.type==source.type && target.value isSameAs source.value
|
||||
is PtBinaryExpression ->
|
||||
target isSameAs source.left
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PtAssignTarget(position: Position) : PtNode(position) {
|
||||
val identifier: PtIdentifier?
|
||||
get() = children.single() as? PtIdentifier
|
||||
@ -62,9 +87,9 @@ class PtAssignTarget(position: Position) : PtNode(position) {
|
||||
get() {
|
||||
return when(val tgt = children.single()) {
|
||||
is PtIdentifier -> tgt.type
|
||||
is PtArrayIndexer -> tgt.type // TODO array to elt type?
|
||||
is PtArrayIndexer -> tgt.type
|
||||
is PtMemoryByte -> tgt.type
|
||||
else -> throw AssemblyError("weird dt")
|
||||
else -> throw AssemblyError("weird target $tgt")
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,8 +122,8 @@ class PtForLoop(position: Position) : PtNode(position) {
|
||||
|
||||
|
||||
class PtIfElse(position: Position) : PtNode(position) {
|
||||
val condition: PtExpression
|
||||
get() = children[0] as PtExpression
|
||||
val condition: PtBinaryExpression
|
||||
get() = children[0] as PtBinaryExpression
|
||||
val ifScope: PtNodeGroup
|
||||
get() = children[1] as PtNodeGroup
|
||||
val elseScope: PtNodeGroup
|
||||
|
@ -24,7 +24,7 @@ compileTestKotlin {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':virtualmachine')
|
||||
// should have no dependencies to other modules
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.14"
|
||||
}
|
||||
|
@ -10,6 +10,5 @@
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
<orderEntry type="module" module-name="virtualmachine" />
|
||||
</component>
|
||||
</module>
|
89
codeCore/src/prog8/code/core/Conversions.kt
Normal file
89
codeCore/src/prog8/code/core/Conversions.kt
Normal file
@ -0,0 +1,89 @@
|
||||
package prog8.code.core
|
||||
|
||||
import kotlin.math.abs
|
||||
|
||||
fun Number.toHex(): String {
|
||||
// 0..15 -> "0".."15"
|
||||
// 16..255 -> "$10".."$ff"
|
||||
// 256..65536 -> "$0100".."$ffff"
|
||||
// negative values are prefixed with '-'.
|
||||
val integer = this.toInt()
|
||||
if(integer<0)
|
||||
return '-' + abs(integer).toHex()
|
||||
return when (integer) {
|
||||
in 0 until 16 -> integer.toString()
|
||||
in 0 until 0x100 -> "$"+integer.toString(16).padStart(2,'0')
|
||||
in 0 until 0x10000 -> "$"+integer.toString(16).padStart(4,'0')
|
||||
else -> throw IllegalArgumentException("number too large for 16 bits $this")
|
||||
}
|
||||
}
|
||||
|
||||
fun UInt.toHex(): String {
|
||||
// 0..15 -> "0".."15"
|
||||
// 16..255 -> "$10".."$ff"
|
||||
// 256..65536 -> "$0100".."$ffff"
|
||||
return when (this) {
|
||||
in 0u until 16u -> this.toString()
|
||||
in 0u until 0x100u -> "$"+this.toString(16).padStart(2,'0')
|
||||
in 0u until 0x10000u -> "$"+this.toString(16).padStart(4,'0')
|
||||
else -> throw IllegalArgumentException("number too large for 16 bits $this")
|
||||
}
|
||||
}
|
||||
|
||||
fun Char.escape(): Char = this.toString().escape()[0]
|
||||
|
||||
fun String.escape(): String {
|
||||
val es = this.map {
|
||||
when(it) {
|
||||
'\t' -> "\\t"
|
||||
'\n' -> "\\n"
|
||||
'\r' -> "\\r"
|
||||
'"' -> "\\\""
|
||||
in '\u8000'..'\u80ff' -> "\\x" + (it.code - 0x8000).toString(16).padStart(2, '0') // 'ugly' passthrough hack
|
||||
in '\u0000'..'\u00ff' -> it.toString()
|
||||
else -> "\\u" + it.code.toString(16).padStart(4, '0')
|
||||
}
|
||||
}
|
||||
return es.joinToString("")
|
||||
}
|
||||
|
||||
fun String.unescape(): String {
|
||||
val result = mutableListOf<Char>()
|
||||
val iter = this.iterator()
|
||||
while(iter.hasNext()) {
|
||||
val c = iter.nextChar()
|
||||
if(c=='\\') {
|
||||
val ec = iter.nextChar()
|
||||
result.add(when(ec) {
|
||||
'\\' -> '\\'
|
||||
'n' -> '\n'
|
||||
'r' -> '\r'
|
||||
'"' -> '"'
|
||||
'\'' -> '\''
|
||||
'u' -> {
|
||||
try {
|
||||
"${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}".toInt(16).toChar()
|
||||
} catch (sb: StringIndexOutOfBoundsException) {
|
||||
throw IllegalArgumentException("invalid \\u escape sequence")
|
||||
} catch (nf: NumberFormatException) {
|
||||
throw IllegalArgumentException("invalid \\u escape sequence")
|
||||
}
|
||||
}
|
||||
'x' -> {
|
||||
try {
|
||||
val hex = ("" + iter.nextChar() + iter.nextChar()).toInt(16)
|
||||
(0x8000 + hex).toChar() // 'ugly' pass-through hack
|
||||
} catch (sb: StringIndexOutOfBoundsException) {
|
||||
throw IllegalArgumentException("invalid \\x escape sequence")
|
||||
} catch (nf: NumberFormatException) {
|
||||
throw IllegalArgumentException("invalid \\x escape sequence")
|
||||
}
|
||||
}
|
||||
else -> throw IllegalArgumentException("invalid escape char in string: \\$ec")
|
||||
})
|
||||
} else {
|
||||
result.add(c)
|
||||
}
|
||||
}
|
||||
return result.joinToString("")
|
||||
}
|
@ -102,16 +102,16 @@ enum class Statusflag {
|
||||
enum class BranchCondition {
|
||||
CS,
|
||||
CC,
|
||||
EQ,
|
||||
EQ, // EQ == Z
|
||||
Z,
|
||||
NE,
|
||||
NE, // NE == NZ
|
||||
NZ,
|
||||
MI, // MI == NEG
|
||||
NEG,
|
||||
PL, // PL == POS
|
||||
POS,
|
||||
VS,
|
||||
VC,
|
||||
MI,
|
||||
NEG,
|
||||
PL,
|
||||
POS
|
||||
}
|
||||
|
||||
|
||||
@ -120,6 +120,7 @@ val WordDatatypes = arrayOf(DataType.UWORD, DataType.WORD)
|
||||
val IntegerDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD)
|
||||
val NumericDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT)
|
||||
val SignedDatatypes = arrayOf(DataType.BYTE, DataType.WORD, DataType.FLOAT)
|
||||
val IntegerArrayDatatypes = arrayOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W)
|
||||
val ArrayDatatypes = arrayOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F)
|
||||
val StringlyDatatypes = arrayOf(DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B, DataType.UWORD)
|
||||
val IterableDatatypes = arrayOf(
|
||||
|
18
codeCore/src/prog8/code/core/Operators.kt
Normal file
18
codeCore/src/prog8/code/core/Operators.kt
Normal file
@ -0,0 +1,18 @@
|
||||
package prog8.code.core
|
||||
|
||||
val AssociativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "==", "!=")
|
||||
val ComparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
|
||||
val AugmentAssignmentOperators = setOf("+", "-", "/", "*", "&", "|", "^", "<<", ">>", "%", "and", "or", "xor")
|
||||
val LogicalOperators = setOf("and", "or", "xor", "not")
|
||||
val BitwiseOperators = setOf("&", "|", "^")
|
||||
|
||||
fun invertedComparisonOperator(operator: String) =
|
||||
when (operator) {
|
||||
"==" -> "!="
|
||||
"!=" -> "=="
|
||||
"<" -> ">="
|
||||
">" -> "<="
|
||||
"<=" -> ">"
|
||||
">=" -> "<"
|
||||
else -> null
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
package prog8.code.core
|
||||
|
||||
import kotlin.math.abs
|
||||
|
||||
fun Number.toHex(): String {
|
||||
// 0..15 -> "0".."15"
|
||||
// 16..255 -> "$10".."$ff"
|
||||
// 256..65536 -> "$0100".."$ffff"
|
||||
// negative values are prefixed with '-'.
|
||||
val integer = this.toInt()
|
||||
if(integer<0)
|
||||
return '-' + abs(integer).toHex()
|
||||
return when (integer) {
|
||||
in 0 until 16 -> integer.toString()
|
||||
in 0 until 0x100 -> "$"+integer.toString(16).padStart(2,'0')
|
||||
in 0 until 0x10000 -> "$"+integer.toString(16).padStart(4,'0')
|
||||
else -> throw IllegalArgumentException("number too large for 16 bits $this")
|
||||
}
|
||||
}
|
||||
|
||||
fun UInt.toHex(): String {
|
||||
// 0..15 -> "0".."15"
|
||||
// 16..255 -> "$10".."$ff"
|
||||
// 256..65536 -> "$0100".."$ffff"
|
||||
return when (this) {
|
||||
in 0u until 16u -> this.toString()
|
||||
in 0u until 0x100u -> "$"+this.toString(16).padStart(2,'0')
|
||||
in 0u until 0x10000u -> "$"+this.toString(16).padStart(4,'0')
|
||||
else -> throw IllegalArgumentException("number too large for 16 bits $this")
|
||||
}
|
||||
}
|
@ -4,9 +4,6 @@ import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.CpuType
|
||||
import prog8.code.core.IMachineDefinition
|
||||
import prog8.code.core.Zeropage
|
||||
import prog8.vm.Assembler
|
||||
import prog8.vm.Memory
|
||||
import prog8.vm.VirtualMachine
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
|
||||
@ -17,7 +14,7 @@ class VirtualMachineDefinition: IMachineDefinition {
|
||||
|
||||
override val FLOAT_MAX_POSITIVE = Float.MAX_VALUE.toDouble()
|
||||
override val FLOAT_MAX_NEGATIVE = -Float.MAX_VALUE.toDouble()
|
||||
override val FLOAT_MEM_SIZE = 4
|
||||
override val FLOAT_MEM_SIZE = 4 // 32-bits floating point
|
||||
override val PROGRAM_LOAD_ADDRESS = 0u // not actually used
|
||||
|
||||
override val ESTACK_LO = 0u // not actually used
|
||||
@ -33,14 +30,10 @@ class VirtualMachineDefinition: IMachineDefinition {
|
||||
|
||||
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
|
||||
println("\nStarting Virtual Machine...")
|
||||
// to not have external module dependencies we launch the virtual machine via reflection
|
||||
val vm = Class.forName("prog8.vm.VmRunner").getDeclaredConstructor().newInstance() as IVirtualMachineRunner
|
||||
val source = File("$programNameWithPath.p8virt").readText()
|
||||
val (memsrc, programsrc) = source.split("------PROGRAM------".toRegex(), 2)
|
||||
val memory = Memory()
|
||||
val assembler = Assembler()
|
||||
assembler.initializeMemory(memsrc, memory)
|
||||
val program = assembler.assembleProgram(programsrc)
|
||||
val vm = VirtualMachine(memory, program)
|
||||
vm.run(throttle = true)
|
||||
vm.runProgram(source, true)
|
||||
}
|
||||
|
||||
override fun isIOAddress(address: UInt): Boolean = false
|
||||
@ -49,3 +42,7 @@ class VirtualMachineDefinition: IMachineDefinition {
|
||||
|
||||
override val opcodeNames = emptySet<String>()
|
||||
}
|
||||
|
||||
interface IVirtualMachineRunner {
|
||||
fun runProgram(source: String, throttle: Boolean)
|
||||
}
|
||||
|
@ -1,7 +1,10 @@
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import com.github.michaelbull.result.fold
|
||||
import prog8.ast.*
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.ParentSentinel
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
@ -10,7 +13,6 @@ import prog8.code.core.*
|
||||
import prog8.codegen.cpu6502.assignment.*
|
||||
import prog8.compiler.BuiltinFunctions
|
||||
import prog8.compiler.builtinFunctionReturnType
|
||||
import prog8.code.core.SourceCode
|
||||
import java.util.*
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.writeLines
|
||||
@ -442,11 +444,11 @@ class AsmGen(internal val program: Program,
|
||||
internal fun translateBuiltinFunctionCallExpression(bfc: BuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) =
|
||||
builtinFunctionsAsmGen.translateFunctioncallExpression(bfc, resultToStack, resultRegister)
|
||||
|
||||
private fun translateBuiltinFunctionCallExpression(name: String, singleArg:AsmAssignSource, scope: Subroutine): DataType =
|
||||
builtinFunctionsAsmGen.translateUnaryFunctioncall(name, singleArg, false, scope)
|
||||
private fun translateBuiltinFunctionCallExpression(bfc: IFunctionCall, firstArg: AsmAssignSource, scope: Subroutine): DataType =
|
||||
builtinFunctionsAsmGen.translateFunctionCallWithFirstArg(bfc, firstArg, false, scope)
|
||||
|
||||
private fun translateBuiltinFunctionCallStatement(name: String, singleArg: AsmAssignSource, scope: Subroutine) =
|
||||
builtinFunctionsAsmGen.translateUnaryFunctioncall(name, singleArg, true, scope)
|
||||
private fun translateBuiltinFunctionCallStatement(bfc: IFunctionCall, firstArg: AsmAssignSource, scope: Subroutine) =
|
||||
builtinFunctionsAsmGen.translateFunctionCallWithFirstArg(bfc, firstArg, true, scope)
|
||||
|
||||
internal fun translateFunctionCall(functionCallExpr: FunctionCallExpression, isExpression: Boolean) =
|
||||
functioncallAsmGen.translateFunctionCall(functionCallExpr, isExpression)
|
||||
@ -947,7 +949,7 @@ $repeatLabel lda $counterVar
|
||||
assemblyLines.add(assembly)
|
||||
}
|
||||
|
||||
internal fun returnRegisterOfFunction(it: IdentifierReference, argumentTypesForBuiltinFunc: List<DataType>?): RegisterOrPair {
|
||||
internal fun returnRegisterOfFunction(it: IdentifierReference): RegisterOrPair {
|
||||
return when (val targetRoutine = it.targetStatement(program)!!) {
|
||||
is BuiltinFunctionPlaceholder -> {
|
||||
val func = BuiltinFunctions.getValue(targetRoutine.name)
|
||||
@ -956,16 +958,11 @@ $repeatLabel lda $counterVar
|
||||
in WordDatatypes -> RegisterOrPair.AY
|
||||
DataType.FLOAT -> RegisterOrPair.FAC1
|
||||
else -> {
|
||||
if(!func.hasReturn)
|
||||
throw AssemblyError("func has no returntype")
|
||||
else {
|
||||
val args = argumentTypesForBuiltinFunc!!.map { defaultZero(it, Position.DUMMY) }
|
||||
when(builtinFunctionReturnType(func.name, args, program).getOrElse { DataType.UNDEFINED }) {
|
||||
in ByteDatatypes -> RegisterOrPair.A
|
||||
in WordDatatypes -> RegisterOrPair.AY
|
||||
DataType.FLOAT -> RegisterOrPair.FAC1
|
||||
else -> throw AssemblyError("weird returntype")
|
||||
}
|
||||
when(builtinFunctionReturnType(func.name).getOrElse { DataType.UNDEFINED }) {
|
||||
in ByteDatatypes -> RegisterOrPair.A
|
||||
in WordDatatypes -> RegisterOrPair.AY
|
||||
DataType.FLOAT -> RegisterOrPair.FAC1
|
||||
else -> throw AssemblyError("weird returntype")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2827,27 +2824,27 @@ $repeatLabel lda $counterVar
|
||||
var valueDt = source.inferType(program).getOrElse { throw FatalAstException("invalid dt") }
|
||||
var valueSource: AsmAssignSource =
|
||||
if(source is IFunctionCall) {
|
||||
val resultReg = returnRegisterOfFunction(source.target, listOf(valueDt))
|
||||
val resultReg = returnRegisterOfFunction(source.target)
|
||||
assignExpressionToRegister(source, resultReg, valueDt in listOf(DataType.BYTE, DataType.WORD, DataType.FLOAT))
|
||||
AsmAssignSource(SourceStorageKind.REGISTER, program, this, valueDt, register = resultReg)
|
||||
} else {
|
||||
AsmAssignSource.fromAstSource(source, program, this)
|
||||
}
|
||||
|
||||
// the segments (except the last one): unary function calls taking a single param and producing a value.
|
||||
// directly assign their argument from the previous call's returnvalue.
|
||||
// the segments (except the last one): function calls taking one or more parameters and producing a value.
|
||||
// directly assign their first argument from the previous call's returnvalue (and take the rest, if any, from the call itself)
|
||||
segments.dropLast(1).forEach {
|
||||
it as IFunctionCall
|
||||
valueDt = translateUnaryFunctionCallWithArgSource(it.target, valueSource, false, subroutine)
|
||||
val resultReg = returnRegisterOfFunction(it.target, listOf(valueDt))
|
||||
valueDt = translateFunctionCallWithFirstArg(it, valueSource, false, subroutine)
|
||||
val resultReg = returnRegisterOfFunction(it.target)
|
||||
valueSource = AsmAssignSource(SourceStorageKind.REGISTER, program, this, valueDt, register = resultReg)
|
||||
}
|
||||
// the last segment: unary function call taking a single param and optionally producing a result value.
|
||||
// the last segment: function call taking one or more parameters and optionally producing a result value.
|
||||
val lastCall = segments.last() as IFunctionCall
|
||||
if(isStatement) {
|
||||
translateUnaryFunctionCallWithArgSource(lastCall.target, valueSource, true, subroutine)
|
||||
translateFunctionCallWithFirstArg(lastCall, valueSource, true, subroutine)
|
||||
} else {
|
||||
valueDt = translateUnaryFunctionCallWithArgSource(lastCall.target, valueSource, false, subroutine)
|
||||
valueDt = translateFunctionCallWithFirstArg(lastCall, valueSource, false, subroutine)
|
||||
if(pushResultOnEstack) {
|
||||
when (valueDt) {
|
||||
in ByteDatatypes -> {
|
||||
@ -2865,14 +2862,23 @@ $repeatLabel lda $counterVar
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateUnaryFunctionCallWithArgSource(target: IdentifierReference, singleArg: AsmAssignSource, isStatement: Boolean, scope: Subroutine): DataType {
|
||||
when(val targetStmt = target.targetStatement(program)!!) {
|
||||
private fun translateFunctionCallWithFirstArg(
|
||||
fcall: IFunctionCall,
|
||||
firstArg: AsmAssignSource,
|
||||
isStatement: Boolean,
|
||||
scope: Subroutine
|
||||
): DataType {
|
||||
|
||||
if(fcall.args.isNotEmpty())
|
||||
TODO("deal with additional args (non-unary function): ${fcall.target.nameInSource} (... , ${fcall.args.joinToString(", ")})")
|
||||
|
||||
when(val targetStmt = fcall.target.targetStatement(program)!!) {
|
||||
is BuiltinFunctionPlaceholder -> {
|
||||
return if(isStatement) {
|
||||
translateBuiltinFunctionCallStatement(targetStmt.name, singleArg, scope)
|
||||
translateBuiltinFunctionCallStatement(fcall, firstArg, scope)
|
||||
DataType.UNDEFINED
|
||||
} else {
|
||||
translateBuiltinFunctionCallExpression(targetStmt.name, singleArg, scope)
|
||||
translateBuiltinFunctionCallExpression(fcall, firstArg, scope)
|
||||
}
|
||||
}
|
||||
is Subroutine -> {
|
||||
@ -2881,9 +2887,9 @@ $repeatLabel lda $counterVar
|
||||
// argument via registers
|
||||
val argRegister = targetStmt.asmParameterRegisters.single().registerOrPair!!
|
||||
val assignArgument = AsmAssignment(
|
||||
singleArg,
|
||||
firstArg,
|
||||
AsmAssignTarget.fromRegisters(argRegister, argDt in SignedDatatypes, scope, program, this),
|
||||
false, program.memsizer, target.position
|
||||
false, program.memsizer, fcall.position
|
||||
)
|
||||
translateNormalAssignment(assignArgument)
|
||||
} else {
|
||||
@ -2897,24 +2903,24 @@ $repeatLabel lda $counterVar
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
AsmAssignment(
|
||||
singleArg,
|
||||
firstArg,
|
||||
AsmAssignTarget(TargetStorageKind.REGISTER, program, this, argDt, scope, register = paramReg),
|
||||
false, program.memsizer, target.position
|
||||
false, program.memsizer, fcall.position
|
||||
)
|
||||
} else {
|
||||
// arg goes via parameter variable
|
||||
val argVarName = asmVariableName(targetStmt.scopedName + targetStmt.parameters.single().name)
|
||||
AsmAssignment(
|
||||
singleArg,
|
||||
firstArg,
|
||||
AsmAssignTarget(TargetStorageKind.VARIABLE, program, this, argDt, scope, argVarName),
|
||||
false, program.memsizer, target.position
|
||||
false, program.memsizer, fcall.position
|
||||
)
|
||||
}
|
||||
translateNormalAssignment(assignArgument)
|
||||
}
|
||||
if(targetStmt.shouldSaveX())
|
||||
saveRegisterLocal(CpuRegister.X, scope)
|
||||
out(" jsr ${asmSymbolName(target)}")
|
||||
out(" jsr ${asmSymbolName(fcall.target)}")
|
||||
if(targetStmt.shouldSaveX())
|
||||
restoreRegisterLocal(CpuRegister.X)
|
||||
return if(isStatement) DataType.UNDEFINED else targetStmt.returntypes.single()
|
||||
|
@ -4,7 +4,6 @@ import com.github.michaelbull.result.Ok
|
||||
import com.github.michaelbull.result.Result
|
||||
import com.github.michaelbull.result.mapError
|
||||
import prog8.code.core.*
|
||||
import prog8.code.core.SourceCode
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.Path
|
||||
|
@ -30,7 +30,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
translateFunctioncall(fcall, func, discardResult = true, resultToStack = false, resultRegister = null)
|
||||
}
|
||||
|
||||
internal fun translateUnaryFunctioncall(name: String, singleArg: AsmAssignSource, isStatement: Boolean, scope: Subroutine): DataType {
|
||||
internal fun translateFunctionCallWithFirstArg(bfc: IFunctionCall, singleArg: AsmAssignSource, isStatement: Boolean, scope: Subroutine): DataType {
|
||||
val name = bfc.target.nameInSource.single()
|
||||
val func = BuiltinFunctions.getValue(name)
|
||||
val argExpression =
|
||||
when(singleArg.kind) {
|
||||
@ -56,14 +57,14 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
ident
|
||||
}
|
||||
}
|
||||
val argExpressions = mutableListOf(argExpression);
|
||||
val argExpressions = mutableListOf(argExpression)
|
||||
val fcall = BuiltinFunctionCall(IdentifierReference(listOf(name), Position.DUMMY), argExpressions, Position.DUMMY)
|
||||
fcall.linkParents(scope)
|
||||
translateFunctioncall(fcall, func, discardResult = false, resultToStack = false, null)
|
||||
return if(isStatement) {
|
||||
DataType.UNDEFINED
|
||||
} else {
|
||||
builtinFunctionReturnType(func.name, argExpressions, program).getOrElse { throw AssemblyError("unknown dt") }
|
||||
builtinFunctionReturnType(func.name).getOrElse { throw AssemblyError("unknown dt") }
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,18 +83,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
"mkword" -> funcMkword(fcall, resultToStack, resultRegister)
|
||||
"abs" -> funcAbs(fcall, func, resultToStack, resultRegister, sscope)
|
||||
"swap" -> funcSwap(fcall)
|
||||
"min", "max" -> funcMinMax(fcall, func, resultToStack, resultRegister, sscope)
|
||||
"sum" -> funcSum(fcall, resultToStack, resultRegister, sscope)
|
||||
"any", "all" -> funcAnyAll(fcall, func, resultToStack, resultRegister, sscope)
|
||||
"sin8", "sin8u", "sin16", "sin16u",
|
||||
"sinr8", "sinr8u", "sinr16", "sinr16u",
|
||||
"cos8", "cos8u", "cos16", "cos16u",
|
||||
"cosr8", "cosr8u", "cosr16", "cosr16u" -> funcSinCosInt(fcall, func, resultToStack, resultRegister, sscope)
|
||||
"sgn" -> funcSgn(fcall, func, resultToStack, resultRegister, sscope)
|
||||
"sin", "cos", "tan", "atan",
|
||||
"ln", "log2", "sqrt", "rad",
|
||||
"deg", "round", "floor", "ceil",
|
||||
"rndf" -> funcVariousFloatFuncs(fcall, func, resultToStack, resultRegister, sscope)
|
||||
"rnd", "rndw" -> funcRnd(func, resultToStack, resultRegister, sscope)
|
||||
"sqrt16" -> funcSqrt16(fcall, func, resultToStack, resultRegister, sscope)
|
||||
"rol" -> funcRol(fcall)
|
||||
@ -129,7 +120,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
"cmp" -> funcCmp(fcall)
|
||||
"callfar" -> funcCallFar(fcall)
|
||||
"callrom" -> funcCallRom(fcall)
|
||||
"syscall", "syscall1", "syscall2", "syscall3" -> throw AssemblyError("6502 assembly target doesn't use syscall function interface")
|
||||
else -> throw AssemblyError("missing asmgen for builtin func ${func.name}")
|
||||
}
|
||||
}
|
||||
@ -391,23 +381,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcSinCosInt(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
|
||||
translateArguments(fcall.args, func, scope)
|
||||
if(resultToStack)
|
||||
asmgen.out(" jsr prog8_lib.func_${func.name}_stack")
|
||||
else
|
||||
when(func.name) {
|
||||
"sin8", "sin8u", "sinr8", "sinr8u", "cos8", "cos8u", "cosr8", "cosr8u" -> {
|
||||
asmgen.out(" jsr prog8_lib.func_${func.name}_into_A")
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
|
||||
}
|
||||
"sin16", "sin16u", "sinr16", "sinr16u", "cos16", "cos16u", "cosr16", "cosr16u" -> {
|
||||
asmgen.out(" jsr prog8_lib.func_${func.name}_into_AY")
|
||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcReverse(fcall: IFunctionCall) {
|
||||
val variable = fcall.args.single()
|
||||
if (variable is IdentifierReference) {
|
||||
@ -686,16 +659,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
asmgen.assignExpressionToVariable(indexer.indexExpr, "prog8_lib.${operation}_array_u${dt}._arg_index", DataType.UBYTE, null)
|
||||
}
|
||||
|
||||
private fun funcVariousFloatFuncs(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
|
||||
translateArguments(fcall.args, func, scope)
|
||||
if(resultToStack)
|
||||
asmgen.out(" jsr floats.func_${func.name}_stack")
|
||||
else {
|
||||
asmgen.out(" jsr floats.func_${func.name}_fac1")
|
||||
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, scope, program, asmgen))
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcSgn(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
|
||||
translateArguments(fcall.args, func, scope)
|
||||
val dt = fcall.args.single().inferType(program)
|
||||
@ -733,92 +696,15 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
}
|
||||
} else {
|
||||
when (dt.getOr(DataType.UNDEFINED)) {
|
||||
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${function.name}_b_into_A")
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${function.name}_w_into_A")
|
||||
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_into_A")
|
||||
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${function.name}_b_into_A | ldy #0")
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${function.name}_w_into_A | ldy #0")
|
||||
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_into_A | ldy #0")
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcMinMax(fcall: IFunctionCall, function: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
|
||||
outputAddressAndLenghtOfArray(fcall.args[0])
|
||||
val dt = fcall.args.single().inferType(program)
|
||||
if(resultToStack) {
|
||||
when (dt.getOr(DataType.UNDEFINED)) {
|
||||
DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${function.name}_ub_stack")
|
||||
DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_${function.name}_b_stack")
|
||||
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_${function.name}_uw_stack")
|
||||
DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${function.name}_w_stack")
|
||||
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_stack")
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
} else {
|
||||
when (dt.getOr(DataType.UNDEFINED)) {
|
||||
DataType.ARRAY_UB, DataType.STR -> {
|
||||
asmgen.out(" jsr prog8_lib.func_${function.name}_ub_into_A")
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
|
||||
}
|
||||
DataType.ARRAY_B -> {
|
||||
asmgen.out(" jsr prog8_lib.func_${function.name}_b_into_A")
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
|
||||
}
|
||||
DataType.ARRAY_UW -> {
|
||||
asmgen.out(" jsr prog8_lib.func_${function.name}_uw_into_AY")
|
||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
|
||||
}
|
||||
DataType.ARRAY_W -> {
|
||||
asmgen.out(" jsr prog8_lib.func_${function.name}_w_into_AY")
|
||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
asmgen.out(" jsr floats.func_${function.name}_f_fac1")
|
||||
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, scope, program, asmgen))
|
||||
}
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcSum(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
|
||||
outputAddressAndLenghtOfArray(fcall.args[0])
|
||||
val dt = fcall.args.single().inferType(program)
|
||||
if(resultToStack) {
|
||||
when (dt.getOr(DataType.UNDEFINED)) {
|
||||
DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_sum_ub_stack")
|
||||
DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_sum_b_stack")
|
||||
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_sum_uw_stack")
|
||||
DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_sum_w_stack")
|
||||
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_sum_f_stack")
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
} else {
|
||||
when (dt.getOr(DataType.UNDEFINED)) {
|
||||
DataType.ARRAY_UB, DataType.STR -> {
|
||||
asmgen.out(" jsr prog8_lib.func_sum_ub_into_AY")
|
||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
|
||||
}
|
||||
DataType.ARRAY_B -> {
|
||||
asmgen.out(" jsr prog8_lib.func_sum_b_into_AY")
|
||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
|
||||
}
|
||||
DataType.ARRAY_UW -> {
|
||||
asmgen.out(" jsr prog8_lib.func_sum_uw_into_AY")
|
||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
|
||||
}
|
||||
DataType.ARRAY_W -> {
|
||||
asmgen.out(" jsr prog8_lib.func_sum_w_into_AY")
|
||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
asmgen.out(" jsr floats.func_sum_f_fac1")
|
||||
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, scope, program, asmgen))
|
||||
}
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcSwap(fcall: IFunctionCall) {
|
||||
val first = fcall.args[0]
|
||||
@ -1244,27 +1130,21 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
val dt = fcall.args.single().inferType(program).getOr(DataType.UNDEFINED)
|
||||
if(resultToStack) {
|
||||
when (dt) {
|
||||
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.abs_b_stack")
|
||||
in WordDatatypes -> asmgen.out(" jsr prog8_lib.abs_w_stack")
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.abs_f_stack")
|
||||
DataType.UBYTE -> asmgen.out(" ldy #0")
|
||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.abs_b_stack")
|
||||
DataType.UWORD -> {}
|
||||
DataType.WORD -> asmgen.out(" jsr prog8_lib.abs_w_stack")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
} else {
|
||||
when (dt) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.out(" jsr prog8_lib.abs_b_into_A")
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
asmgen.out(" jsr prog8_lib.abs_w_into_AY")
|
||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out(" jsr floats.abs_f_fac1")
|
||||
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, scope, program, asmgen))
|
||||
}
|
||||
DataType.UBYTE -> asmgen.out(" ldy #0")
|
||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.abs_b_into_AY")
|
||||
DataType.UWORD -> {}
|
||||
DataType.WORD -> asmgen.out(" jsr prog8_lib.abs_w_into_AY")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1596,8 +1476,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
private fun outputAddressAndLenghtOfArray(arg: Expression) {
|
||||
// address in P8ZP_SCRATCH_W1, number of elements in A
|
||||
arg as IdentifierReference
|
||||
val arrayVar = arg.targetVarDecl(program)!!
|
||||
if(!arrayVar.isArray)
|
||||
throw AssemblyError("length of non-array requested")
|
||||
val size = arrayVar.arraysize!!.constIndex()!!
|
||||
val identifierName = asmgen.asmVariableName(arg)
|
||||
val size = arg.targetVarDecl(program)!!.arraysize!!.constIndex()!!
|
||||
asmgen.out("""
|
||||
lda #<$identifierName
|
||||
ldy #>$identifierName
|
||||
|
@ -1,7 +1,6 @@
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.antlr.escape
|
||||
import prog8.ast.statements.*
|
||||
import prog8.code.*
|
||||
import prog8.code.core.*
|
||||
@ -128,9 +127,27 @@ internal class ProgramAndVarsGen(
|
||||
"cx16" -> {
|
||||
if(options.floats)
|
||||
asmgen.out(" lda #4 | sta $01") // to use floats, make sure Basic rom is banked in
|
||||
asmgen.out(" jsr main.start | lda #4 | sta $01 | rts")
|
||||
asmgen.out(" jsr main.start | lda #4 | sta $01")
|
||||
if(!options.noSysInit)
|
||||
asmgen.out(" jmp ${compTarget.name}.cleanup_at_exit")
|
||||
else
|
||||
asmgen.out(" rts")
|
||||
}
|
||||
"c64" -> {
|
||||
asmgen.out(" jsr main.start | lda #31 | sta $01")
|
||||
if(!options.noSysInit)
|
||||
asmgen.out(" jmp ${compTarget.name}.cleanup_at_exit")
|
||||
else
|
||||
asmgen.out(" rts")
|
||||
}
|
||||
"c128" -> {
|
||||
asmgen.out(" jsr main.start")
|
||||
// TODO c128: how to bank basic+kernal back in?
|
||||
if(!options.noSysInit)
|
||||
asmgen.out(" jmp ${compTarget.name}.cleanup_at_exit")
|
||||
else
|
||||
asmgen.out(" rts")
|
||||
}
|
||||
"c64" -> asmgen.out(" jsr main.start | lda #31 | sta $01 | rts")
|
||||
else -> asmgen.jmp("main.start")
|
||||
}
|
||||
}
|
||||
@ -572,7 +589,7 @@ internal class ProgramAndVarsGen(
|
||||
}
|
||||
|
||||
private fun outputStringvar(varname: String, encoding: Encoding, value: String) {
|
||||
asmgen.out("$varname\t; $encoding:\"${escape(value).replace("\u0000", "<NULL>")}\"")
|
||||
asmgen.out("$varname\t; $encoding:\"${value.escape().replace("\u0000", "<NULL>")}\"")
|
||||
val bytes = compTarget.encodeString(value, encoding).plus(0.toUByte())
|
||||
val outputBytes = bytes.map { "$" + it.toString(16).padStart(2, '0') }
|
||||
for (chunk in outputBytes.chunked(16))
|
||||
|
@ -185,15 +185,7 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
||||
}
|
||||
DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||
// copy the actual string result into the target string variable
|
||||
asmgen.out("""
|
||||
pha
|
||||
lda #<${assign.target.asmVarname}
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda #>${assign.target.asmVarname}
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
pla
|
||||
jsr prog8_lib.strcpy""")
|
||||
throw AssemblyError("stringvalue assignment should have been replaced by a call to strcpy")
|
||||
}
|
||||
else -> throw AssemblyError("weird target dt")
|
||||
}
|
||||
@ -229,7 +221,7 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
asmgen.translateBuiltinFunctionCallExpression(value, false, assign.target.register)
|
||||
if(assign.target.register==null) {
|
||||
// still need to assign the result to the target variable/etc.
|
||||
val returntype = builtinFunctionReturnType(value.name, value.args, program)
|
||||
val returntype = builtinFunctionReturnType(value.name)
|
||||
if(!returntype.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
when(returntype.getOr(DataType.UNDEFINED)) {
|
||||
@ -336,8 +328,6 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
val variable = (containment.iterable as? IdentifierReference)?.targetVarDecl(program)
|
||||
?: throw AssemblyError("invalid containment iterable type")
|
||||
|
||||
if(elementDt istype DataType.FLOAT)
|
||||
throw AssemblyError("containment check of floats not supported")
|
||||
if(variable.origin!=VarDeclOrigin.USERCODE) {
|
||||
when(variable.datatype) {
|
||||
DataType.STR -> {
|
||||
@ -396,7 +386,9 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
asmgen.out(" jsr prog8_lib.containment_bytearray")
|
||||
return
|
||||
}
|
||||
DataType.ARRAY_F -> throw AssemblyError("containment check of floats not supported")
|
||||
DataType.ARRAY_F -> {
|
||||
throw AssemblyError("containment check of floats not supported")
|
||||
}
|
||||
DataType.ARRAY_B, DataType.ARRAY_UB -> {
|
||||
val arrayVal = variable.value as ArrayLiteral
|
||||
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
|
||||
@ -568,7 +560,14 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
|
||||
if(target.kind==TargetStorageKind.REGISTER) {
|
||||
assignExpressionToRegister(value, target.register!!, targetDt==DataType.BYTE || targetDt==DataType.WORD)
|
||||
if(valueDt==DataType.FLOAT && target.datatype!=DataType.FLOAT) {
|
||||
// have to typecast the float number on the fly down to an integer
|
||||
assignExpressionToRegister(value, RegisterOrPair.FAC1, target.datatype in SignedDatatypes)
|
||||
assignTypeCastedFloatFAC1("P8ZP_SCRATCH_W1", target.datatype)
|
||||
assignVariableToRegister("P8ZP_SCRATCH_W1", target.register!!, target.datatype in SignedDatatypes)
|
||||
} else {
|
||||
assignExpressionToRegister(value, target.register!!, targetDt==DataType.BYTE || targetDt==DataType.WORD)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -1265,7 +1264,7 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("can't assign float to mem byte")
|
||||
TargetStorageKind.REGISTER -> {
|
||||
if (target.register!! != RegisterOrPair.FAC1)
|
||||
throw AssemblyError("can't assign Fac1 float to another fac register")
|
||||
throw AssemblyError("can't assign Fac1 float to another register")
|
||||
}
|
||||
TargetStorageKind.STACK -> asmgen.out(" jsr floats.push_fac1")
|
||||
}
|
||||
@ -2302,10 +2301,14 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
|
||||
internal fun assignExpressionToVariable(expr: Expression, asmVarName: String, dt: DataType, scope: Subroutine?) {
|
||||
val src = AsmAssignSource.fromAstSource(expr, program, asmgen)
|
||||
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, scope, variableAsmName = asmVarName)
|
||||
val assign = AsmAssignment(src, tgt, false, program.memsizer, expr.position)
|
||||
translateNormalAssignment(assign)
|
||||
if(expr.inferType(program) istype DataType.FLOAT && dt!=DataType.FLOAT) {
|
||||
throw AssemblyError("can't directly assign a FLOAT expression to an integer variable $expr")
|
||||
} else {
|
||||
val src = AsmAssignSource.fromAstSource(expr, program, asmgen)
|
||||
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, scope, variableAsmName = asmVarName)
|
||||
val assign = AsmAssignment(src, tgt, false, program.memsizer, expr.position)
|
||||
translateNormalAssignment(assign)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair, signed: Boolean) {
|
||||
|
@ -445,7 +445,6 @@ class AstToXmlConverter(internal val program: PtProgram,
|
||||
|
||||
private fun write(assign: PtAssignment) {
|
||||
xml.elt("assign")
|
||||
xml.attr("aug", assign.augmentable.toString())
|
||||
xml.pos(assign.position)
|
||||
xml.startChildren()
|
||||
write(assign.target)
|
||||
@ -613,7 +612,7 @@ class AstToXmlConverter(internal val program: PtProgram,
|
||||
}
|
||||
if(asmSub.clobbers.isNotEmpty()) {
|
||||
xml.elt("clobbers")
|
||||
xml.attr("registers", asmSub.clobbers.map {it.name}.joinToString(","))
|
||||
xml.attr("registers", asmSub.clobbers.joinToString(",") { it.name })
|
||||
xml.endElt()
|
||||
}
|
||||
if(asmSub.retvalRegisters.isNotEmpty()) {
|
||||
|
@ -1,11 +1,14 @@
|
||||
package prog8.codegen.virtual
|
||||
|
||||
import prog8.code.core.AssemblyError
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.IAssemblyProgram
|
||||
import prog8.vm.Instruction
|
||||
import prog8.vm.Opcode
|
||||
import prog8.vm.OpcodesWithAddress
|
||||
import prog8.vm.VmDataType
|
||||
import java.io.BufferedWriter
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.bufferedWriter
|
||||
import kotlin.io.path.div
|
||||
|
||||
@ -27,8 +30,10 @@ internal class AssemblyProgram(override val name: String,
|
||||
}
|
||||
out.write("------PROGRAM------\n")
|
||||
|
||||
out.write("; global var inits\n")
|
||||
globalInits.forEach { out.writeLine(it) }
|
||||
if(!options.dontReinitGlobals) {
|
||||
out.write("; global var inits\n")
|
||||
globalInits.forEach { out.writeLine(it) }
|
||||
}
|
||||
|
||||
out.write("; actual program code\n")
|
||||
blocks.asSequence().flatMap { it.lines }.forEach { line->out.writeLine(line) }
|
||||
@ -43,6 +48,21 @@ internal class AssemblyProgram(override val name: String,
|
||||
write(line.ins.toString() + "\n")
|
||||
}
|
||||
is VmCodeLabel -> write("_" + line.name.joinToString(".") + ":\n")
|
||||
is VmCodeInlineAsm -> {
|
||||
val asm = line.assembly.replace("""\{[a-zA-Z\d_\.]+\}""".toRegex()) { matchResult ->
|
||||
val name = matchResult.value.substring(1, matchResult.value.length-1).split('.')
|
||||
allocations.get(name).toString() }
|
||||
write(asm+"\n")
|
||||
}
|
||||
is VmCodeInlineBinary -> {
|
||||
write("incbin \"${line.file}\"")
|
||||
if(line.offset!=null)
|
||||
write(",${line.offset}")
|
||||
if(line.length!=null)
|
||||
write(",${line.length}")
|
||||
write("\n")
|
||||
}
|
||||
else -> throw AssemblyError("invalid vm code line")
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,12 +77,40 @@ internal class VmCodeInstruction(
|
||||
type: VmDataType?=null,
|
||||
reg1: Int?=null, // 0-$ffff
|
||||
reg2: Int?=null, // 0-$ffff
|
||||
reg3: Int?=null, // 0-$ffff
|
||||
fpReg1: Int?=null, // 0-$ffff
|
||||
fpReg2: Int?=null, // 0-$ffff
|
||||
value: Int?=null, // 0-$ffff
|
||||
symbol: List<String>?=null // alternative to value
|
||||
fpValue: Float?=null,
|
||||
labelSymbol: List<String>?=null // alternative to value for branch/call/jump labels
|
||||
): VmCodeLine() {
|
||||
val ins = Instruction(opcode, type, reg1, reg2, reg3, value, symbol)
|
||||
val ins = Instruction(opcode, type, reg1, reg2, fpReg1, fpReg2, value, fpValue, labelSymbol)
|
||||
|
||||
init {
|
||||
if(reg1!=null && (reg1<0 || reg1>65536))
|
||||
throw IllegalArgumentException("reg1 out of bounds")
|
||||
if(reg2!=null && (reg2<0 || reg2>65536))
|
||||
throw IllegalArgumentException("reg2 out of bounds")
|
||||
if(fpReg1!=null && (fpReg1<0 || fpReg1>65536))
|
||||
throw IllegalArgumentException("fpReg1 out of bounds")
|
||||
if(fpReg2!=null && (fpReg2<0 || fpReg2>65536))
|
||||
throw IllegalArgumentException("fpReg2 out of bounds")
|
||||
|
||||
if(value!=null && opcode !in OpcodesWithAddress) {
|
||||
when (type) {
|
||||
VmDataType.BYTE -> {
|
||||
if (value < -128 || value > 255)
|
||||
throw IllegalArgumentException("value out of range for byte: $value")
|
||||
}
|
||||
VmDataType.WORD -> {
|
||||
if (value < -32768 || value > 65535)
|
||||
throw IllegalArgumentException("value out of range for word: $value")
|
||||
}
|
||||
VmDataType.FLOAT, null -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class VmCodeLabel(val name: List<String>): VmCodeLine()
|
||||
internal class VmCodeComment(val comment: String): VmCodeLine()
|
||||
|
||||
@ -81,4 +129,10 @@ internal class VmCodeChunk(initial: VmCodeLine? = null) {
|
||||
operator fun plusAssign(chunk: VmCodeChunk) {
|
||||
lines.addAll(chunk.lines)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class VmCodeInlineAsm(asm: String): VmCodeLine() {
|
||||
val assembly: String = asm.trimIndent()
|
||||
}
|
||||
|
||||
internal class VmCodeInlineBinary(val file: Path, val offset: UInt?, val length: UInt?): VmCodeLine()
|
||||
|
232
codeGenVirtual/src/prog8/codegen/virtual/AssignmentGen.kt
Normal file
232
codeGenVirtual/src/prog8/codegen/virtual/AssignmentGen.kt
Normal file
@ -0,0 +1,232 @@
|
||||
package prog8.codegen.virtual
|
||||
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.AssemblyError
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.SignedDatatypes
|
||||
import prog8.vm.Opcode
|
||||
import prog8.vm.VmDataType
|
||||
|
||||
internal class AssignmentGen(private val codeGen: CodeGen, private val expressionEval: ExpressionGen) {
|
||||
|
||||
internal fun translate(assignment: PtAssignment): VmCodeChunk {
|
||||
if(assignment.target.children.single() is PtMachineRegister)
|
||||
throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister")
|
||||
|
||||
return if (assignment.isInplaceAssign)
|
||||
translateInplaceAssign(assignment)
|
||||
else
|
||||
translateRegularAssign(assignment)
|
||||
}
|
||||
|
||||
private fun translateInplaceAssign(assignment: PtAssignment): VmCodeChunk {
|
||||
val ident = assignment.target.identifier
|
||||
val memory = assignment.target.memory
|
||||
val array = assignment.target.array
|
||||
|
||||
return if(ident!=null) {
|
||||
val address = codeGen.allocations.get(ident.targetName)
|
||||
assignSelfInMemory(address, assignment.value, assignment)
|
||||
} else if(memory != null) {
|
||||
if(memory.address is PtNumber)
|
||||
assignSelfInMemory((memory.address as PtNumber).number.toInt(), assignment.value, assignment)
|
||||
else
|
||||
fallbackAssign(assignment)
|
||||
} else if(array!=null) {
|
||||
// TODO in-place array element assignment?
|
||||
fallbackAssign(assignment)
|
||||
} else {
|
||||
fallbackAssign(assignment)
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignSelfInMemory(
|
||||
address: Int,
|
||||
value: PtExpression,
|
||||
origAssign: PtAssignment
|
||||
): VmCodeChunk {
|
||||
val vmDt = codeGen.vmType(value.type)
|
||||
val code = VmCodeChunk()
|
||||
when(value) {
|
||||
is PtIdentifier -> return code // do nothing, x=x null assignment.
|
||||
is PtMachineRegister -> return code // do nothing, reg=reg null assignment
|
||||
is PtPrefix -> return inplacePrefix(value.operator, vmDt, address)
|
||||
is PtBinaryExpression -> return inplaceBinexpr(value.operator, value.right, vmDt, value.type in SignedDatatypes, address, origAssign)
|
||||
is PtMemoryByte -> {
|
||||
return if (!codeGen.options.compTarget.machine.isIOAddress(address.toUInt()))
|
||||
code // do nothing, mem=mem null assignment.
|
||||
else {
|
||||
// read and write a (i/o) memory location to itself.
|
||||
val tempReg = codeGen.vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.LOADM, vmDt, reg1 = tempReg, value = address)
|
||||
code += VmCodeInstruction(Opcode.STOREM, vmDt, reg1 = tempReg, value = address)
|
||||
code
|
||||
}
|
||||
}
|
||||
else -> return fallbackAssign(origAssign)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun fallbackAssign(origAssign: PtAssignment): VmCodeChunk {
|
||||
if (codeGen.options.slowCodegenWarnings)
|
||||
codeGen.errors.warn("indirect code for in-place assignment", origAssign.position)
|
||||
return translateRegularAssign(origAssign)
|
||||
}
|
||||
|
||||
private fun inplaceBinexpr(
|
||||
operator: String,
|
||||
operand: PtExpression,
|
||||
vmDt: VmDataType,
|
||||
signed: Boolean,
|
||||
address: Int,
|
||||
origAssign: PtAssignment
|
||||
): VmCodeChunk {
|
||||
when(operator) {
|
||||
"+" -> return expressionEval.operatorPlusInplace(address, vmDt, operand)
|
||||
"-" -> return expressionEval.operatorMinusInplace(address, vmDt, operand)
|
||||
"*" -> return expressionEval.operatorMultiplyInplace(address, vmDt, operand)
|
||||
"/" -> return expressionEval.operatorDivideInplace(address, vmDt, signed, operand)
|
||||
"|" -> return expressionEval.operatorOrInplace(address, vmDt, operand)
|
||||
"&" -> return expressionEval.operatorAndInplace(address, vmDt, operand)
|
||||
"^" -> return expressionEval.operatorXorInplace(address, vmDt, operand)
|
||||
"<<" -> return expressionEval.operatorShiftLeftInplace(address, vmDt, operand)
|
||||
">>" -> return expressionEval.operatorShiftRightInplace(address, vmDt, signed, operand)
|
||||
else -> {}
|
||||
}
|
||||
return fallbackAssign(origAssign)
|
||||
}
|
||||
|
||||
private fun inplacePrefix(operator: String, vmDt: VmDataType, address: Int): VmCodeChunk {
|
||||
val code= VmCodeChunk()
|
||||
when(operator) {
|
||||
"+" -> { }
|
||||
"-" -> {
|
||||
code += VmCodeInstruction(Opcode.NEGM, vmDt, value = address)
|
||||
}
|
||||
"~" -> {
|
||||
val regMask = codeGen.vmRegisters.nextFree()
|
||||
val mask = if(vmDt==VmDataType.BYTE) 0x00ff else 0xffff
|
||||
code += VmCodeInstruction(Opcode.LOAD, vmDt, reg1=regMask, value = mask)
|
||||
code += VmCodeInstruction(Opcode.XORM, vmDt, reg1=regMask, value = address)
|
||||
}
|
||||
"not" -> {
|
||||
code += VmCodeInstruction(Opcode.NOTM, vmDt, value = address)
|
||||
}
|
||||
else -> throw AssemblyError("weird prefix operator")
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
private fun translateRegularAssign(assignment: PtAssignment): VmCodeChunk {
|
||||
// note: assigning array and string values is done via an explicit memcopy/stringcopy function call.
|
||||
val ident = assignment.target.identifier
|
||||
val memory = assignment.target.memory
|
||||
val array = assignment.target.array
|
||||
val vmDt = codeGen.vmType(assignment.value.type)
|
||||
|
||||
val code = VmCodeChunk()
|
||||
var resultRegister = -1
|
||||
var resultFpRegister = -1
|
||||
val zero = codeGen.isZero(assignment.value)
|
||||
if(!zero) {
|
||||
// calculate the assignment value
|
||||
if (vmDt == VmDataType.FLOAT) {
|
||||
resultFpRegister = codeGen.vmRegisters.nextFreeFloat()
|
||||
code += expressionEval.translateExpression(assignment.value, -1, resultFpRegister)
|
||||
} else {
|
||||
resultRegister = if (assignment.value is PtMachineRegister) {
|
||||
(assignment.value as PtMachineRegister).register
|
||||
} else {
|
||||
val reg = codeGen.vmRegisters.nextFree()
|
||||
code += expressionEval.translateExpression(assignment.value, reg, -1)
|
||||
reg
|
||||
}
|
||||
}
|
||||
}
|
||||
if(ident!=null) {
|
||||
val address = codeGen.allocations.get(ident.targetName)
|
||||
code += if(zero) {
|
||||
VmCodeInstruction(Opcode.STOREZM, vmDt, value = address)
|
||||
} else {
|
||||
if (vmDt == VmDataType.FLOAT)
|
||||
VmCodeInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, value = address)
|
||||
else
|
||||
VmCodeInstruction(Opcode.STOREM, vmDt, reg1 = resultRegister, value = address)
|
||||
}
|
||||
}
|
||||
else if(array!=null) {
|
||||
val variable = array.variable.targetName
|
||||
var variableAddr = codeGen.allocations.get(variable)
|
||||
val itemsize = codeGen.program.memsizer.memorySize(array.type)
|
||||
val fixedIndex = constIntValue(array.index)
|
||||
if(zero) {
|
||||
if(fixedIndex!=null) {
|
||||
variableAddr += fixedIndex*itemsize
|
||||
code += VmCodeInstruction(Opcode.STOREZM, vmDt, value=variableAddr)
|
||||
} else {
|
||||
val indexReg = codeGen.vmRegisters.nextFree()
|
||||
code += loadIndexReg(array, itemsize, indexReg)
|
||||
code += VmCodeInstruction(Opcode.STOREZX, vmDt, reg1=indexReg, value=variableAddr)
|
||||
}
|
||||
} else {
|
||||
if(vmDt== VmDataType.FLOAT) {
|
||||
if(fixedIndex!=null) {
|
||||
variableAddr += fixedIndex*itemsize
|
||||
code += VmCodeInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, value=variableAddr)
|
||||
} else {
|
||||
val indexReg = codeGen.vmRegisters.nextFree()
|
||||
code += loadIndexReg(array, itemsize, indexReg)
|
||||
code += VmCodeInstruction(Opcode.STOREX, vmDt, reg1 = resultRegister, reg2=indexReg, value=variableAddr)
|
||||
}
|
||||
} else {
|
||||
if(fixedIndex!=null) {
|
||||
variableAddr += fixedIndex*itemsize
|
||||
code += VmCodeInstruction(Opcode.STOREM, vmDt, reg1 = resultRegister, value=variableAddr)
|
||||
} else {
|
||||
val indexReg = codeGen.vmRegisters.nextFree()
|
||||
code += loadIndexReg(array, itemsize, indexReg)
|
||||
code += VmCodeInstruction(Opcode.STOREX, vmDt, reg1 = resultRegister, reg2=indexReg, value=variableAddr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(memory!=null) {
|
||||
require(vmDt== VmDataType.BYTE)
|
||||
if(zero) {
|
||||
if(memory.address is PtNumber) {
|
||||
code += VmCodeInstruction(Opcode.STOREZM, vmDt, value=(memory.address as PtNumber).number.toInt())
|
||||
} else {
|
||||
val addressReg = codeGen.vmRegisters.nextFree()
|
||||
code += expressionEval.translateExpression(memory.address, addressReg, -1)
|
||||
code += VmCodeInstruction(Opcode.STOREZI, vmDt, reg1=addressReg)
|
||||
}
|
||||
} else {
|
||||
if(memory.address is PtNumber) {
|
||||
code += VmCodeInstruction(Opcode.STOREM, vmDt, reg1=resultRegister, value=(memory.address as PtNumber).number.toInt())
|
||||
} else {
|
||||
val addressReg = codeGen.vmRegisters.nextFree()
|
||||
code += expressionEval.translateExpression(memory.address, addressReg, -1)
|
||||
code += VmCodeInstruction(Opcode.STOREI, vmDt, reg1=resultRegister, reg2=addressReg)
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
throw AssemblyError("weird assigntarget")
|
||||
return code
|
||||
}
|
||||
|
||||
private fun loadIndexReg(array: PtArrayIndexer, itemsize: Int, indexReg: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
if(itemsize==1) {
|
||||
code += expressionEval.translateExpression(array.index, indexReg, -1)
|
||||
}
|
||||
else {
|
||||
val mult = PtBinaryExpression("*", DataType.UBYTE, array.position)
|
||||
mult.children += array.index
|
||||
mult.children += PtNumber(DataType.UBYTE, itemsize.toDouble(), array.position)
|
||||
code += expressionEval.translateExpression(mult, indexReg, -1)
|
||||
}
|
||||
return code
|
||||
}
|
||||
}
|
@ -1,8 +1,9 @@
|
||||
package prog8.codegen.virtual
|
||||
|
||||
import prog8.code.ast.PtBuiltinFunctionCall
|
||||
import prog8.code.ast.PtNumber
|
||||
import prog8.code.ast.PtString
|
||||
import prog8.code.StStaticVariable
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.AssemblyError
|
||||
import prog8.code.core.DataType
|
||||
import prog8.vm.Opcode
|
||||
import prog8.vm.Syscall
|
||||
import prog8.vm.VmDataType
|
||||
@ -10,117 +11,380 @@ import prog8.vm.VmDataType
|
||||
internal class BuiltinFuncGen(private val codeGen: CodeGen, private val exprGen: ExpressionGen) {
|
||||
|
||||
fun translate(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
||||
return when(call.name) {
|
||||
"any" -> funcAny(call, resultRegister)
|
||||
"all" -> funcAll(call, resultRegister)
|
||||
"abs" -> funcAbs(call, resultRegister)
|
||||
"cmp" -> funcCmp(call)
|
||||
"sgn" -> funcSgn(call, resultRegister)
|
||||
"sqrt16" -> funcSqrt16(call, resultRegister)
|
||||
"pop" -> funcPop(call)
|
||||
"popw" -> funcPopw(call)
|
||||
"push" -> funcPush(call)
|
||||
"pushw" -> funcPushw(call)
|
||||
"rsave",
|
||||
"rsavex",
|
||||
"rrestore",
|
||||
"rrestorex" -> VmCodeChunk() // vm doesn't have registers to save/restore
|
||||
"rnd" -> funcRnd(resultRegister)
|
||||
"rndw" -> funcRndw(resultRegister)
|
||||
"callfar" -> throw AssemblyError("callfar() is for cx16 target only")
|
||||
"callrom" -> throw AssemblyError("callrom() is for cx16 target only")
|
||||
"msb" -> funcMsb(call, resultRegister)
|
||||
"lsb" -> funcLsb(call, resultRegister)
|
||||
"memory" -> funcMemory(call, resultRegister)
|
||||
"peek" -> funcPeek(call, resultRegister)
|
||||
"peekw" -> funcPeekW(call, resultRegister)
|
||||
"poke" -> funcPoke(call)
|
||||
"pokew" -> funcPokeW(call)
|
||||
"pokemon" -> VmCodeChunk()
|
||||
"mkword" -> funcMkword(call, resultRegister)
|
||||
"sort" -> funcSort(call)
|
||||
"reverse" -> funcReverse(call)
|
||||
"swap" -> funcSwap(call)
|
||||
"rol" -> funcRolRor2(Opcode.ROXL, call, resultRegister)
|
||||
"ror" -> funcRolRor2(Opcode.ROXR, call, resultRegister)
|
||||
"rol2" -> funcRolRor2(Opcode.ROL, call, resultRegister)
|
||||
"ror2" -> funcRolRor2(Opcode.ROR, call, resultRegister)
|
||||
else -> TODO("builtinfunc ${call.name}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcCmp(call: PtBuiltinFunctionCall): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
when(call.name) {
|
||||
"syscall" -> {
|
||||
val vExpr = call.args.single() as PtNumber
|
||||
code += VmCodeInstruction(Opcode.SYSCALL, value=vExpr.number.toInt())
|
||||
val leftRegister = codeGen.vmRegisters.nextFree()
|
||||
val rightRegister = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args[0], leftRegister, -1)
|
||||
code += exprGen.translateExpression(call.args[1], rightRegister, -1)
|
||||
code += VmCodeInstruction(Opcode.CMP, codeGen.vmType(call.args[0].type), reg1=leftRegister, reg2=rightRegister)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcAny(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
||||
val arrayName = call.args[0] as PtIdentifier
|
||||
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
|
||||
val code = VmCodeChunk()
|
||||
val syscall =
|
||||
when (array.dt) {
|
||||
DataType.ARRAY_UB,
|
||||
DataType.ARRAY_B -> Syscall.ANY_BYTE
|
||||
DataType.ARRAY_UW,
|
||||
DataType.ARRAY_W -> Syscall.ANY_WORD
|
||||
DataType.ARRAY_F -> Syscall.ANY_FLOAT
|
||||
else -> throw IllegalArgumentException("weird type")
|
||||
}
|
||||
"syscall1" -> {
|
||||
code += VmCodeInstruction(Opcode.PUSH, VmDataType.WORD, reg1 = 0)
|
||||
val callNr = (call.args[0] as PtNumber).number.toInt()
|
||||
code += exprGen.translateExpression(call.args[1], 0)
|
||||
code += VmCodeInstruction(Opcode.SYSCALL, value=callNr)
|
||||
code += VmCodeInstruction(Opcode.POP, VmDataType.WORD, reg1 = 0)
|
||||
code += exprGen.translateExpression(call.args[0], 0, -1)
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1 = 1, value = array.length)
|
||||
code += VmCodeInstruction(Opcode.SYSCALL, value = syscall.ordinal)
|
||||
if (resultRegister != 0)
|
||||
code += VmCodeInstruction(Opcode.LOADR, VmDataType.BYTE, reg1 = resultRegister, reg2 = 0)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcAll(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
||||
val arrayName = call.args[0] as PtIdentifier
|
||||
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
|
||||
val syscall =
|
||||
when(array.dt) {
|
||||
DataType.ARRAY_UB,
|
||||
DataType.ARRAY_B -> Syscall.ALL_BYTE
|
||||
DataType.ARRAY_UW,
|
||||
DataType.ARRAY_W -> Syscall.ALL_WORD
|
||||
DataType.ARRAY_F -> Syscall.ALL_FLOAT
|
||||
else -> throw IllegalArgumentException("weird type")
|
||||
}
|
||||
"syscall2" -> {
|
||||
code += VmCodeInstruction(Opcode.PUSH, VmDataType.WORD, reg1 = 0)
|
||||
code += VmCodeInstruction(Opcode.PUSH, VmDataType.WORD, reg1 = 1)
|
||||
while(codeGen.vmRegisters.peekNext()<2) {
|
||||
codeGen.vmRegisters.nextFree()
|
||||
val code = VmCodeChunk()
|
||||
code += exprGen.translateExpression(call.args[0], 0, -1)
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=1, value=array.length)
|
||||
code += VmCodeInstruction(Opcode.SYSCALL, value=syscall.ordinal)
|
||||
if(resultRegister!=0)
|
||||
code += VmCodeInstruction(Opcode.LOADR, VmDataType.BYTE, reg1=resultRegister, reg2=0)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcAbs(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val sourceDt = call.args.single().type
|
||||
if(sourceDt!=DataType.UWORD) {
|
||||
code += exprGen.translateExpression(call.args[0], resultRegister, -1)
|
||||
when (sourceDt) {
|
||||
DataType.UBYTE -> {
|
||||
code += VmCodeInstruction(Opcode.EXT, VmDataType.BYTE, reg1=resultRegister)
|
||||
}
|
||||
val callNr = (call.args[0] as PtNumber).number.toInt()
|
||||
code += exprGen.translateExpression(call.args[1], 0)
|
||||
code += exprGen.translateExpression(call.args[2], 1)
|
||||
code += VmCodeInstruction(Opcode.SYSCALL, value=callNr)
|
||||
code += VmCodeInstruction(Opcode.POP, VmDataType.WORD, reg1 = 1)
|
||||
code += VmCodeInstruction(Opcode.POP, VmDataType.WORD, reg1 = 0)
|
||||
}
|
||||
"syscall3" -> {
|
||||
code += VmCodeInstruction(Opcode.PUSH, VmDataType.WORD, reg1 = 0)
|
||||
code += VmCodeInstruction(Opcode.PUSH, VmDataType.WORD, reg1 = 1)
|
||||
code += VmCodeInstruction(Opcode.PUSH, VmDataType.WORD, reg1 = 2)
|
||||
while(codeGen.vmRegisters.peekNext()<3) {
|
||||
codeGen.vmRegisters.nextFree()
|
||||
DataType.BYTE -> {
|
||||
val andReg = codeGen.vmRegisters.nextFree()
|
||||
val notNegativeLabel = codeGen.createLabelName()
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=andReg, value=0x80)
|
||||
code += VmCodeInstruction(Opcode.AND, VmDataType.BYTE, reg1=andReg, reg2=resultRegister)
|
||||
code += VmCodeInstruction(Opcode.BZ, VmDataType.BYTE, reg1=andReg, labelSymbol = notNegativeLabel)
|
||||
code += VmCodeInstruction(Opcode.NEG, VmDataType.BYTE, reg1=resultRegister)
|
||||
code += VmCodeInstruction(Opcode.EXT, VmDataType.BYTE, reg1=resultRegister)
|
||||
code += VmCodeLabel(notNegativeLabel)
|
||||
}
|
||||
val callNr = (call.args[0] as PtNumber).number.toInt()
|
||||
code += exprGen.translateExpression(call.args[1], 0)
|
||||
code += exprGen.translateExpression(call.args[2], 1)
|
||||
code += exprGen.translateExpression(call.args[3], 2)
|
||||
code += VmCodeInstruction(Opcode.SYSCALL, value=callNr)
|
||||
code += VmCodeInstruction(Opcode.POP, VmDataType.WORD, reg1 = 2)
|
||||
code += VmCodeInstruction(Opcode.POP, VmDataType.WORD, reg1 = 1)
|
||||
code += VmCodeInstruction(Opcode.POP, VmDataType.WORD, reg1 = 0)
|
||||
}
|
||||
"msb" -> {
|
||||
code += exprGen.translateExpression(call.args.single(), resultRegister)
|
||||
code += VmCodeInstruction(Opcode.SWAP, VmDataType.BYTE, reg1 = resultRegister)
|
||||
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
|
||||
}
|
||||
"lsb" -> {
|
||||
code += exprGen.translateExpression(call.args.single(), resultRegister)
|
||||
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
|
||||
}
|
||||
"memory" -> {
|
||||
val name = (call.args[0] as PtString).value
|
||||
val size = (call.args[1] as PtNumber).number.toUInt()
|
||||
val align = (call.args[2] as PtNumber).number.toUInt()
|
||||
val existing = codeGen.allocations.getMemorySlab(name)
|
||||
val address = if(existing==null)
|
||||
codeGen.allocations.allocateMemorySlab(name, size, align)
|
||||
else if(existing.second!=size || existing.third!=align) {
|
||||
codeGen.errors.err("memory slab '$name' already exists with a different size or alignment", call.position)
|
||||
return VmCodeChunk()
|
||||
DataType.WORD -> {
|
||||
val andReg = codeGen.vmRegisters.nextFree()
|
||||
val notNegativeLabel = codeGen.createLabelName()
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.WORD, reg1=andReg, value=0x8000)
|
||||
code += VmCodeInstruction(Opcode.AND, VmDataType.WORD, reg1=andReg, reg2=resultRegister)
|
||||
code += VmCodeInstruction(Opcode.BZ, VmDataType.WORD, reg1=andReg, labelSymbol = notNegativeLabel)
|
||||
code += VmCodeInstruction(Opcode.NEG, VmDataType.WORD, reg1=resultRegister)
|
||||
code += VmCodeLabel(notNegativeLabel)
|
||||
}
|
||||
else
|
||||
existing.first
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.WORD, reg1=resultRegister, value=address.toInt())
|
||||
}
|
||||
"rnd" -> {
|
||||
code += VmCodeInstruction(Opcode.SYSCALL, value= Syscall.RND.ordinal)
|
||||
if(resultRegister!=0)
|
||||
code += VmCodeInstruction(Opcode.LOADR, VmDataType.BYTE, reg1=resultRegister, reg2=0)
|
||||
}
|
||||
"peek" -> {
|
||||
val addressReg = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args.single(), addressReg)
|
||||
code += VmCodeInstruction(Opcode.LOADI, VmDataType.BYTE, reg1 = resultRegister, reg2=addressReg)
|
||||
}
|
||||
"peekw" -> {
|
||||
val addressReg = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args.single(), addressReg)
|
||||
code += VmCodeInstruction(Opcode.LOADI, VmDataType.WORD, reg1 = resultRegister, reg2=addressReg)
|
||||
}
|
||||
"mkword" -> {
|
||||
val msbReg = codeGen.vmRegisters.nextFree()
|
||||
val lsbReg = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args[0], msbReg)
|
||||
code += exprGen.translateExpression(call.args[1], lsbReg)
|
||||
code += VmCodeInstruction(Opcode.CONCAT, VmDataType.BYTE, reg1=resultRegister, reg2=msbReg, reg3=lsbReg)
|
||||
}
|
||||
else -> {
|
||||
TODO("builtinfunc ${call.name}")
|
||||
// code += VmCodeInstruction(Opcode.NOP))
|
||||
// for (arg in call.args) {
|
||||
// code += translateExpression(arg, resultRegister)
|
||||
// code += when(arg.type) {
|
||||
// in ByteDatatypes -> VmCodeInstruction(Opcode.PUSH, VmDataType.BYTE, reg1=resultRegister))
|
||||
// in WordDatatypes -> VmCodeInstruction(Opcode.PUSH, VmDataType.WORD, reg1=resultRegister))
|
||||
// else -> throw AssemblyError("weird arg dt")
|
||||
// }
|
||||
// }
|
||||
// code += VmCodeInstruction(Opcode.CALL), labelArg = listOf("_prog8_builtin", call.name))
|
||||
// for (arg in call.args) {
|
||||
// code += when(arg.type) {
|
||||
// in ByteDatatypes -> VmCodeInstruction(Opcode.POP, VmDataType.BYTE, reg1=resultRegister))
|
||||
// in WordDatatypes -> VmCodeInstruction(Opcode.POP, VmDataType.WORD, reg1=resultRegister))
|
||||
// else -> throw AssemblyError("weird arg dt")
|
||||
// }
|
||||
// }
|
||||
// code += VmCodeInstruction(Opcode.NOP))
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcSgn(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val reg = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args.single(), reg, -1)
|
||||
code += VmCodeInstruction(Opcode.SGN, codeGen.vmType(call.type), reg1=resultRegister, reg2=reg)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcSqrt16(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val reg = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args.single(), reg, -1)
|
||||
code += VmCodeInstruction(Opcode.SQRT, VmDataType.WORD, reg1=resultRegister, reg2=reg)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcPop(call: PtBuiltinFunctionCall): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val reg = codeGen.vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.POP, VmDataType.BYTE, reg1=reg)
|
||||
code += assignRegisterTo(call.args.single(), reg)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcPopw(call: PtBuiltinFunctionCall): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val reg = codeGen.vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.POP, VmDataType.WORD, reg1=reg)
|
||||
code += assignRegisterTo(call.args.single(), reg)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcPush(call: PtBuiltinFunctionCall): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val reg = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args.single(), reg, -1)
|
||||
code += VmCodeInstruction(Opcode.PUSH, VmDataType.BYTE, reg1=reg)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcPushw(call: PtBuiltinFunctionCall): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val reg = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args.single(), reg, -1)
|
||||
code += VmCodeInstruction(Opcode.PUSH, VmDataType.WORD, reg1=reg)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcSwap(call: PtBuiltinFunctionCall): VmCodeChunk {
|
||||
val left = call.args[0]
|
||||
val right = call.args[1]
|
||||
val leftReg = codeGen.vmRegisters.nextFree()
|
||||
val rightReg = codeGen.vmRegisters.nextFree()
|
||||
val code = VmCodeChunk()
|
||||
code += exprGen.translateExpression(left, leftReg, -1)
|
||||
code += exprGen.translateExpression(right, rightReg, -1)
|
||||
code += assignRegisterTo(left, rightReg)
|
||||
code += assignRegisterTo(right, leftReg)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcReverse(call: PtBuiltinFunctionCall): VmCodeChunk {
|
||||
val arrayName = call.args[0] as PtIdentifier
|
||||
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
|
||||
val sortSyscall =
|
||||
when(array.dt) {
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.STR -> Syscall.REVERSE_BYTES
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> Syscall.REVERSE_WORDS
|
||||
DataType.ARRAY_F -> Syscall.REVERSE_FLOATS
|
||||
else -> throw IllegalArgumentException("weird type to reverse")
|
||||
}
|
||||
val code = VmCodeChunk()
|
||||
code += exprGen.translateExpression(call.args[0], 0, -1)
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=1, value=array.length)
|
||||
code += VmCodeInstruction(Opcode.SYSCALL, value=sortSyscall.ordinal)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcSort(call: PtBuiltinFunctionCall): VmCodeChunk {
|
||||
val arrayName = call.args[0] as PtIdentifier
|
||||
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
|
||||
val sortSyscall =
|
||||
when(array.dt) {
|
||||
DataType.ARRAY_UB -> Syscall.SORT_UBYTE
|
||||
DataType.ARRAY_B -> Syscall.SORT_BYTE
|
||||
DataType.ARRAY_UW -> Syscall.SORT_UWORD
|
||||
DataType.ARRAY_W -> Syscall.SORT_WORD
|
||||
DataType.STR -> Syscall.SORT_UBYTE
|
||||
DataType.ARRAY_F -> throw java.lang.IllegalArgumentException("sorting a floating point array is not supported")
|
||||
else -> throw IllegalArgumentException("weird type to sort")
|
||||
}
|
||||
val code = VmCodeChunk()
|
||||
code += exprGen.translateExpression(call.args[0], 0, -1)
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=1, value=array.length)
|
||||
code += VmCodeInstruction(Opcode.SYSCALL, value=sortSyscall.ordinal)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcMkword(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
||||
val msbReg = codeGen.vmRegisters.nextFree()
|
||||
val code = VmCodeChunk()
|
||||
code += exprGen.translateExpression(call.args[0], msbReg, -1)
|
||||
code += exprGen.translateExpression(call.args[1], resultRegister, -1)
|
||||
code += VmCodeInstruction(Opcode.CONCAT, VmDataType.BYTE, reg1=resultRegister, reg2=msbReg)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcPokeW(call: PtBuiltinFunctionCall): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
if(codeGen.isZero(call.args[1])) {
|
||||
if (call.args[0] is PtNumber) {
|
||||
val address = (call.args[0] as PtNumber).number.toInt()
|
||||
code += VmCodeInstruction(Opcode.STOREZM, VmDataType.WORD, value = address)
|
||||
} else {
|
||||
val addressReg = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args[0], addressReg, -1)
|
||||
code += VmCodeInstruction(Opcode.STOREZI, VmDataType.WORD, reg2 = addressReg)
|
||||
}
|
||||
} else {
|
||||
val valueReg = codeGen.vmRegisters.nextFree()
|
||||
if (call.args[0] is PtNumber) {
|
||||
val address = (call.args[0] as PtNumber).number.toInt()
|
||||
code += exprGen.translateExpression(call.args[1], valueReg, -1)
|
||||
code += VmCodeInstruction(Opcode.STOREM, VmDataType.WORD, reg1 = valueReg, value = address)
|
||||
} else {
|
||||
val addressReg = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args[0], addressReg, -1)
|
||||
code += exprGen.translateExpression(call.args[1], valueReg, -1)
|
||||
code += VmCodeInstruction(Opcode.STOREI, VmDataType.WORD, reg1 = valueReg, reg2 = addressReg)
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcPoke(call: PtBuiltinFunctionCall): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
if(codeGen.isZero(call.args[1])) {
|
||||
if (call.args[0] is PtNumber) {
|
||||
val address = (call.args[0] as PtNumber).number.toInt()
|
||||
code += VmCodeInstruction(Opcode.STOREZM, VmDataType.BYTE, value = address)
|
||||
} else {
|
||||
val addressReg = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args[0], addressReg, -1)
|
||||
code += VmCodeInstruction(Opcode.STOREZI, VmDataType.BYTE, reg2 = addressReg)
|
||||
}
|
||||
} else {
|
||||
val valueReg = codeGen.vmRegisters.nextFree()
|
||||
if (call.args[0] is PtNumber) {
|
||||
val address = (call.args[0] as PtNumber).number.toInt()
|
||||
code += exprGen.translateExpression(call.args[1], valueReg, -1)
|
||||
code += VmCodeInstruction(Opcode.STOREM, VmDataType.BYTE, reg1 = valueReg, value = address)
|
||||
} else {
|
||||
val addressReg = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args[0], addressReg, -1)
|
||||
code += exprGen.translateExpression(call.args[1], valueReg, -1)
|
||||
code += VmCodeInstruction(Opcode.STOREI, VmDataType.BYTE, reg1 = valueReg, reg2 = addressReg)
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcPeekW(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
if(call.args[0] is PtNumber) {
|
||||
val address = (call.args[0] as PtNumber).number.toInt()
|
||||
code += VmCodeInstruction(Opcode.LOADM, VmDataType.WORD, reg1 = resultRegister, value = address)
|
||||
} else {
|
||||
val addressReg = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args.single(), addressReg, -1)
|
||||
code += VmCodeInstruction(Opcode.LOADI, VmDataType.WORD, reg1 = resultRegister, reg2 = addressReg)
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcPeek(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
if(call.args[0] is PtNumber) {
|
||||
val address = (call.args[0] as PtNumber).number.toInt()
|
||||
code += VmCodeInstruction(Opcode.LOADM, VmDataType.BYTE, reg1 = resultRegister, value = address)
|
||||
} else {
|
||||
val addressReg = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args.single(), addressReg, -1)
|
||||
code += VmCodeInstruction(Opcode.LOADI, VmDataType.BYTE, reg1 = resultRegister, reg2 = addressReg)
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcRnd(resultRegister: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
code += VmCodeInstruction(Opcode.RND, VmDataType.BYTE, reg1=resultRegister)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcRndw(resultRegister: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
code += VmCodeInstruction(Opcode.RND, VmDataType.WORD, reg1=resultRegister)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcMemory(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
||||
val name = (call.args[0] as PtString).value
|
||||
val size = (call.args[1] as PtNumber).number.toUInt()
|
||||
val align = (call.args[2] as PtNumber).number.toUInt()
|
||||
val existing = codeGen.allocations.getMemorySlab(name)
|
||||
val address = if(existing==null)
|
||||
codeGen.allocations.allocateMemorySlab(name, size, align)
|
||||
else if(existing.second!=size || existing.third!=align) {
|
||||
codeGen.errors.err("memory slab '$name' already exists with a different size or alignment", call.position)
|
||||
return VmCodeChunk()
|
||||
}
|
||||
else
|
||||
existing.first
|
||||
val code = VmCodeChunk()
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.WORD, reg1=resultRegister, value=address.toInt())
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcLsb(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
code += exprGen.translateExpression(call.args.single(), resultRegister, -1)
|
||||
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcMsb(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
code += exprGen.translateExpression(call.args.single(), resultRegister, -1)
|
||||
code += VmCodeInstruction(Opcode.MSIG, VmDataType.BYTE, reg1 = resultRegister, reg2=resultRegister)
|
||||
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcRolRor2(opcode: Opcode, call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
||||
val vmDt = codeGen.vmType(call.args[0].type)
|
||||
val code = VmCodeChunk()
|
||||
code += exprGen.translateExpression(call.args[0], resultRegister, -1)
|
||||
code += VmCodeInstruction(opcode, vmDt, reg1=resultRegister)
|
||||
code += assignRegisterTo(call.args[0], resultRegister)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun assignRegisterTo(target: PtExpression, register: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val assignment = PtAssignment(target.position)
|
||||
val assignTarget = PtAssignTarget(target.position)
|
||||
assignTarget.children.add(target)
|
||||
assignment.children.add(assignTarget)
|
||||
assignment.children.add(PtMachineRegister(register, target.type, target.position))
|
||||
code += codeGen.translateNode(assignment)
|
||||
return code
|
||||
}
|
||||
}
|
||||
|
@ -10,15 +10,25 @@ import kotlin.math.pow
|
||||
|
||||
|
||||
internal class VmRegisterPool {
|
||||
private var firstFree: Int=3 // registers 0,1,2 are reserved
|
||||
|
||||
private var firstFree: Int=3 // integer registers 0,1,2 are reserved
|
||||
private var firstFreeFloat: Int=0
|
||||
|
||||
fun peekNext() = firstFree
|
||||
fun peekNextFloat() = firstFreeFloat
|
||||
|
||||
fun nextFree(): Int {
|
||||
val result = firstFree
|
||||
firstFree++
|
||||
if(firstFree>65535)
|
||||
throw AssemblyError("out of virtual registers")
|
||||
throw AssemblyError("out of virtual registers (int)")
|
||||
return result
|
||||
}
|
||||
|
||||
fun nextFreeFloat(): Int {
|
||||
val result = firstFreeFloat
|
||||
firstFreeFloat++
|
||||
if(firstFreeFloat>65535)
|
||||
throw AssemblyError("out of virtual registers (fp)")
|
||||
return result
|
||||
}
|
||||
}
|
||||
@ -33,21 +43,19 @@ class CodeGen(internal val program: PtProgram,
|
||||
internal val allocations = VariableAllocator(symbolTable, program, errors)
|
||||
private val expressionEval = ExpressionGen(this)
|
||||
private val builtinFuncGen = BuiltinFuncGen(this, expressionEval)
|
||||
private val assignmentGen = AssignmentGen(this, expressionEval)
|
||||
internal val vmRegisters = VmRegisterPool()
|
||||
|
||||
init {
|
||||
if(options.dontReinitGlobals)
|
||||
TODO("support no globals re-init in vm")
|
||||
}
|
||||
|
||||
override fun compileToAssembly(): IAssemblyProgram? {
|
||||
val vmprog = AssemblyProgram(program.name, allocations)
|
||||
|
||||
// collect global variables initializers
|
||||
program.allBlocks().forEach {
|
||||
val code = VmCodeChunk()
|
||||
it.children.filterIsInstance<PtAssignment>().forEach { assign -> code += translate(assign) }
|
||||
vmprog.addGlobalInits(code)
|
||||
if(!options.dontReinitGlobals) {
|
||||
// collect global variables initializers
|
||||
program.allBlocks().forEach {
|
||||
val code = VmCodeChunk()
|
||||
it.children.filterIsInstance<PtAssignment>().forEach { assign -> code += assignmentGen.translate(assign) }
|
||||
vmprog.addGlobalInits(code)
|
||||
}
|
||||
}
|
||||
|
||||
for (block in program.allBlocks()) {
|
||||
@ -60,7 +68,7 @@ class CodeGen(internal val program: PtProgram,
|
||||
}
|
||||
|
||||
|
||||
private fun translateNode(node: PtNode): VmCodeChunk {
|
||||
internal fun translateNode(node: PtNode): VmCodeChunk {
|
||||
val code = when(node) {
|
||||
is PtBlock -> translate(node)
|
||||
is PtSub -> translate(node)
|
||||
@ -68,10 +76,10 @@ class CodeGen(internal val program: PtProgram,
|
||||
is PtVariable -> VmCodeChunk() // var should be looked up via symbol table
|
||||
is PtMemMapped -> VmCodeChunk() // memmapped var should be looked up via symbol table
|
||||
is PtConstant -> VmCodeChunk() // constants have all been folded into the code
|
||||
is PtAssignment -> translate(node)
|
||||
is PtAssignment -> assignmentGen.translate(node)
|
||||
is PtNodeGroup -> translateGroup(node.children)
|
||||
is PtBuiltinFunctionCall -> translateBuiltinFunc(node, 0)
|
||||
is PtFunctionCall -> expressionEval.translate(node, 0)
|
||||
is PtFunctionCall -> expressionEval.translate(node, 0, 0)
|
||||
is PtNop -> VmCodeChunk()
|
||||
is PtReturn -> translate(node)
|
||||
is PtJump -> translate(node)
|
||||
@ -83,6 +91,10 @@ class CodeGen(internal val program: PtProgram,
|
||||
is PtRepeatLoop -> translate(node)
|
||||
is PtLabel -> VmCodeChunk(VmCodeLabel(node.scopedName))
|
||||
is PtBreakpoint -> VmCodeChunk(VmCodeInstruction(Opcode.BREAKPOINT))
|
||||
is PtConditionalBranch -> translate(node)
|
||||
is PtInlineAssembly -> VmCodeChunk(VmCodeInlineAsm(node.assembly))
|
||||
is PtIncludeBinary -> VmCodeChunk(VmCodeInlineBinary(node.file, node.offset, node.length))
|
||||
is PtAsmSub -> TODO("asmsub not yet supported on virtual machine target ${node.position}")
|
||||
is PtAddressOf,
|
||||
is PtContainmentCheck,
|
||||
is PtMemoryByte,
|
||||
@ -98,11 +110,7 @@ class CodeGen(internal val program: PtProgram,
|
||||
is PtSubroutineParameter,
|
||||
is PtNumber,
|
||||
is PtArray,
|
||||
is PtString -> throw AssemblyError("strings should not occur as separate statement node ${node.position}")
|
||||
is PtAsmSub -> throw AssemblyError("asmsub not supported on virtual machine target ${node.position}")
|
||||
is PtInlineAssembly -> throw AssemblyError("inline assembly not supported on virtual machine target ${node.position}")
|
||||
is PtIncludeBinary -> throw AssemblyError("inline binary data not supported on virtual machine target ${node.position}")
|
||||
is PtConditionalBranch -> throw AssemblyError("conditional branches not supported in vm target due to lack of cpu flags ${node.position}")
|
||||
is PtString -> throw AssemblyError("should not occur as separate statement node ${node.position}")
|
||||
else -> TODO("missing codegen for $node")
|
||||
}
|
||||
if(code.lines.isNotEmpty() && node.position.line!=0)
|
||||
@ -110,6 +118,33 @@ class CodeGen(internal val program: PtProgram,
|
||||
return code
|
||||
}
|
||||
|
||||
private fun translate(branch: PtConditionalBranch): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val elseLabel = createLabelName()
|
||||
// note that the branch opcode used is the opposite as the branch condition, because the generated code jumps to the 'else' part
|
||||
code += when(branch.condition) {
|
||||
BranchCondition.CS -> VmCodeInstruction(Opcode.BSTCC, labelSymbol = elseLabel)
|
||||
BranchCondition.CC -> VmCodeInstruction(Opcode.BSTCS, labelSymbol = elseLabel)
|
||||
BranchCondition.EQ, BranchCondition.Z -> VmCodeInstruction(Opcode.BSTNE, labelSymbol = elseLabel)
|
||||
BranchCondition.NE, BranchCondition.NZ -> VmCodeInstruction(Opcode.BSTEQ, labelSymbol = elseLabel)
|
||||
BranchCondition.MI, BranchCondition.NEG -> VmCodeInstruction(Opcode.BSTPOS, labelSymbol = elseLabel)
|
||||
BranchCondition.PL, BranchCondition.POS -> VmCodeInstruction(Opcode.BSTNEG, labelSymbol = elseLabel)
|
||||
BranchCondition.VC,
|
||||
BranchCondition.VS -> throw AssemblyError("conditional branch ${branch.condition} not supported in vm target due to lack of cpu V flag ${branch.position}")
|
||||
}
|
||||
code += translateNode(branch.trueScope)
|
||||
if(branch.falseScope.children.isNotEmpty()) {
|
||||
val endLabel = createLabelName()
|
||||
code += VmCodeInstruction(Opcode.JUMP, labelSymbol = endLabel)
|
||||
code += VmCodeLabel(elseLabel)
|
||||
code += translateNode(branch.falseScope)
|
||||
code += VmCodeLabel(endLabel)
|
||||
} else {
|
||||
code += VmCodeLabel(elseLabel)
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
private fun translate(whenStmt: PtWhen): VmCodeChunk {
|
||||
if(whenStmt.choices.children.isEmpty())
|
||||
return VmCodeChunk()
|
||||
@ -117,7 +152,7 @@ class CodeGen(internal val program: PtProgram,
|
||||
val valueReg = vmRegisters.nextFree()
|
||||
val choiceReg = vmRegisters.nextFree()
|
||||
val valueDt = vmType(whenStmt.value.type)
|
||||
code += expressionEval.translateExpression(whenStmt.value, valueReg)
|
||||
code += expressionEval.translateExpression(whenStmt.value, valueReg, -1)
|
||||
val choices = whenStmt.choices.children.map {it as PtWhenChoice }
|
||||
val endLabel = createLabelName()
|
||||
for (choice in choices) {
|
||||
@ -128,21 +163,21 @@ class CodeGen(internal val program: PtProgram,
|
||||
val values = choice.values.children.map {it as PtNumber}
|
||||
if(values.size==1) {
|
||||
code += VmCodeInstruction(Opcode.LOAD, valueDt, reg1=choiceReg, value=values[0].number.toInt())
|
||||
code += VmCodeInstruction(Opcode.BNE, valueDt, reg1=valueReg, reg2=choiceReg, symbol = skipLabel)
|
||||
code += VmCodeInstruction(Opcode.BNE, valueDt, reg1=valueReg, reg2=choiceReg, labelSymbol = skipLabel)
|
||||
code += translateNode(choice.statements)
|
||||
if(choice.statements.children.last() !is PtReturn)
|
||||
code += VmCodeInstruction(Opcode.JUMP, symbol = endLabel)
|
||||
code += VmCodeInstruction(Opcode.JUMP, labelSymbol = endLabel)
|
||||
} else {
|
||||
val matchLabel = createLabelName()
|
||||
for (value in values) {
|
||||
code += VmCodeInstruction(Opcode.LOAD, valueDt, reg1=choiceReg, value=value.number.toInt())
|
||||
code += VmCodeInstruction(Opcode.BEQ, valueDt, reg1=valueReg, reg2=choiceReg, symbol = matchLabel)
|
||||
code += VmCodeInstruction(Opcode.BEQ, valueDt, reg1=valueReg, reg2=choiceReg, labelSymbol = matchLabel)
|
||||
}
|
||||
code += VmCodeInstruction(Opcode.JUMP, symbol = skipLabel)
|
||||
code += VmCodeInstruction(Opcode.JUMP, labelSymbol = skipLabel)
|
||||
code += VmCodeLabel(matchLabel)
|
||||
code += translateNode(choice.statements)
|
||||
if(choice.statements.children.last() !is PtReturn)
|
||||
code += VmCodeInstruction(Opcode.JUMP, symbol = endLabel)
|
||||
code += VmCodeInstruction(Opcode.JUMP, labelSymbol = endLabel)
|
||||
}
|
||||
code += VmCodeLabel(skipLabel)
|
||||
}
|
||||
@ -167,35 +202,46 @@ class CodeGen(internal val program: PtProgram,
|
||||
val iterableVar = symbolTable.lookup(iterable.targetName) as StStaticVariable
|
||||
val loopvarAddress = allocations.get(loopvar.scopedName)
|
||||
val indexReg = vmRegisters.nextFree()
|
||||
val tmpReg = vmRegisters.nextFree()
|
||||
val loopLabel = createLabelName()
|
||||
val endLabel = createLabelName()
|
||||
if(iterableVar.dt==DataType.STR) {
|
||||
// iterate over a zero-terminated string
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=indexReg, value=0)
|
||||
code += VmCodeLabel(loopLabel)
|
||||
code += VmCodeInstruction(Opcode.LOADX, VmDataType.BYTE, reg1=0, reg2=indexReg, value = arrayAddress)
|
||||
code += VmCodeInstruction(Opcode.BZ, VmDataType.BYTE, reg1=0, symbol = endLabel)
|
||||
code += VmCodeInstruction(Opcode.STOREM, VmDataType.BYTE, reg1=0, value = loopvarAddress)
|
||||
code += VmCodeInstruction(Opcode.LOADX, VmDataType.BYTE, reg1=tmpReg, reg2=indexReg, value = arrayAddress)
|
||||
code += VmCodeInstruction(Opcode.BZ, VmDataType.BYTE, reg1=tmpReg, labelSymbol = endLabel)
|
||||
code += VmCodeInstruction(Opcode.STOREM, VmDataType.BYTE, reg1=tmpReg, value = loopvarAddress)
|
||||
code += translateNode(forLoop.statements)
|
||||
code += VmCodeInstruction(Opcode.INC, VmDataType.BYTE, reg1=indexReg)
|
||||
code += VmCodeInstruction(Opcode.JUMP, symbol = loopLabel)
|
||||
code += VmCodeInstruction(Opcode.JUMP, labelSymbol = loopLabel)
|
||||
code += VmCodeLabel(endLabel)
|
||||
} else {
|
||||
// iterate over array
|
||||
val elementDt = ArrayToElementTypes.getValue(iterable.type)
|
||||
val elementSize = program.memsizer.memorySize(elementDt)
|
||||
val lengthBytes = iterableVar.length!! * elementSize
|
||||
val lengthReg = vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=indexReg, value=0)
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=lengthReg, value=lengthBytes)
|
||||
code += VmCodeLabel(loopLabel)
|
||||
code += VmCodeInstruction(Opcode.BEQ, VmDataType.BYTE, reg1=indexReg, reg2=lengthReg, symbol = endLabel)
|
||||
code += VmCodeInstruction(Opcode.LOADX, vmType(elementDt), reg1=0, reg2=indexReg, value=arrayAddress)
|
||||
code += VmCodeInstruction(Opcode.STOREM, vmType(elementDt), reg1=0, value = loopvarAddress)
|
||||
code += translateNode(forLoop.statements)
|
||||
code += addConstReg(VmDataType.BYTE, indexReg, elementSize)
|
||||
code += VmCodeInstruction(Opcode.JUMP, symbol = loopLabel)
|
||||
code += VmCodeLabel(endLabel)
|
||||
if(lengthBytes<256) {
|
||||
val lengthReg = vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=indexReg, value=0)
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=lengthReg, value=lengthBytes)
|
||||
code += VmCodeLabel(loopLabel)
|
||||
code += VmCodeInstruction(Opcode.LOADX, vmType(elementDt), reg1=tmpReg, reg2=indexReg, value=arrayAddress)
|
||||
code += VmCodeInstruction(Opcode.STOREM, vmType(elementDt), reg1=tmpReg, value = loopvarAddress)
|
||||
code += translateNode(forLoop.statements)
|
||||
code += addConstReg(VmDataType.BYTE, indexReg, elementSize)
|
||||
code += VmCodeInstruction(Opcode.BNE, VmDataType.BYTE, reg1=indexReg, reg2=lengthReg, labelSymbol = loopLabel)
|
||||
} else if(lengthBytes==256) {
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=indexReg, value=0)
|
||||
code += VmCodeLabel(loopLabel)
|
||||
code += VmCodeInstruction(Opcode.LOADX, vmType(elementDt), reg1=tmpReg, reg2=indexReg, value=arrayAddress)
|
||||
code += VmCodeInstruction(Opcode.STOREM, vmType(elementDt), reg1=tmpReg, value = loopvarAddress)
|
||||
code += translateNode(forLoop.statements)
|
||||
code += addConstReg(VmDataType.BYTE, indexReg, elementSize)
|
||||
code += VmCodeInstruction(Opcode.BNZ, VmDataType.BYTE, reg1=indexReg, labelSymbol = loopLabel)
|
||||
} else {
|
||||
throw AssemblyError("iterator length should never exceed 256")
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird for iterable")
|
||||
@ -215,8 +261,8 @@ class CodeGen(internal val program: PtProgram,
|
||||
val loopLabel = createLabelName()
|
||||
val code = VmCodeChunk()
|
||||
|
||||
code += expressionEval.translateExpression(iterable.to, endvalueReg)
|
||||
code += expressionEval.translateExpression(iterable.from, indexReg)
|
||||
code += expressionEval.translateExpression(iterable.to, endvalueReg, -1)
|
||||
code += expressionEval.translateExpression(iterable.from, indexReg, -1)
|
||||
code += VmCodeInstruction(Opcode.STOREM, loopvarDt, reg1=indexReg, value=loopvarAddress)
|
||||
code += VmCodeLabel(loopLabel)
|
||||
code += translateNode(forLoop.statements)
|
||||
@ -229,41 +275,49 @@ class CodeGen(internal val program: PtProgram,
|
||||
code += VmCodeInstruction(Opcode.STOREM, loopvarDt, reg1 = indexReg, value = loopvarAddress)
|
||||
}
|
||||
val branchOpcode = if(loopvar.dt in SignedDatatypes) Opcode.BLES else Opcode.BLE
|
||||
code += VmCodeInstruction(branchOpcode, loopvarDt, reg1=indexReg, reg2=endvalueReg, symbol=loopLabel)
|
||||
code += VmCodeInstruction(branchOpcode, loopvarDt, reg1=indexReg, reg2=endvalueReg, labelSymbol=loopLabel)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun translateForInConstantRange(forLoop: PtForLoop, loopvar: StStaticVariable): VmCodeChunk {
|
||||
val iterable = forLoop.iterable as PtRange
|
||||
val step = iterable.step.number.toInt()
|
||||
val range = IntProgression.fromClosedRange(
|
||||
(iterable.from as PtNumber).number.toInt(),
|
||||
(iterable.to as PtNumber).number.toInt() + step,
|
||||
step)
|
||||
if (range.isEmpty() || range.step==0)
|
||||
throw AssemblyError("empty range or step 0")
|
||||
|
||||
val loopLabel = createLabelName()
|
||||
val loopvarAddress = allocations.get(loopvar.scopedName)
|
||||
val indexReg = vmRegisters.nextFree()
|
||||
val endvalueReg = vmRegisters.nextFree()
|
||||
val loopvarDt = vmType(loopvar.dt)
|
||||
val iterable = forLoop.iterable as PtRange
|
||||
val step = iterable.step.number.toInt()
|
||||
val rangeStart = (iterable.from as PtNumber).number.toInt()
|
||||
val rangeEndUntyped = (iterable.to as PtNumber).number.toInt() + step
|
||||
if(step==0)
|
||||
throw AssemblyError("step 0")
|
||||
if(step>0 && rangeEndUntyped<rangeStart || step<0 && rangeEndUntyped>rangeStart)
|
||||
throw AssemblyError("empty range")
|
||||
val rangeEndWrapped = if(loopvarDt==VmDataType.BYTE) rangeEndUntyped and 255 else rangeEndUntyped and 65535
|
||||
val code = VmCodeChunk()
|
||||
code += VmCodeInstruction(Opcode.LOAD, loopvarDt, reg1=endvalueReg, value=range.last)
|
||||
code += VmCodeInstruction(Opcode.LOAD, loopvarDt, reg1=indexReg, value=range.first)
|
||||
val endvalueReg: Int
|
||||
if(rangeEndWrapped!=0) {
|
||||
endvalueReg = vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.LOAD, loopvarDt, reg1 = endvalueReg, value = rangeEndWrapped)
|
||||
} else {
|
||||
endvalueReg = -1 // not used
|
||||
}
|
||||
code += VmCodeInstruction(Opcode.LOAD, loopvarDt, reg1=indexReg, value=rangeStart)
|
||||
code += VmCodeInstruction(Opcode.STOREM, loopvarDt, reg1=indexReg, value=loopvarAddress)
|
||||
code += VmCodeLabel(loopLabel)
|
||||
code += translateNode(forLoop.statements)
|
||||
if(range.step<3) {
|
||||
code += addConstMem(loopvarDt, loopvarAddress.toUInt(), range.step)
|
||||
if(step<3) {
|
||||
code += addConstMem(loopvarDt, loopvarAddress.toUInt(), step)
|
||||
code += VmCodeInstruction(Opcode.LOADM, loopvarDt, reg1 = indexReg, value = loopvarAddress)
|
||||
} else {
|
||||
code += VmCodeInstruction(Opcode.LOADM, loopvarDt, reg1 = indexReg, value = loopvarAddress)
|
||||
code += addConstReg(loopvarDt, indexReg, range.step)
|
||||
code += addConstReg(loopvarDt, indexReg, step)
|
||||
code += VmCodeInstruction(Opcode.STOREM, loopvarDt, reg1 = indexReg, value = loopvarAddress)
|
||||
}
|
||||
// TODO more optimal loop instruction for loops ending on 0 (BNZ?)
|
||||
code += VmCodeInstruction(Opcode.BNE, loopvarDt, reg1=indexReg, reg2=endvalueReg, symbol=loopLabel)
|
||||
code += if(rangeEndWrapped==0) {
|
||||
VmCodeInstruction(Opcode.BNZ, loopvarDt, reg1 = indexReg, labelSymbol = loopLabel)
|
||||
} else {
|
||||
VmCodeInstruction(Opcode.BNE, loopvarDt, reg1 = indexReg, reg2 = endvalueReg, labelSymbol = loopLabel)
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
@ -289,11 +343,11 @@ class CodeGen(internal val program: PtProgram,
|
||||
val valueReg = vmRegisters.nextFree()
|
||||
if(value>0) {
|
||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=valueReg, value= value)
|
||||
code += VmCodeInstruction(Opcode.ADD, dt, reg1 = reg, reg2 = reg, reg3 = valueReg)
|
||||
code += VmCodeInstruction(Opcode.ADD, dt, reg1 = reg, reg2 = valueReg)
|
||||
}
|
||||
else {
|
||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=valueReg, value= -value)
|
||||
code += VmCodeInstruction(Opcode.SUB, dt, reg1 = reg, reg2 = reg, reg3 = valueReg)
|
||||
code += VmCodeInstruction(Opcode.SUB, dt, reg1 = reg, reg2 = valueReg)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -324,13 +378,13 @@ class CodeGen(internal val program: PtProgram,
|
||||
if(value>0) {
|
||||
code += VmCodeInstruction(Opcode.LOADM, dt, reg1=valueReg, value=address.toInt())
|
||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=operandReg, value=value)
|
||||
code += VmCodeInstruction(Opcode.ADD, dt, reg1 = valueReg, reg2 = valueReg, reg3 = operandReg)
|
||||
code += VmCodeInstruction(Opcode.ADD, dt, reg1 = valueReg, reg2 = operandReg)
|
||||
code += VmCodeInstruction(Opcode.STOREM, dt, reg1=valueReg, value=address.toInt())
|
||||
}
|
||||
else {
|
||||
code += VmCodeInstruction(Opcode.LOADM, dt, reg1=valueReg, value=address.toInt())
|
||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=operandReg, value=-value)
|
||||
code += VmCodeInstruction(Opcode.SUB, dt, reg1 = valueReg, reg2 = valueReg, reg3 = operandReg)
|
||||
code += VmCodeInstruction(Opcode.SUB, dt, reg1 = valueReg, reg2 = operandReg)
|
||||
code += VmCodeInstruction(Opcode.STOREM, dt, reg1=valueReg, value=address.toInt())
|
||||
}
|
||||
}
|
||||
@ -338,113 +392,333 @@ class CodeGen(internal val program: PtProgram,
|
||||
return code
|
||||
}
|
||||
|
||||
private val powersOfTwo = (0..16).map { 2.0.pow(it.toDouble()).toInt() }
|
||||
|
||||
private fun multiplyByConst(dt: VmDataType, reg: Int, factor: UInt): VmCodeChunk {
|
||||
internal fun multiplyByConstFloat(fpReg: Int, factor: Float): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val pow2 = powersOfTwo.indexOf(factor.toInt())
|
||||
if(pow2>=1) {
|
||||
// just shift bits
|
||||
code += VmCodeInstruction(Opcode.LSL, dt, reg1=reg, reg2=reg, reg3=pow2)
|
||||
if(factor==1f)
|
||||
return code
|
||||
if(factor==0f) {
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.FLOAT, fpReg1 = fpReg, fpValue = 0f)
|
||||
} else {
|
||||
when(factor) {
|
||||
0u -> {
|
||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=reg, value=0)
|
||||
}
|
||||
1u -> { /* do nothing */ }
|
||||
else -> {
|
||||
val factorReg = vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=factorReg, value=factor.toInt())
|
||||
code += VmCodeInstruction(Opcode.MUL, dt, reg1=reg, reg2=reg, reg3=factorReg)
|
||||
}
|
||||
val factorReg = vmRegisters.nextFreeFloat()
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.FLOAT, fpReg1=factorReg, fpValue = factor)
|
||||
code += VmCodeInstruction(Opcode.MUL, VmDataType.FLOAT, fpReg1 = fpReg, fpReg2 = factorReg)
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
internal fun multiplyByConstFloatInplace(address: Int, factor: Float): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
if(factor==1f)
|
||||
return code
|
||||
if(factor==0f) {
|
||||
code += VmCodeInstruction(Opcode.STOREZM, VmDataType.FLOAT, value = address)
|
||||
} else {
|
||||
val factorReg = vmRegisters.nextFreeFloat()
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.FLOAT, fpReg1=factorReg, fpValue = factor)
|
||||
code += VmCodeInstruction(Opcode.MULM, VmDataType.FLOAT, fpReg1 = factorReg, value = address)
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
internal val powersOfTwo = (0..16).map { 2.0.pow(it.toDouble()).toInt() }
|
||||
|
||||
internal fun multiplyByConst(dt: VmDataType, reg: Int, factor: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
if(factor==1)
|
||||
return code
|
||||
val pow2 = powersOfTwo.indexOf(factor)
|
||||
if(pow2==1) {
|
||||
// just shift 1 bit
|
||||
code += VmCodeInstruction(Opcode.LSL, dt, reg1=reg)
|
||||
}
|
||||
else if(pow2>=1) {
|
||||
// just shift multiple bits
|
||||
val pow2reg = vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=pow2reg, value=pow2)
|
||||
code += VmCodeInstruction(Opcode.LSLN, dt, reg1=reg, reg2=pow2reg)
|
||||
} else {
|
||||
if (factor == 0) {
|
||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=reg, value=0)
|
||||
}
|
||||
else {
|
||||
val factorReg = vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=factorReg, value= factor)
|
||||
code += VmCodeInstruction(Opcode.MUL, dt, reg1=reg, reg2=factorReg)
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
internal fun multiplyByConstInplace(dt: VmDataType, address: Int, factor: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
if(factor==1)
|
||||
return code
|
||||
val pow2 = powersOfTwo.indexOf(factor)
|
||||
if(pow2==1) {
|
||||
// just shift 1 bit
|
||||
code += VmCodeInstruction(Opcode.LSLM, dt, value = address)
|
||||
}
|
||||
else if(pow2>=1) {
|
||||
// just shift multiple bits
|
||||
val pow2reg = vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=pow2reg, value=pow2)
|
||||
code += VmCodeInstruction(Opcode.LSLNM, dt, reg1=pow2reg, value=address)
|
||||
} else {
|
||||
if (factor == 0) {
|
||||
code += VmCodeInstruction(Opcode.STOREZM, dt, value=address)
|
||||
}
|
||||
else {
|
||||
val factorReg = vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=factorReg, value = factor)
|
||||
code += VmCodeInstruction(Opcode.MULM, dt, reg1=factorReg, value = address)
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
internal fun divideByConstFloat(fpReg: Int, factor: Float): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
if(factor==1f)
|
||||
return code
|
||||
if(factor==0f) {
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.FLOAT, fpReg1 = fpReg, fpValue = Float.MAX_VALUE)
|
||||
} else {
|
||||
val factorReg = vmRegisters.nextFreeFloat()
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.FLOAT, fpReg1=factorReg, fpValue = factor)
|
||||
code += VmCodeInstruction(Opcode.DIVS, VmDataType.FLOAT, fpReg1 = fpReg, fpReg2 = factorReg)
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
internal fun divideByConstFloatInplace(address: Int, factor: Float): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
if(factor==1f)
|
||||
return code
|
||||
if(factor==0f) {
|
||||
val maxvalueReg = vmRegisters.nextFreeFloat()
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.FLOAT, fpReg1 = maxvalueReg, fpValue = Float.MAX_VALUE)
|
||||
code += VmCodeInstruction(Opcode.STOREM, VmDataType.FLOAT, fpReg1 = maxvalueReg, value=address)
|
||||
} else {
|
||||
val factorReg = vmRegisters.nextFreeFloat()
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.FLOAT, fpReg1=factorReg, fpValue = factor)
|
||||
code += VmCodeInstruction(Opcode.DIVSM, VmDataType.FLOAT, fpReg1 = factorReg, value=address)
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
internal fun divideByConst(dt: VmDataType, reg: Int, factor: Int, signed: Boolean): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
if(factor==1)
|
||||
return code
|
||||
val pow2 = powersOfTwo.indexOf(factor)
|
||||
if(pow2==1) {
|
||||
// just shift 1 bit
|
||||
code += if(signed)
|
||||
VmCodeInstruction(Opcode.ASR, dt, reg1=reg)
|
||||
else
|
||||
VmCodeInstruction(Opcode.LSR, dt, reg1=reg)
|
||||
}
|
||||
else if(pow2>=1) {
|
||||
// just shift multiple bits
|
||||
val pow2reg = vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=pow2reg, value=pow2)
|
||||
code += if(signed)
|
||||
VmCodeInstruction(Opcode.ASRN, dt, reg1=reg, reg2=pow2reg)
|
||||
else
|
||||
VmCodeInstruction(Opcode.LSRN, dt, reg1=reg, reg2=pow2reg)
|
||||
} else {
|
||||
if (factor == 0) {
|
||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=reg, value=0xffff)
|
||||
}
|
||||
else {
|
||||
val factorReg = vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=factorReg, value= factor)
|
||||
code += if(signed)
|
||||
VmCodeInstruction(Opcode.DIVS, dt, reg1=reg, reg2=factorReg)
|
||||
else
|
||||
VmCodeInstruction(Opcode.DIV, dt, reg1=reg, reg2=factorReg)
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
internal fun divideByConstInplace(dt: VmDataType, address: Int, factor: Int, signed: Boolean): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
if(factor==1)
|
||||
return code
|
||||
val pow2 = powersOfTwo.indexOf(factor)
|
||||
if(pow2==1) {
|
||||
// just shift 1 bit
|
||||
code += if(signed)
|
||||
VmCodeInstruction(Opcode.ASRM, dt, value=address)
|
||||
else
|
||||
VmCodeInstruction(Opcode.LSRM, dt, value=address)
|
||||
}
|
||||
else if(pow2>=1) {
|
||||
// just shift multiple bits
|
||||
val pow2reg = vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=pow2reg, value=pow2)
|
||||
code += if(signed)
|
||||
VmCodeInstruction(Opcode.ASRNM, dt, reg1=pow2reg, value=address)
|
||||
else
|
||||
VmCodeInstruction(Opcode.LSRNM, dt, reg1=pow2reg, value=address)
|
||||
} else {
|
||||
if (factor == 0) {
|
||||
val reg = vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=reg, value=0xffff)
|
||||
code += VmCodeInstruction(Opcode.STOREM, dt, reg1=reg, value=address)
|
||||
}
|
||||
else {
|
||||
val factorReg = vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=factorReg, value= factor)
|
||||
code += if(signed)
|
||||
VmCodeInstruction(Opcode.DIVSM, dt, reg1=factorReg, value=address)
|
||||
else
|
||||
VmCodeInstruction(Opcode.DIVM, dt, reg1=factorReg, value=address)
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
private fun translate(ifElse: PtIfElse): VmCodeChunk {
|
||||
var branch = Opcode.BZ
|
||||
var condition = ifElse.condition
|
||||
if(ifElse.condition.operator !in ComparisonOperators)
|
||||
throw AssemblyError("if condition should only be a binary comparison expression")
|
||||
|
||||
val cond = ifElse.condition as? PtBinaryExpression
|
||||
if((cond?.right as? PtNumber)?.number==0.0) {
|
||||
if(cond.operator == "==") {
|
||||
// if X==0 ... so we branch on Not-zero instead.
|
||||
branch = Opcode.BNZ
|
||||
condition = cond.left
|
||||
}
|
||||
else if(cond.operator == "!=") {
|
||||
// if X!=0 ... so we keep branching on Zero.
|
||||
condition = cond.left
|
||||
}
|
||||
}
|
||||
|
||||
val conditionReg = vmRegisters.nextFree()
|
||||
val vmDt = vmType(condition.type)
|
||||
val signed = ifElse.condition.left.type in arrayOf(DataType.BYTE, DataType.WORD, DataType.FLOAT)
|
||||
val vmDt = vmType(ifElse.condition.left.type)
|
||||
val code = VmCodeChunk()
|
||||
code += expressionEval.translateExpression(condition, conditionReg)
|
||||
if(ifElse.elseScope.children.isNotEmpty()) {
|
||||
// if and else parts
|
||||
val elseLabel = createLabelName()
|
||||
val afterIfLabel = createLabelName()
|
||||
code += VmCodeInstruction(branch, vmDt, reg1=conditionReg, symbol = elseLabel)
|
||||
code += translateNode(ifElse.ifScope)
|
||||
code += VmCodeInstruction(Opcode.JUMP, symbol = afterIfLabel)
|
||||
code += VmCodeLabel(elseLabel)
|
||||
code += translateNode(ifElse.elseScope)
|
||||
code += VmCodeLabel(afterIfLabel)
|
||||
} else {
|
||||
// only if part
|
||||
val afterIfLabel = createLabelName()
|
||||
code += VmCodeInstruction(branch, vmDt, reg1=conditionReg, symbol = afterIfLabel)
|
||||
code += translateNode(ifElse.ifScope)
|
||||
code += VmCodeLabel(afterIfLabel)
|
||||
|
||||
fun translateNonZeroComparison(): VmCodeChunk {
|
||||
val elseBranch = when(ifElse.condition.operator) {
|
||||
"==" -> Opcode.BNE
|
||||
"!=" -> Opcode.BEQ
|
||||
"<" -> if(signed) Opcode.BGES else Opcode.BGE
|
||||
">" -> if(signed) Opcode.BLES else Opcode.BLE
|
||||
"<=" -> if(signed) Opcode.BGTS else Opcode.BGT
|
||||
">=" -> if(signed) Opcode.BLTS else Opcode.BLT
|
||||
else -> throw AssemblyError("invalid comparison operator")
|
||||
}
|
||||
|
||||
val leftReg = vmRegisters.nextFree()
|
||||
val rightReg = vmRegisters.nextFree()
|
||||
code += expressionEval.translateExpression(ifElse.condition.left, leftReg, -1)
|
||||
code += expressionEval.translateExpression(ifElse.condition.right, rightReg, -1)
|
||||
if(ifElse.elseScope.children.isNotEmpty()) {
|
||||
// if and else parts
|
||||
val elseLabel = createLabelName()
|
||||
val afterIfLabel = createLabelName()
|
||||
code += VmCodeInstruction(elseBranch, vmDt, reg1=leftReg, reg2=rightReg, labelSymbol = elseLabel)
|
||||
code += translateNode(ifElse.ifScope)
|
||||
code += VmCodeInstruction(Opcode.JUMP, labelSymbol = afterIfLabel)
|
||||
code += VmCodeLabel(elseLabel)
|
||||
code += translateNode(ifElse.elseScope)
|
||||
code += VmCodeLabel(afterIfLabel)
|
||||
} else {
|
||||
// only if part
|
||||
val afterIfLabel = createLabelName()
|
||||
code += VmCodeInstruction(elseBranch, vmDt, reg1=leftReg, reg2=rightReg, labelSymbol = afterIfLabel)
|
||||
code += translateNode(ifElse.ifScope)
|
||||
code += VmCodeLabel(afterIfLabel)
|
||||
}
|
||||
return code
|
||||
}
|
||||
return code
|
||||
|
||||
fun translateZeroComparison(): VmCodeChunk {
|
||||
fun equalOrNotEqualZero(elseBranch: Opcode): VmCodeChunk {
|
||||
val leftReg = vmRegisters.nextFree()
|
||||
code += expressionEval.translateExpression(ifElse.condition.left, leftReg, -1)
|
||||
if(ifElse.elseScope.children.isNotEmpty()) {
|
||||
// if and else parts
|
||||
val elseLabel = createLabelName()
|
||||
val afterIfLabel = createLabelName()
|
||||
code += VmCodeInstruction(elseBranch, vmDt, reg1=leftReg, labelSymbol = elseLabel)
|
||||
code += translateNode(ifElse.ifScope)
|
||||
code += VmCodeInstruction(Opcode.JUMP, labelSymbol = afterIfLabel)
|
||||
code += VmCodeLabel(elseLabel)
|
||||
code += translateNode(ifElse.elseScope)
|
||||
code += VmCodeLabel(afterIfLabel)
|
||||
} else {
|
||||
// only if part
|
||||
val afterIfLabel = createLabelName()
|
||||
code += VmCodeInstruction(elseBranch, vmDt, reg1=leftReg, labelSymbol = afterIfLabel)
|
||||
code += translateNode(ifElse.ifScope)
|
||||
code += VmCodeLabel(afterIfLabel)
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
return when (ifElse.condition.operator) {
|
||||
"==" -> {
|
||||
// if X==0 ... so we just branch on left expr is Not-zero.
|
||||
equalOrNotEqualZero(Opcode.BNZ)
|
||||
}
|
||||
"!=" -> {
|
||||
// if X!=0 ... so we just branch on left expr is Zero.
|
||||
equalOrNotEqualZero(Opcode.BZ)
|
||||
}
|
||||
else -> {
|
||||
// another comparison against 0, just use regular codegen for this.
|
||||
translateNonZeroComparison()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return if(constValue(ifElse.condition.right)==0.0)
|
||||
translateZeroComparison()
|
||||
else
|
||||
translateNonZeroComparison()
|
||||
}
|
||||
|
||||
|
||||
private fun translate(postIncrDecr: PtPostIncrDecr): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val operation = when(postIncrDecr.operator) {
|
||||
"++" -> Opcode.INC
|
||||
"--" -> Opcode.DEC
|
||||
val operationMem: Opcode
|
||||
val operationRegister: Opcode
|
||||
when(postIncrDecr.operator) {
|
||||
"++" -> {
|
||||
operationMem = Opcode.INCM
|
||||
operationRegister = Opcode.INC
|
||||
}
|
||||
"--" -> {
|
||||
operationMem = Opcode.DECM
|
||||
operationRegister = Opcode.DEC
|
||||
}
|
||||
else -> throw AssemblyError("weird operator")
|
||||
}
|
||||
val ident = postIncrDecr.target.identifier
|
||||
val memory = postIncrDecr.target.memory
|
||||
val array = postIncrDecr.target.array
|
||||
val vmDt = vmType(postIncrDecr.target.type)
|
||||
val resultReg = vmRegisters.nextFree()
|
||||
if(ident!=null) {
|
||||
val address = allocations.get(ident.targetName)
|
||||
code += VmCodeInstruction(Opcode.LOADM, vmDt, reg1=resultReg, value = address)
|
||||
code += VmCodeInstruction(operation, vmDt, reg1=resultReg)
|
||||
code += VmCodeInstruction(Opcode.STOREM, vmDt, reg1=resultReg, value = address)
|
||||
code += VmCodeInstruction(operationMem, vmDt, value = address)
|
||||
} else if(memory!=null) {
|
||||
val addressReg = vmRegisters.nextFree()
|
||||
code += expressionEval.translateExpression(memory.address, addressReg)
|
||||
code += VmCodeInstruction(Opcode.LOADI, vmDt, reg1=resultReg, reg2=addressReg)
|
||||
code += VmCodeInstruction(operation, vmDt, reg1=resultReg)
|
||||
code += VmCodeInstruction(Opcode.STOREI, vmDt, reg1=resultReg, reg2=addressReg)
|
||||
if(memory.address is PtNumber) {
|
||||
val address = (memory.address as PtNumber).number.toInt()
|
||||
code += VmCodeInstruction(operationMem, vmDt, value = address)
|
||||
} else {
|
||||
val incReg = vmRegisters.nextFree()
|
||||
val addressReg = vmRegisters.nextFree()
|
||||
code += expressionEval.translateExpression(memory.address, addressReg, -1)
|
||||
code += VmCodeInstruction(Opcode.LOADI, vmDt, reg1 = incReg, reg2 = addressReg)
|
||||
code += VmCodeInstruction(operationRegister, vmDt, reg1 = incReg)
|
||||
code += VmCodeInstruction(Opcode.STOREI, vmDt, reg1 = incReg, reg2 = addressReg)
|
||||
}
|
||||
} else if (array!=null) {
|
||||
val variable = array.variable.targetName
|
||||
var variableAddr = allocations.get(variable)
|
||||
val itemsize = program.memsizer.memorySize(array.type)
|
||||
val fixedIndex = (array.index as? PtNumber)?.number?.toInt()
|
||||
val memOp = when(postIncrDecr.operator) {
|
||||
"++" -> Opcode.INCM
|
||||
"--" -> Opcode.DECM
|
||||
else -> throw AssemblyError("weird operator")
|
||||
}
|
||||
val fixedIndex = constIntValue(array.index)
|
||||
if(fixedIndex!=null) {
|
||||
variableAddr += fixedIndex*itemsize
|
||||
code += VmCodeInstruction(memOp, vmDt, value=variableAddr)
|
||||
code += VmCodeInstruction(operationMem, vmDt, value=variableAddr)
|
||||
} else {
|
||||
val incReg = vmRegisters.nextFree()
|
||||
val indexReg = vmRegisters.nextFree()
|
||||
code += expressionEval.translateExpression(array.index, indexReg)
|
||||
code += VmCodeInstruction(Opcode.LOADX, vmDt, reg1=resultReg, reg2=indexReg, value=variableAddr)
|
||||
code += VmCodeInstruction(operation, vmDt, reg1=resultReg)
|
||||
code += VmCodeInstruction(Opcode.STOREX, vmDt, reg1=resultReg, reg2=indexReg, value=variableAddr)
|
||||
code += expressionEval.translateExpression(array.index, indexReg, -1)
|
||||
code += VmCodeInstruction(Opcode.LOADX, vmDt, reg1=incReg, reg2=indexReg, value=variableAddr)
|
||||
code += VmCodeInstruction(operationRegister, vmDt, reg1=incReg)
|
||||
code += VmCodeInstruction(Opcode.STOREX, vmDt, reg1=incReg, reg2=indexReg, value=variableAddr)
|
||||
}
|
||||
} else
|
||||
throw AssemblyError("weird assigntarget")
|
||||
@ -453,24 +727,24 @@ class CodeGen(internal val program: PtProgram,
|
||||
}
|
||||
|
||||
private fun translate(repeat: PtRepeatLoop): VmCodeChunk {
|
||||
if((repeat.count as? PtNumber)?.number==0.0)
|
||||
return VmCodeChunk()
|
||||
if((repeat.count as? PtNumber)?.number==1.0)
|
||||
return translateGroup(repeat.children)
|
||||
if((repeat.count as? PtNumber)?.number==256.0) {
|
||||
// 256 iterations can still be done with just a byte counter if you set it to zero as starting value.
|
||||
repeat.children[0] = PtNumber(DataType.UBYTE, 0.0, repeat.count.position)
|
||||
when (constIntValue(repeat.count)) {
|
||||
0 -> return VmCodeChunk()
|
||||
1 -> return translateGroup(repeat.children)
|
||||
256 -> {
|
||||
// 256 iterations can still be done with just a byte counter if you set it to zero as starting value.
|
||||
repeat.children[0] = PtNumber(DataType.UBYTE, 0.0, repeat.count.position)
|
||||
}
|
||||
}
|
||||
|
||||
val code = VmCodeChunk()
|
||||
val counterReg = vmRegisters.nextFree()
|
||||
val vmDt = vmType(repeat.count.type)
|
||||
code += expressionEval.translateExpression(repeat.count, counterReg)
|
||||
code += expressionEval.translateExpression(repeat.count, counterReg, -1)
|
||||
val repeatLabel = createLabelName()
|
||||
code += VmCodeLabel(repeatLabel)
|
||||
code += translateNode(repeat.statements)
|
||||
code += VmCodeInstruction(Opcode.DEC, vmDt, reg1=counterReg)
|
||||
code += VmCodeInstruction(Opcode.BNZ, vmDt, reg1=counterReg, symbol = repeatLabel)
|
||||
code += VmCodeInstruction(Opcode.BNZ, vmDt, reg1=counterReg, labelSymbol = repeatLabel)
|
||||
return code
|
||||
}
|
||||
|
||||
@ -479,9 +753,9 @@ class CodeGen(internal val program: PtProgram,
|
||||
if(jump.address!=null)
|
||||
throw AssemblyError("cannot jump to memory location in the vm target")
|
||||
code += if(jump.generatedLabel!=null)
|
||||
VmCodeInstruction(Opcode.JUMP, symbol = listOf(jump.generatedLabel!!))
|
||||
VmCodeInstruction(Opcode.JUMP, labelSymbol = listOf(jump.generatedLabel!!))
|
||||
else if(jump.identifier!=null)
|
||||
VmCodeInstruction(Opcode.JUMP, symbol = jump.identifier!!.targetName)
|
||||
VmCodeInstruction(Opcode.JUMP, labelSymbol = jump.identifier!!.targetName)
|
||||
else
|
||||
throw AssemblyError("weird jump")
|
||||
return code
|
||||
@ -493,62 +767,21 @@ class CodeGen(internal val program: PtProgram,
|
||||
return code
|
||||
}
|
||||
|
||||
private fun translate(assignment: PtAssignment): VmCodeChunk {
|
||||
// TODO can in-place assignments (assignment.augmentable = true) be optimized more?
|
||||
|
||||
val code = VmCodeChunk()
|
||||
val resultRegister = vmRegisters.nextFree()
|
||||
code += expressionEval.translateExpression(assignment.value, resultRegister)
|
||||
val ident = assignment.target.identifier
|
||||
val memory = assignment.target.memory
|
||||
val array = assignment.target.array
|
||||
val vmDt = vmType(assignment.value.type)
|
||||
if(ident!=null) {
|
||||
val address = allocations.get(ident.targetName)
|
||||
code += VmCodeInstruction(Opcode.STOREM, vmDt, reg1=resultRegister, value=address)
|
||||
}
|
||||
else if(array!=null) {
|
||||
val variable = array.variable.targetName
|
||||
var variableAddr = allocations.get(variable)
|
||||
val itemsize = program.memsizer.memorySize(array.type)
|
||||
val fixedIndex = (array.index as? PtNumber)?.number?.toInt()
|
||||
val vmDtArrayIdx = vmType(array.type)
|
||||
if(fixedIndex!=null) {
|
||||
variableAddr += fixedIndex*itemsize
|
||||
code += VmCodeInstruction(Opcode.STOREM, vmDtArrayIdx, reg1 = resultRegister, value=variableAddr)
|
||||
} else {
|
||||
val indexReg = vmRegisters.nextFree()
|
||||
code += expressionEval.translateExpression(array.index, indexReg)
|
||||
code += VmCodeInstruction(Opcode.STOREX, vmDtArrayIdx, reg1 = resultRegister, reg2=indexReg, value=variableAddr)
|
||||
}
|
||||
}
|
||||
else if(memory!=null) {
|
||||
if(memory.address is PtNumber) {
|
||||
code += VmCodeInstruction(Opcode.STOREM, vmDt, reg1=resultRegister, value=(memory.address as PtNumber).number.toInt())
|
||||
} else {
|
||||
val addressRegister = vmRegisters.nextFree()
|
||||
code += expressionEval.translateExpression(assignment.value, addressRegister)
|
||||
code += VmCodeInstruction(Opcode.STOREI, vmDt, reg1=resultRegister, reg2=addressRegister)
|
||||
}
|
||||
}
|
||||
else
|
||||
throw AssemblyError("weird assigntarget")
|
||||
return code
|
||||
}
|
||||
|
||||
private fun translate(ret: PtReturn): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val value = ret.value
|
||||
if(value!=null) {
|
||||
// Call Convention: return value is always returned in r0
|
||||
code += expressionEval.translateExpression(value, 0)
|
||||
// Call Convention: return value is always returned in r0 (or fr0 if float)
|
||||
code += if(value.type==DataType.FLOAT)
|
||||
expressionEval.translateExpression(value, -1, 0)
|
||||
else
|
||||
expressionEval.translateExpression(value, 0, -1)
|
||||
}
|
||||
code += VmCodeInstruction(Opcode.RETURN)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun translate(sub: PtSub): VmCodeChunk {
|
||||
// TODO actually inline subroutines marked as inline
|
||||
val code = VmCodeChunk()
|
||||
code += VmCodeComment("SUB: ${sub.scopedName} -> ${sub.returntype}")
|
||||
code += VmCodeLabel(sub.scopedName)
|
||||
@ -577,6 +810,7 @@ class CodeGen(internal val program: PtProgram,
|
||||
DataType.BYTE -> VmDataType.BYTE
|
||||
DataType.UWORD,
|
||||
DataType.WORD -> VmDataType.WORD
|
||||
DataType.FLOAT -> VmDataType.FLOAT
|
||||
in PassByReferenceDatatypes -> VmDataType.WORD
|
||||
else -> throw AssemblyError("no vm datatype for $type")
|
||||
}
|
||||
@ -590,4 +824,9 @@ class CodeGen(internal val program: PtProgram,
|
||||
|
||||
internal fun translateBuiltinFunc(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk =
|
||||
builtinFuncGen.translate(call, resultRegister)
|
||||
|
||||
internal fun isZero(expression: PtExpression): Boolean = expression is PtNumber && expression.number==0.0
|
||||
|
||||
internal fun isOne(expression: PtExpression): Boolean = expression is PtNumber && expression.number==1.0
|
||||
}
|
||||
|
||||
|
@ -3,50 +3,66 @@ package prog8.codegen.virtual
|
||||
import prog8.code.StStaticVariable
|
||||
import prog8.code.StSub
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.AssemblyError
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.PassByValueDatatypes
|
||||
import prog8.code.core.SignedDatatypes
|
||||
import prog8.code.core.*
|
||||
import prog8.vm.Opcode
|
||||
import prog8.vm.VmDataType
|
||||
|
||||
|
||||
internal class ExpressionGen(private val codeGen: CodeGen) {
|
||||
fun translateExpression(expr: PtExpression, resultRegister: Int): VmCodeChunk {
|
||||
fun translateExpression(expr: PtExpression, resultRegister: Int, resultFpRegister: Int): VmCodeChunk {
|
||||
require(codeGen.vmRegisters.peekNext() > resultRegister)
|
||||
|
||||
val code = VmCodeChunk()
|
||||
val vmDt = codeGen.vmType(expr.type)
|
||||
|
||||
when (expr) {
|
||||
is PtMachineRegister -> {
|
||||
if(resultRegister!=expr.register) {
|
||||
val vmDt = codeGen.vmType(expr.type)
|
||||
code += VmCodeInstruction(Opcode.LOADR, vmDt, reg1=resultRegister, reg2=expr.register)
|
||||
}
|
||||
}
|
||||
is PtNumber -> {
|
||||
code += VmCodeInstruction(Opcode.LOAD, vmDt, reg1=resultRegister, value=expr.number.toInt())
|
||||
val vmDt = codeGen.vmType(expr.type)
|
||||
code += if(vmDt==VmDataType.FLOAT)
|
||||
VmCodeInstruction(Opcode.LOAD, vmDt, fpReg1 = resultFpRegister, fpValue = expr.number.toFloat())
|
||||
else
|
||||
VmCodeInstruction(Opcode.LOAD, vmDt, reg1=resultRegister, value=expr.number.toInt())
|
||||
}
|
||||
is PtIdentifier -> {
|
||||
val vmDt = codeGen.vmType(expr.type)
|
||||
val mem = codeGen.allocations.get(expr.targetName)
|
||||
code += if(expr.type in PassByValueDatatypes) {
|
||||
VmCodeInstruction(Opcode.LOADM, vmDt, reg1=resultRegister, value=mem)
|
||||
code += if (expr.type in PassByValueDatatypes) {
|
||||
if(vmDt==VmDataType.FLOAT)
|
||||
VmCodeInstruction(Opcode.LOADM, vmDt, fpReg1 = resultFpRegister, value = mem)
|
||||
else
|
||||
VmCodeInstruction(Opcode.LOADM, vmDt, reg1 = resultRegister, value = mem)
|
||||
} else {
|
||||
// for strings and arrays etc., load the *address* of the value instead
|
||||
VmCodeInstruction(Opcode.LOAD, vmDt, reg1=resultRegister, value=mem)
|
||||
VmCodeInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, value = mem)
|
||||
}
|
||||
}
|
||||
is PtAddressOf -> {
|
||||
val vmDt = codeGen.vmType(expr.type)
|
||||
val mem = codeGen.allocations.get(expr.identifier.targetName)
|
||||
code += VmCodeInstruction(Opcode.LOAD, vmDt, reg1=resultRegister, value=mem)
|
||||
}
|
||||
is PtMemoryByte -> {
|
||||
val addressRegister = codeGen.vmRegisters.nextFree()
|
||||
val addressExprCode = translateExpression(expr.address, addressRegister)
|
||||
code += addressExprCode
|
||||
if(expr.address is PtNumber) {
|
||||
val address = (expr.address as PtNumber).number.toInt()
|
||||
code += VmCodeInstruction(Opcode.LOADM, VmDataType.BYTE, reg1=resultRegister, value = address)
|
||||
} else {
|
||||
val addressRegister = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(expr.address, addressRegister, -1)
|
||||
code += VmCodeInstruction(Opcode.LOADI, VmDataType.BYTE, reg1=resultRegister, reg2=addressRegister)
|
||||
}
|
||||
}
|
||||
is PtTypeCast -> code += translate(expr, resultRegister)
|
||||
is PtTypeCast -> code += translate(expr, resultRegister, resultFpRegister)
|
||||
is PtPrefix -> code += translate(expr, resultRegister)
|
||||
is PtArrayIndexer -> code += translate(expr, resultRegister)
|
||||
is PtBinaryExpression -> code += translate(expr, resultRegister)
|
||||
is PtArrayIndexer -> code += translate(expr, resultRegister, resultFpRegister)
|
||||
is PtBinaryExpression -> code += translate(expr, resultRegister, resultFpRegister)
|
||||
is PtBuiltinFunctionCall -> code += codeGen.translateBuiltinFunc(expr, resultRegister)
|
||||
is PtFunctionCall -> code += translate(expr, resultRegister)
|
||||
is PtContainmentCheck -> code += translate(expr, resultRegister)
|
||||
is PtFunctionCall -> code += translate(expr, resultRegister, resultFpRegister)
|
||||
is PtContainmentCheck -> code += translate(expr, resultRegister, resultFpRegister)
|
||||
is PtPipe -> code += translate(expr, resultRegister)
|
||||
is PtRange,
|
||||
is PtArray,
|
||||
@ -57,60 +73,104 @@ internal class ExpressionGen(private val codeGen: CodeGen) {
|
||||
}
|
||||
|
||||
internal fun translate(pipe: PtPipe, resultRegister: Int): VmCodeChunk {
|
||||
TODO("Not yet implemented: pipe expression")
|
||||
val segments = pipe.segments
|
||||
var valueDt = segments[0].type
|
||||
var valueReg = if(pipe.void) codeGen.vmRegisters.nextFree() else resultRegister
|
||||
|
||||
fun addImplicitArgToSegment(segment: PtExpression, sourceReg: Int, sourceDt: DataType): PtExpression {
|
||||
return when (segment) {
|
||||
is PtFunctionCall -> {
|
||||
val segWithArg = PtFunctionCall(segment.functionName, segment.void, segment.type, segment.position)
|
||||
segWithArg.children.add(PtMachineRegister(sourceReg, sourceDt, segment.position))
|
||||
segWithArg.children.addAll(segment.args)
|
||||
segWithArg
|
||||
}
|
||||
is PtBuiltinFunctionCall -> {
|
||||
val segWithArg = PtBuiltinFunctionCall(segment.name, segment.void, segment.hasNoSideEffects, segment.type, segment.position)
|
||||
segWithArg.children.add(PtMachineRegister(sourceReg, sourceDt, segment.position))
|
||||
segWithArg.children.addAll(segment.args)
|
||||
segWithArg
|
||||
}
|
||||
else -> throw AssemblyError("weird segment type")
|
||||
}
|
||||
}
|
||||
|
||||
val code = VmCodeChunk()
|
||||
code += translateExpression(segments[0], valueReg, -1)
|
||||
for (segment in segments.subList(1, segments.size-1)) {
|
||||
val sourceReg = valueReg
|
||||
val sourceDt = valueDt
|
||||
if(segment.type!=valueDt) {
|
||||
valueDt = segment.type
|
||||
valueReg = codeGen.vmRegisters.nextFree()
|
||||
}
|
||||
val segmentWithImplicitArgument = addImplicitArgToSegment(segment, sourceReg, sourceDt)
|
||||
code += translateExpression(segmentWithImplicitArgument, valueReg, -1)
|
||||
}
|
||||
val segWithArg = addImplicitArgToSegment(segments.last(), valueReg, valueDt)
|
||||
code += translateExpression(segWithArg, resultRegister, -1)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun translate(check: PtContainmentCheck, resultRegister: Int): VmCodeChunk {
|
||||
private fun translate(check: PtContainmentCheck, resultRegister: Int, resultFpRegister: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
code += translateExpression(check.element, resultRegister) // load the element to check in resultRegister
|
||||
code += translateExpression(check.element, resultRegister, -1) // load the element to check in resultRegister
|
||||
val iterable = codeGen.symbolTable.flat.getValue(check.iterable.targetName) as StStaticVariable
|
||||
when(iterable.dt) {
|
||||
DataType.STR -> {
|
||||
val call = PtFunctionCall(listOf("prog8_lib", "string_contains"), false, DataType.UBYTE, check.position)
|
||||
call.children.add(check.element)
|
||||
call.children.add(check.iterable)
|
||||
code += translate(call, resultRegister)
|
||||
code += translate(call, resultRegister, resultFpRegister)
|
||||
}
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||
val call = PtFunctionCall(listOf("prog8_lib", "bytearray_contains"), false, DataType.UBYTE, check.position)
|
||||
call.children.add(check.element)
|
||||
call.children.add(check.iterable)
|
||||
call.children.add(PtNumber(DataType.UBYTE, iterable.length!!.toDouble(), iterable.position))
|
||||
code += translate(call, resultRegister)
|
||||
code += translate(call, resultRegister, resultFpRegister)
|
||||
}
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
val call = PtFunctionCall(listOf("prog8_lib", "wordarray_contains"), false, DataType.UBYTE, check.position)
|
||||
call.children.add(check.element)
|
||||
call.children.add(check.iterable)
|
||||
call.children.add(PtNumber(DataType.UBYTE, iterable.length!!.toDouble(), iterable.position))
|
||||
code += translate(call, resultRegister)
|
||||
code += translate(call, resultRegister, resultFpRegister)
|
||||
}
|
||||
DataType.ARRAY_F -> TODO("containment check in float-array")
|
||||
DataType.ARRAY_F -> throw AssemblyError("containment check in float-array not supported")
|
||||
else -> throw AssemblyError("weird iterable dt ${iterable.dt} for ${check.iterable.targetName}")
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
private fun translate(arrayIx: PtArrayIndexer, resultRegister: Int): VmCodeChunk {
|
||||
private fun translate(arrayIx: PtArrayIndexer, resultRegister: Int, resultFpRegister: Int): VmCodeChunk {
|
||||
val eltSize = codeGen.program.memsizer.memorySize(arrayIx.type)
|
||||
val vmDt = codeGen.vmType(arrayIx.type)
|
||||
val code = VmCodeChunk()
|
||||
val idxReg = codeGen.vmRegisters.nextFree()
|
||||
// TODO: optimized code when the index is a constant value
|
||||
code += translateExpression(arrayIx.index, idxReg)
|
||||
if(eltSize>1) {
|
||||
val factorReg = codeGen.vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=factorReg, value=eltSize)
|
||||
code += VmCodeInstruction(Opcode.MUL, VmDataType.BYTE, reg1=idxReg, reg2=idxReg, reg3=factorReg)
|
||||
}
|
||||
val arrayLocation = codeGen.allocations.get(arrayIx.variable.targetName)
|
||||
code += VmCodeInstruction(Opcode.LOADX, vmDt, reg1=resultRegister, reg2=idxReg, value = arrayLocation)
|
||||
if(arrayIx.index is PtNumber) {
|
||||
// optimized code when index is known - just calculate the memory address here
|
||||
val memOffset = (arrayIx.index as PtNumber).number.toInt() * eltSize
|
||||
if(vmDt==VmDataType.FLOAT)
|
||||
code += VmCodeInstruction(Opcode.LOADM, VmDataType.FLOAT, fpReg1=resultFpRegister, value=arrayLocation+memOffset)
|
||||
else
|
||||
code += VmCodeInstruction(Opcode.LOADM, vmDt, reg1=resultRegister, value=arrayLocation+memOffset)
|
||||
} else {
|
||||
code += translateExpression(arrayIx.index, idxReg, -1)
|
||||
if(eltSize>1)
|
||||
code += codeGen.multiplyByConst(VmDataType.BYTE, idxReg, eltSize)
|
||||
if(vmDt==VmDataType.FLOAT)
|
||||
code += VmCodeInstruction(Opcode.LOADX, VmDataType.FLOAT, fpReg1 = resultFpRegister, reg1=idxReg, value = arrayLocation)
|
||||
else
|
||||
code += VmCodeInstruction(Opcode.LOADX, vmDt, reg1=resultRegister, reg2=idxReg, value = arrayLocation)
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
private fun translate(expr: PtPrefix, resultRegister: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
code += translateExpression(expr.value, resultRegister)
|
||||
code += translateExpression(expr.value, resultRegister, -1)
|
||||
val vmDt = codeGen.vmType(expr.type)
|
||||
when(expr.operator) {
|
||||
"+" -> { }
|
||||
@ -121,43 +181,40 @@ internal class ExpressionGen(private val codeGen: CodeGen) {
|
||||
val regMask = codeGen.vmRegisters.nextFree()
|
||||
val mask = if(vmDt==VmDataType.BYTE) 0x00ff else 0xffff
|
||||
code += VmCodeInstruction(Opcode.LOAD, vmDt, reg1=regMask, value=mask)
|
||||
code += VmCodeInstruction(Opcode.XOR, vmDt, reg1=resultRegister, reg2=resultRegister, reg3=regMask)
|
||||
code += VmCodeInstruction(Opcode.XOR, vmDt, reg1=resultRegister, reg2=regMask)
|
||||
}
|
||||
"not" -> {
|
||||
val label = codeGen.createLabelName()
|
||||
code += VmCodeInstruction(Opcode.BZ, vmDt, reg1=resultRegister, symbol = label)
|
||||
code += VmCodeInstruction(Opcode.LOAD, vmDt, reg1=resultRegister, value=1)
|
||||
code += VmCodeLabel(label)
|
||||
val regMask = codeGen.vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.LOAD, vmDt, reg1=regMask, value=1)
|
||||
code += VmCodeInstruction(Opcode.XOR, vmDt, reg1=resultRegister, reg2=resultRegister, reg3=regMask)
|
||||
code += VmCodeInstruction(Opcode.NOT, vmDt, reg1=resultRegister)
|
||||
}
|
||||
else -> throw AssemblyError("weird prefix operator")
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
private fun translate(cast: PtTypeCast, resultRegister: Int): VmCodeChunk {
|
||||
private fun translate(cast: PtTypeCast, predefinedResultRegister: Int, predefinedResultFpRegister: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
if(cast.type==cast.value.type)
|
||||
return code
|
||||
code += translateExpression(cast.value, resultRegister)
|
||||
val actualResultFpReg = if(predefinedResultFpRegister>=0) predefinedResultFpRegister else codeGen.vmRegisters.nextFreeFloat()
|
||||
val actualResultReg = if(predefinedResultRegister>=0) predefinedResultRegister else codeGen.vmRegisters.nextFree()
|
||||
if(cast.value.type==DataType.FLOAT) {
|
||||
// a cast from float to integer, so evaluate the value into a float register first
|
||||
code += translateExpression(cast.value, -1, actualResultFpReg)
|
||||
}
|
||||
else
|
||||
code += translateExpression(cast.value, actualResultReg, -1)
|
||||
when(cast.type) {
|
||||
DataType.UBYTE -> {
|
||||
when(cast.value.type) {
|
||||
DataType.BYTE, DataType.UWORD, DataType.WORD -> { /* just keep the LSB as it is */ }
|
||||
DataType.FLOAT -> {
|
||||
TODO("float -> ubyte") // float not yet supported
|
||||
}
|
||||
DataType.FLOAT -> code += VmCodeInstruction(Opcode.FTOUB, VmDataType.FLOAT, reg1=actualResultReg, fpReg1 = actualResultFpReg)
|
||||
else -> throw AssemblyError("weird cast value type")
|
||||
}
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
when(cast.value.type) {
|
||||
DataType.UBYTE, DataType.UWORD, DataType.WORD -> { /* just keep the LSB as it is */ }
|
||||
DataType.FLOAT -> {
|
||||
TODO("float -> byte") // float not yet supported
|
||||
}
|
||||
DataType.FLOAT -> code += VmCodeInstruction(Opcode.FTOSB, VmDataType.FLOAT, reg1=actualResultReg, fpReg1 = actualResultFpReg)
|
||||
else -> throw AssemblyError("weird cast value type")
|
||||
}
|
||||
}
|
||||
@ -165,15 +222,15 @@ internal class ExpressionGen(private val codeGen: CodeGen) {
|
||||
when(cast.value.type) {
|
||||
DataType.BYTE -> {
|
||||
// byte -> uword: sign extend
|
||||
code += VmCodeInstruction(Opcode.EXTS, type = VmDataType.BYTE, reg1 = resultRegister)
|
||||
code += VmCodeInstruction(Opcode.EXTS, type = VmDataType.BYTE, reg1 = actualResultReg)
|
||||
}
|
||||
DataType.UBYTE -> {
|
||||
// ubyte -> uword: sign extend
|
||||
code += VmCodeInstruction(Opcode.EXT, type = VmDataType.BYTE, reg1 = resultRegister)
|
||||
code += VmCodeInstruction(Opcode.EXT, type = VmDataType.BYTE, reg1 = actualResultReg)
|
||||
}
|
||||
DataType.WORD -> { }
|
||||
DataType.FLOAT -> {
|
||||
TODO("float -> uword") // float not yet supported
|
||||
code += VmCodeInstruction(Opcode.FTOUW, VmDataType.FLOAT, reg1=actualResultReg, fpReg1 = actualResultFpReg)
|
||||
}
|
||||
else -> throw AssemblyError("weird cast value type")
|
||||
}
|
||||
@ -182,123 +239,606 @@ internal class ExpressionGen(private val codeGen: CodeGen) {
|
||||
when(cast.value.type) {
|
||||
DataType.BYTE -> {
|
||||
// byte -> word: sign extend
|
||||
code += VmCodeInstruction(Opcode.EXTS, type = VmDataType.BYTE, reg1 = resultRegister)
|
||||
code += VmCodeInstruction(Opcode.EXTS, type = VmDataType.BYTE, reg1 = actualResultReg)
|
||||
}
|
||||
DataType.UBYTE -> {
|
||||
// byte -> word: sign extend
|
||||
code += VmCodeInstruction(Opcode.EXT, type = VmDataType.BYTE, reg1 = resultRegister)
|
||||
code += VmCodeInstruction(Opcode.EXT, type = VmDataType.BYTE, reg1 = actualResultReg)
|
||||
}
|
||||
DataType.UWORD -> { }
|
||||
DataType.FLOAT -> {
|
||||
TODO("float -> word") // float not yet supported
|
||||
code += VmCodeInstruction(Opcode.FTOSW, VmDataType.FLOAT, reg1=actualResultReg, fpReg1 = actualResultFpReg)
|
||||
}
|
||||
else -> throw AssemblyError("weird cast value type")
|
||||
}
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
TODO("floating point not yet supported")
|
||||
// when(cast.value.type) {
|
||||
// DataType.BYTE -> {
|
||||
// }
|
||||
// DataType.UBYTE -> {
|
||||
// }
|
||||
// DataType.WORD -> {
|
||||
// }
|
||||
// DataType.UWORD -> {
|
||||
// }
|
||||
// else -> throw AssemblyError("weird cast value type")
|
||||
// }
|
||||
when(cast.value.type) {
|
||||
DataType.UBYTE -> {
|
||||
code += VmCodeInstruction(Opcode.FFROMUB, VmDataType.FLOAT, reg1=actualResultReg, fpReg1 = actualResultFpReg)
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
code += VmCodeInstruction(Opcode.FFROMSB, VmDataType.FLOAT, reg1=actualResultReg, fpReg1 = actualResultFpReg)
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
code += VmCodeInstruction(Opcode.FFROMUW, VmDataType.FLOAT, reg1=actualResultReg, fpReg1 = actualResultFpReg)
|
||||
}
|
||||
DataType.WORD -> {
|
||||
code += VmCodeInstruction(Opcode.FFROMSW, VmDataType.FLOAT, reg1=actualResultReg, fpReg1 = actualResultFpReg)
|
||||
}
|
||||
else -> throw AssemblyError("weird cast value type")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird cast type")
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
private fun translate(binExpr: PtBinaryExpression, resultRegister: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val leftResultReg = codeGen.vmRegisters.nextFree()
|
||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
||||
// TODO: optimized codegen when left or right operand is known 0 or 1 or whatever. But only if this would result in a different opcode such as ADD 1 -> INC, MUL 1 -> NOP
|
||||
// actually optimizing the code should not be done here but in a tailored code optimizer step.
|
||||
val leftCode = translateExpression(binExpr.left, leftResultReg)
|
||||
val rightCode = translateExpression(binExpr.right, rightResultReg)
|
||||
code += leftCode
|
||||
code += rightCode
|
||||
private fun translate(binExpr: PtBinaryExpression, resultRegister: Int, resultFpRegister: Int): VmCodeChunk {
|
||||
val vmDt = codeGen.vmType(binExpr.left.type)
|
||||
val signed = binExpr.left.type in SignedDatatypes
|
||||
when(binExpr.operator) {
|
||||
"+" -> {
|
||||
code += VmCodeInstruction(Opcode.ADD, vmDt, reg1=resultRegister, reg2=leftResultReg, reg3=rightResultReg)
|
||||
}
|
||||
"-" -> {
|
||||
code += VmCodeInstruction(Opcode.SUB, vmDt, reg1=resultRegister, reg2=leftResultReg, reg3=rightResultReg)
|
||||
}
|
||||
"*" -> {
|
||||
code += VmCodeInstruction(Opcode.MUL, vmDt, reg1=resultRegister, reg2=leftResultReg, reg3=rightResultReg)
|
||||
}
|
||||
"/" -> {
|
||||
code += VmCodeInstruction(Opcode.DIV, vmDt, reg1=resultRegister, reg2=leftResultReg, reg3=rightResultReg)
|
||||
}
|
||||
"%" -> {
|
||||
code += VmCodeInstruction(Opcode.MOD, vmDt, reg1=resultRegister, reg2=leftResultReg, reg3=rightResultReg)
|
||||
}
|
||||
"|", "or" -> {
|
||||
code += VmCodeInstruction(Opcode.OR, vmDt, reg1=resultRegister, reg2=leftResultReg, reg3=rightResultReg)
|
||||
}
|
||||
"&", "and" -> {
|
||||
code += VmCodeInstruction(Opcode.AND, vmDt, reg1=resultRegister, reg2=leftResultReg, reg3=rightResultReg)
|
||||
}
|
||||
"^", "xor" -> {
|
||||
code += VmCodeInstruction(Opcode.XOR, vmDt, reg1=resultRegister, reg2=leftResultReg, reg3=rightResultReg)
|
||||
}
|
||||
"<<" -> {
|
||||
code += VmCodeInstruction(Opcode.LSL, vmDt, reg1=resultRegister, reg2=leftResultReg, reg3=rightResultReg)
|
||||
}
|
||||
">>" -> {
|
||||
val opc = if(signed) Opcode.ASR else Opcode.LSR
|
||||
code += VmCodeInstruction(opc, vmDt, reg1=resultRegister, reg2=leftResultReg, reg3=rightResultReg)
|
||||
}
|
||||
"==" -> {
|
||||
code += VmCodeInstruction(Opcode.SEQ, vmDt, reg1=resultRegister, reg2=leftResultReg, reg3=rightResultReg)
|
||||
}
|
||||
"!=" -> {
|
||||
code += VmCodeInstruction(Opcode.SNE, vmDt, reg1=resultRegister, reg2=leftResultReg, reg3=rightResultReg)
|
||||
}
|
||||
"<" -> {
|
||||
val ins = if(signed) Opcode.SLTS else Opcode.SLT
|
||||
code += VmCodeInstruction(ins, vmDt, reg1=resultRegister, reg2=leftResultReg, reg3=rightResultReg)
|
||||
}
|
||||
">" -> {
|
||||
val ins = if(signed) Opcode.SGTS else Opcode.SGT
|
||||
code += VmCodeInstruction(ins, vmDt, reg1=resultRegister, reg2=leftResultReg, reg3=rightResultReg)
|
||||
}
|
||||
"<=" -> {
|
||||
val ins = if(signed) Opcode.SLES else Opcode.SLE
|
||||
code += VmCodeInstruction(ins, vmDt, reg1=resultRegister, reg2=leftResultReg, reg3=rightResultReg)
|
||||
}
|
||||
">=" -> {
|
||||
val ins = if(signed) Opcode.SGES else Opcode.SGE
|
||||
code += VmCodeInstruction(ins, vmDt, reg1=resultRegister, reg2=leftResultReg, reg3=rightResultReg)
|
||||
}
|
||||
return when(binExpr.operator) {
|
||||
"+" -> operatorPlus(binExpr, vmDt, resultRegister, resultFpRegister)
|
||||
"-" -> operatorMinus(binExpr, vmDt, resultRegister, resultFpRegister)
|
||||
"*" -> operatorMultiply(binExpr, vmDt, resultRegister, resultFpRegister)
|
||||
"/" -> operatorDivide(binExpr, vmDt, resultRegister, resultFpRegister, signed)
|
||||
"%" -> operatorModulo(binExpr, vmDt, resultRegister)
|
||||
"|", "or" -> operatorOr(binExpr, vmDt, resultRegister)
|
||||
"&", "and" -> operatorAnd(binExpr, vmDt, resultRegister)
|
||||
"^", "xor" -> operatorXor(binExpr, vmDt, resultRegister)
|
||||
"<<" -> operatorShiftLeft(binExpr, vmDt, resultRegister)
|
||||
">>" -> operatorShiftRight(binExpr, vmDt, resultRegister, signed)
|
||||
"==" -> operatorEquals(binExpr, vmDt, resultRegister, false)
|
||||
"!=" -> operatorEquals(binExpr, vmDt, resultRegister, true)
|
||||
"<" -> operatorLessThan(binExpr, vmDt, resultRegister, signed, false)
|
||||
">" -> operatorGreaterThan(binExpr, vmDt, resultRegister, signed, false)
|
||||
"<=" -> operatorLessThan(binExpr, vmDt, resultRegister, signed, true)
|
||||
">=" -> operatorGreaterThan(binExpr, vmDt, resultRegister, signed, true)
|
||||
else -> throw AssemblyError("weird operator ${binExpr.operator}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun operatorGreaterThan(
|
||||
binExpr: PtBinaryExpression,
|
||||
vmDt: VmDataType,
|
||||
resultRegister: Int,
|
||||
signed: Boolean,
|
||||
greaterEquals: Boolean
|
||||
): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
if(vmDt==VmDataType.FLOAT) {
|
||||
val leftFpReg = codeGen.vmRegisters.nextFreeFloat()
|
||||
val rightFpReg = codeGen.vmRegisters.nextFreeFloat()
|
||||
val zeroRegister = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(binExpr.left, -1, leftFpReg)
|
||||
code += translateExpression(binExpr.right, -1, rightFpReg)
|
||||
code += VmCodeInstruction(Opcode.FCOMP, VmDataType.FLOAT, reg1=resultRegister, fpReg1 = leftFpReg, fpReg2 = rightFpReg)
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=zeroRegister, value=0)
|
||||
val ins = if (signed) {
|
||||
if (greaterEquals) Opcode.SGES else Opcode.SGTS
|
||||
} else {
|
||||
if (greaterEquals) Opcode.SGE else Opcode.SGT
|
||||
}
|
||||
code += VmCodeInstruction(ins, VmDataType.BYTE, reg1 = resultRegister, reg2 = zeroRegister)
|
||||
} else {
|
||||
if(binExpr.left.type==DataType.STR && binExpr.right.type==DataType.STR) {
|
||||
val comparisonCall = PtFunctionCall(listOf("prog8_lib", "string_compare"), false, DataType.BYTE, Position.DUMMY)
|
||||
comparisonCall.children.add(binExpr.left)
|
||||
comparisonCall.children.add(binExpr.right)
|
||||
code += translate(comparisonCall, resultRegister, -1)
|
||||
val zeroRegister = codeGen.vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=zeroRegister, value=0)
|
||||
code += if(greaterEquals)
|
||||
VmCodeInstruction(Opcode.SGES, VmDataType.BYTE, reg1=resultRegister, reg2=zeroRegister)
|
||||
else
|
||||
VmCodeInstruction(Opcode.SGTS, VmDataType.BYTE, reg1=resultRegister, reg2=zeroRegister)
|
||||
} else {
|
||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
||||
val ins = if (signed) {
|
||||
if (greaterEquals) Opcode.SGES else Opcode.SGTS
|
||||
} else {
|
||||
if (greaterEquals) Opcode.SGE else Opcode.SGT
|
||||
}
|
||||
code += VmCodeInstruction(ins, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
fun translate(fcall: PtFunctionCall, resultRegister: Int): VmCodeChunk {
|
||||
private fun operatorLessThan(
|
||||
binExpr: PtBinaryExpression,
|
||||
vmDt: VmDataType,
|
||||
resultRegister: Int,
|
||||
signed: Boolean,
|
||||
lessEquals: Boolean
|
||||
): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
if(vmDt==VmDataType.FLOAT) {
|
||||
val leftFpReg = codeGen.vmRegisters.nextFreeFloat()
|
||||
val rightFpReg = codeGen.vmRegisters.nextFreeFloat()
|
||||
val zeroRegister = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(binExpr.left, -1, leftFpReg)
|
||||
code += translateExpression(binExpr.right, -1, rightFpReg)
|
||||
code += VmCodeInstruction(Opcode.FCOMP, VmDataType.FLOAT, reg1=resultRegister, fpReg1 = leftFpReg, fpReg2 = rightFpReg)
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=zeroRegister, value=0)
|
||||
val ins = if (signed) {
|
||||
if (lessEquals) Opcode.SLES else Opcode.SLTS
|
||||
} else {
|
||||
if (lessEquals) Opcode.SLE else Opcode.SLT
|
||||
}
|
||||
code += VmCodeInstruction(ins, VmDataType.BYTE, reg1 = resultRegister, reg2 = zeroRegister)
|
||||
} else {
|
||||
if(binExpr.left.type==DataType.STR && binExpr.right.type==DataType.STR) {
|
||||
val comparisonCall = PtFunctionCall(listOf("prog8_lib", "string_compare"), false, DataType.BYTE, Position.DUMMY)
|
||||
comparisonCall.children.add(binExpr.left)
|
||||
comparisonCall.children.add(binExpr.right)
|
||||
code += translate(comparisonCall, resultRegister, -1)
|
||||
val zeroRegister = codeGen.vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=zeroRegister, value=0)
|
||||
code += if(lessEquals)
|
||||
VmCodeInstruction(Opcode.SLES, VmDataType.BYTE, reg1=resultRegister, reg2=zeroRegister)
|
||||
else
|
||||
VmCodeInstruction(Opcode.SLTS, VmDataType.BYTE, reg1=resultRegister, reg2=zeroRegister)
|
||||
} else {
|
||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
||||
val ins = if (signed) {
|
||||
if (lessEquals) Opcode.SLES else Opcode.SLTS
|
||||
} else {
|
||||
if (lessEquals) Opcode.SLE else Opcode.SLT
|
||||
}
|
||||
code += VmCodeInstruction(ins, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
private fun operatorEquals(binExpr: PtBinaryExpression, vmDt: VmDataType, resultRegister: Int, notEquals: Boolean): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
if(vmDt==VmDataType.FLOAT) {
|
||||
val leftFpReg = codeGen.vmRegisters.nextFreeFloat()
|
||||
val rightFpReg = codeGen.vmRegisters.nextFreeFloat()
|
||||
code += translateExpression(binExpr.left, -1, leftFpReg)
|
||||
code += translateExpression(binExpr.right, -1, rightFpReg)
|
||||
if (notEquals) {
|
||||
code += VmCodeInstruction(Opcode.FCOMP, VmDataType.FLOAT, reg1=resultRegister, fpReg1 = leftFpReg, fpReg2 = rightFpReg)
|
||||
} else {
|
||||
val label = codeGen.createLabelName()
|
||||
val valueReg = codeGen.vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=resultRegister, value=1)
|
||||
code += VmCodeInstruction(Opcode.FCOMP, VmDataType.FLOAT, reg1=valueReg, fpReg1 = leftFpReg, fpReg2 = rightFpReg)
|
||||
code += VmCodeInstruction(Opcode.BZ, VmDataType.BYTE, reg1=valueReg, labelSymbol = label)
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=resultRegister, value=0)
|
||||
code += VmCodeLabel(label)
|
||||
}
|
||||
} else {
|
||||
if(binExpr.left.type==DataType.STR && binExpr.right.type==DataType.STR) {
|
||||
val comparisonCall = PtFunctionCall(listOf("prog8_lib", "string_compare"), false, DataType.BYTE, Position.DUMMY)
|
||||
comparisonCall.children.add(binExpr.left)
|
||||
comparisonCall.children.add(binExpr.right)
|
||||
code += translate(comparisonCall, resultRegister, -1)
|
||||
if(notEquals) {
|
||||
val maskReg = codeGen.vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.LOAD, vmDt, reg1=maskReg, value=1)
|
||||
code += VmCodeInstruction(Opcode.AND, vmDt, reg1=resultRegister, reg2=maskReg)
|
||||
} else {
|
||||
code += VmCodeInstruction(Opcode.NOT, vmDt, reg1=resultRegister)
|
||||
}
|
||||
} else {
|
||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
||||
val opcode = if (notEquals) Opcode.SNE else Opcode.SEQ
|
||||
code += VmCodeInstruction(opcode, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
private fun operatorShiftRight(binExpr: PtBinaryExpression, vmDt: VmDataType, resultRegister: Int, signed: Boolean): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
if(codeGen.isOne(binExpr.right)) {
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
val opc = if (signed) Opcode.ASR else Opcode.LSR
|
||||
code += VmCodeInstruction(opc, vmDt, reg1 = resultRegister)
|
||||
} else {
|
||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
||||
val opc = if (signed) Opcode.ASRN else Opcode.LSRN
|
||||
code += VmCodeInstruction(opc, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
internal fun operatorShiftRightInplace(address: Int, vmDt: VmDataType, signed: Boolean, operand: PtExpression): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
if(codeGen.isOne(operand)) {
|
||||
val opc = if (signed) Opcode.ASRM else Opcode.LSRM
|
||||
code += VmCodeInstruction(opc, vmDt, value=address)
|
||||
} else {
|
||||
val operandReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(operand, operandReg, -1)
|
||||
val opc = if (signed) Opcode.ASRNM else Opcode.LSRNM
|
||||
code += VmCodeInstruction(opc, vmDt, reg1 = operandReg, value=address)
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
private fun operatorShiftLeft(binExpr: PtBinaryExpression, vmDt: VmDataType, resultRegister: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
if(codeGen.isOne(binExpr.right)){
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += VmCodeInstruction(Opcode.LSL, vmDt, reg1=resultRegister)
|
||||
} else {
|
||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
||||
code += VmCodeInstruction(Opcode.LSLN, vmDt, reg1=resultRegister, rightResultReg)
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
internal fun operatorShiftLeftInplace(address: Int, vmDt: VmDataType, operand: PtExpression): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
if(codeGen.isOne(operand)){
|
||||
code += VmCodeInstruction(Opcode.LSLM, vmDt, value=address)
|
||||
} else {
|
||||
val operandReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(operand, operandReg, -1)
|
||||
code += VmCodeInstruction(Opcode.LSLNM, vmDt, reg1=operandReg, value=address)
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
private fun operatorXor(binExpr: PtBinaryExpression, vmDt: VmDataType, resultRegister: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
||||
code += VmCodeInstruction(Opcode.XOR, vmDt, reg1=resultRegister, reg2=rightResultReg)
|
||||
return code
|
||||
}
|
||||
|
||||
internal fun operatorXorInplace(address: Int, vmDt: VmDataType, operand: PtExpression): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val operandReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(operand, operandReg, -1)
|
||||
code += VmCodeInstruction(Opcode.XORM, vmDt, reg1=operandReg, value = address)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun operatorAnd(binExpr: PtBinaryExpression, vmDt: VmDataType, resultRegister: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
||||
code += VmCodeInstruction(Opcode.AND, vmDt, reg1=resultRegister, reg2=rightResultReg)
|
||||
return code
|
||||
}
|
||||
|
||||
internal fun operatorAndInplace(address: Int, vmDt: VmDataType, operand: PtExpression): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val operandReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(operand, operandReg, -1)
|
||||
code += VmCodeInstruction(Opcode.ANDM, vmDt, reg1=operandReg, value=address)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun operatorOr(binExpr: PtBinaryExpression, vmDt: VmDataType, resultRegister: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
||||
code += VmCodeInstruction(Opcode.OR, vmDt, reg1=resultRegister, reg2=rightResultReg)
|
||||
return code
|
||||
}
|
||||
|
||||
internal fun operatorOrInplace(address: Int, vmDt: VmDataType, operand: PtExpression): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val operandReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(operand, operandReg, -1)
|
||||
code += VmCodeInstruction(Opcode.ORM, vmDt, reg1=operandReg, value = address)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun operatorModulo(binExpr: PtBinaryExpression, vmDt: VmDataType, resultRegister: Int): VmCodeChunk {
|
||||
if(vmDt==VmDataType.FLOAT)
|
||||
throw IllegalArgumentException("floating-point modulo not supported")
|
||||
val code = VmCodeChunk()
|
||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
||||
code += VmCodeInstruction(Opcode.MOD, vmDt, reg1=resultRegister, reg2=rightResultReg)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun operatorDivide(binExpr: PtBinaryExpression,
|
||||
vmDt: VmDataType,
|
||||
resultRegister: Int,
|
||||
resultFpRegister: Int,
|
||||
signed: Boolean): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val constFactorRight = binExpr.right as? PtNumber
|
||||
if(vmDt==VmDataType.FLOAT) {
|
||||
if(constFactorRight!=null && constFactorRight.type!=DataType.FLOAT) {
|
||||
code += translateExpression(binExpr.left, -1, resultFpRegister)
|
||||
val factor = constFactorRight.number.toFloat()
|
||||
code += codeGen.divideByConstFloat(resultFpRegister, factor)
|
||||
} else {
|
||||
val rightResultFpReg = codeGen.vmRegisters.nextFreeFloat()
|
||||
code += translateExpression(binExpr.left, -1, resultFpRegister)
|
||||
code += translateExpression(binExpr.right, -1, rightResultFpReg)
|
||||
code += if(signed)
|
||||
VmCodeInstruction(Opcode.DIVS, vmDt, fpReg1 = resultFpRegister, fpReg2=rightResultFpReg)
|
||||
else
|
||||
VmCodeInstruction(Opcode.DIV, vmDt, fpReg1 = resultFpRegister, fpReg2=rightResultFpReg)
|
||||
}
|
||||
} else {
|
||||
if(constFactorRight!=null && constFactorRight.type!=DataType.FLOAT) {
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
val factor = constFactorRight.number.toInt()
|
||||
code += codeGen.divideByConst(vmDt, resultRegister, factor, signed)
|
||||
} else {
|
||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
||||
code += if(signed)
|
||||
VmCodeInstruction(Opcode.DIVS, vmDt, reg1=resultRegister, reg2=rightResultReg)
|
||||
else
|
||||
VmCodeInstruction(Opcode.DIV, vmDt, reg1=resultRegister, reg2=rightResultReg)
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
internal fun operatorDivideInplace(address: Int, vmDt: VmDataType, signed: Boolean, operand: PtExpression): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val constFactorRight = operand as? PtNumber
|
||||
if(vmDt==VmDataType.FLOAT) {
|
||||
if(constFactorRight!=null && constFactorRight.type!=DataType.FLOAT) {
|
||||
val factor = constFactorRight.number.toFloat()
|
||||
code += codeGen.divideByConstFloatInplace(address, factor)
|
||||
} else {
|
||||
val operandFpReg = codeGen.vmRegisters.nextFreeFloat()
|
||||
code += translateExpression(operand, -1, operandFpReg)
|
||||
code += if(signed)
|
||||
VmCodeInstruction(Opcode.DIVSM, vmDt, fpReg1 = operandFpReg, value=address)
|
||||
else
|
||||
VmCodeInstruction(Opcode.DIVM, vmDt, fpReg1 = operandFpReg, value=address)
|
||||
}
|
||||
} else {
|
||||
if(constFactorRight!=null && constFactorRight.type!=DataType.FLOAT) {
|
||||
val factor = constFactorRight.number.toInt()
|
||||
code += codeGen.divideByConstInplace(vmDt, address, factor, signed)
|
||||
} else {
|
||||
val operandReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(operand, operandReg, -1)
|
||||
code += if(signed)
|
||||
VmCodeInstruction(Opcode.DIVSM, vmDt, reg1=operandReg, value = address)
|
||||
else
|
||||
VmCodeInstruction(Opcode.DIVM, vmDt, reg1=operandReg, value = address)
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
private fun operatorMultiply(binExpr: PtBinaryExpression, vmDt: VmDataType, resultRegister: Int, resultFpRegister: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val constFactorLeft = binExpr.left as? PtNumber
|
||||
val constFactorRight = binExpr.right as? PtNumber
|
||||
if(vmDt==VmDataType.FLOAT) {
|
||||
if(constFactorLeft!=null) {
|
||||
code += translateExpression(binExpr.right, -1, resultFpRegister)
|
||||
val factor = constFactorLeft.number.toFloat()
|
||||
code += codeGen.multiplyByConstFloat(resultFpRegister, factor)
|
||||
} else if(constFactorRight!=null) {
|
||||
code += translateExpression(binExpr.left, -1, resultFpRegister)
|
||||
val factor = constFactorRight.number.toFloat()
|
||||
code += codeGen.multiplyByConstFloat(resultFpRegister, factor)
|
||||
} else {
|
||||
val rightResultFpReg = codeGen.vmRegisters.nextFreeFloat()
|
||||
code += translateExpression(binExpr.left, -1, resultFpRegister)
|
||||
code += translateExpression(binExpr.right, -1, rightResultFpReg)
|
||||
code += VmCodeInstruction(Opcode.MUL, vmDt, fpReg1 = resultFpRegister, fpReg2 = rightResultFpReg)
|
||||
}
|
||||
} else {
|
||||
if(constFactorLeft!=null && constFactorLeft.type!=DataType.FLOAT) {
|
||||
code += translateExpression(binExpr.right, resultRegister, -1)
|
||||
val factor = constFactorLeft.number.toInt()
|
||||
code += codeGen.multiplyByConst(vmDt, resultRegister, factor)
|
||||
} else if(constFactorRight!=null && constFactorRight.type!=DataType.FLOAT) {
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
val factor = constFactorRight.number.toInt()
|
||||
code += codeGen.multiplyByConst(vmDt, resultRegister, factor)
|
||||
} else {
|
||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
||||
code += VmCodeInstruction(Opcode.MUL, vmDt, reg1=resultRegister, reg2=rightResultReg)
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
internal fun operatorMultiplyInplace(address: Int, vmDt: VmDataType, operand: PtExpression): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val constFactorRight = operand as? PtNumber
|
||||
if(vmDt==VmDataType.FLOAT) {
|
||||
if(constFactorRight!=null) {
|
||||
val factor = constFactorRight.number.toFloat()
|
||||
code += codeGen.multiplyByConstFloatInplace(address, factor)
|
||||
} else {
|
||||
val operandFpReg = codeGen.vmRegisters.nextFreeFloat()
|
||||
code += translateExpression(operand, -1, operandFpReg)
|
||||
code += VmCodeInstruction(Opcode.MULM, vmDt, fpReg1 = operandFpReg, value = address)
|
||||
}
|
||||
} else {
|
||||
if(constFactorRight!=null && constFactorRight.type!=DataType.FLOAT) {
|
||||
val factor = constFactorRight.number.toInt()
|
||||
code += codeGen.multiplyByConstInplace(vmDt, address, factor)
|
||||
} else {
|
||||
val operandReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(operand, operandReg, -1)
|
||||
code += VmCodeInstruction(Opcode.MULM, vmDt, reg1=operandReg, value = address)
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
private fun operatorMinus(binExpr: PtBinaryExpression, vmDt: VmDataType, resultRegister: Int, resultFpRegister: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
if(vmDt==VmDataType.FLOAT) {
|
||||
if((binExpr.right as? PtNumber)?.number==1.0) {
|
||||
code += translateExpression(binExpr.left, -1, resultFpRegister)
|
||||
code += VmCodeInstruction(Opcode.DEC, vmDt, fpReg1 = resultFpRegister)
|
||||
}
|
||||
else {
|
||||
val rightResultFpReg = codeGen.vmRegisters.nextFreeFloat()
|
||||
code += translateExpression(binExpr.left, -1, resultFpRegister)
|
||||
code += translateExpression(binExpr.right, -1, rightResultFpReg)
|
||||
code += VmCodeInstruction(Opcode.SUB, vmDt, fpReg1=resultFpRegister, fpReg2=rightResultFpReg)
|
||||
}
|
||||
} else {
|
||||
if((binExpr.right as? PtNumber)?.number==1.0) {
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += VmCodeInstruction(Opcode.DEC, vmDt, reg1=resultRegister)
|
||||
}
|
||||
else {
|
||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
||||
code += VmCodeInstruction(Opcode.SUB, vmDt, reg1=resultRegister, reg2=rightResultReg)
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
internal fun operatorMinusInplace(address: Int, vmDt: VmDataType, operand: PtExpression): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
if(vmDt==VmDataType.FLOAT) {
|
||||
if((operand as? PtNumber)?.number==1.0) {
|
||||
code += VmCodeInstruction(Opcode.DECM, vmDt, value=address)
|
||||
}
|
||||
else {
|
||||
val operandFpReg = codeGen.vmRegisters.nextFreeFloat()
|
||||
code += translateExpression(operand, -1, operandFpReg)
|
||||
code += VmCodeInstruction(Opcode.SUBM, vmDt, fpReg1=operandFpReg, value=address)
|
||||
}
|
||||
} else {
|
||||
if((operand as? PtNumber)?.number==1.0) {
|
||||
code += VmCodeInstruction(Opcode.DECM, vmDt, value=address)
|
||||
}
|
||||
else {
|
||||
val operandReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(operand, operandReg, -1)
|
||||
code += VmCodeInstruction(Opcode.SUBM, vmDt, reg1=operandReg, value = address)
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
private fun operatorPlus(binExpr: PtBinaryExpression, vmDt: VmDataType, resultRegister: Int, resultFpRegister: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
if(vmDt==VmDataType.FLOAT) {
|
||||
if((binExpr.left as? PtNumber)?.number==1.0) {
|
||||
code += translateExpression(binExpr.right, -1, resultFpRegister)
|
||||
code += VmCodeInstruction(Opcode.INC, vmDt, fpReg1=resultFpRegister)
|
||||
}
|
||||
else if((binExpr.right as? PtNumber)?.number==1.0) {
|
||||
code += translateExpression(binExpr.left, -1, resultFpRegister)
|
||||
code += VmCodeInstruction(Opcode.INC, vmDt, fpReg1=resultFpRegister)
|
||||
}
|
||||
else {
|
||||
val rightResultFpReg = codeGen.vmRegisters.nextFreeFloat()
|
||||
code += translateExpression(binExpr.left, -1, resultFpRegister)
|
||||
code += translateExpression(binExpr.right, -1, rightResultFpReg)
|
||||
code += VmCodeInstruction(Opcode.ADD, vmDt, fpReg1=resultFpRegister, fpReg2=rightResultFpReg)
|
||||
}
|
||||
} else {
|
||||
if((binExpr.left as? PtNumber)?.number==1.0) {
|
||||
code += translateExpression(binExpr.right, resultRegister, -1)
|
||||
code += VmCodeInstruction(Opcode.INC, vmDt, reg1=resultRegister)
|
||||
}
|
||||
else if((binExpr.right as? PtNumber)?.number==1.0) {
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += VmCodeInstruction(Opcode.INC, vmDt, reg1=resultRegister)
|
||||
}
|
||||
else {
|
||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
||||
code += VmCodeInstruction(Opcode.ADD, vmDt, reg1=resultRegister, reg2=rightResultReg)
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
internal fun operatorPlusInplace(address: Int, vmDt: VmDataType, operand: PtExpression): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
if(vmDt==VmDataType.FLOAT) {
|
||||
if((operand as? PtNumber)?.number==1.0) {
|
||||
code += VmCodeInstruction(Opcode.INCM, vmDt, value = address)
|
||||
}
|
||||
else {
|
||||
val operandFpReg = codeGen.vmRegisters.nextFreeFloat()
|
||||
code += translateExpression(operand, -1, operandFpReg)
|
||||
code += VmCodeInstruction(Opcode.ADDM, vmDt, fpReg1=operandFpReg, value=address)
|
||||
}
|
||||
} else {
|
||||
if((operand as? PtNumber)?.number==1.0) {
|
||||
code += VmCodeInstruction(Opcode.INCM, vmDt, value = address)
|
||||
}
|
||||
else {
|
||||
val operandReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(operand, operandReg, -1)
|
||||
code += VmCodeInstruction(Opcode.ADDM, vmDt, reg1=operandReg, value=address)
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
fun translate(fcall: PtFunctionCall, resultRegister: Int, resultFpRegister: Int): VmCodeChunk {
|
||||
val subroutine = codeGen.symbolTable.flat.getValue(fcall.functionName) as StSub
|
||||
val code = VmCodeChunk()
|
||||
for ((arg, parameter) in fcall.args.zip(subroutine.parameters)) {
|
||||
val argReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(arg, argReg)
|
||||
val vmDt = codeGen.vmType(parameter.type)
|
||||
val mem = codeGen.allocations.get(fcall.functionName + parameter.name)
|
||||
code += VmCodeInstruction(Opcode.STOREM, vmDt, reg1=argReg, value=mem)
|
||||
val paramDt = codeGen.vmType(parameter.type)
|
||||
if(codeGen.isZero(arg)) {
|
||||
if (paramDt == VmDataType.FLOAT) {
|
||||
val mem = codeGen.allocations.get(fcall.functionName + parameter.name)
|
||||
code += VmCodeInstruction(Opcode.STOREZM, paramDt, value = mem)
|
||||
} else {
|
||||
val mem = codeGen.allocations.get(fcall.functionName + parameter.name)
|
||||
code += VmCodeInstruction(Opcode.STOREZM, paramDt, value = mem)
|
||||
}
|
||||
} else {
|
||||
if (paramDt == VmDataType.FLOAT) {
|
||||
val argFpReg = codeGen.vmRegisters.nextFreeFloat()
|
||||
code += translateExpression(arg, -1, argFpReg)
|
||||
val mem = codeGen.allocations.get(fcall.functionName + parameter.name)
|
||||
code += VmCodeInstruction(Opcode.STOREM, paramDt, fpReg1 = argFpReg, value = mem)
|
||||
} else {
|
||||
val argReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(arg, argReg, -1)
|
||||
val mem = codeGen.allocations.get(fcall.functionName + parameter.name)
|
||||
code += VmCodeInstruction(Opcode.STOREM, paramDt, reg1 = argReg, value = mem)
|
||||
}
|
||||
}
|
||||
}
|
||||
code += VmCodeInstruction(Opcode.CALL, symbol=fcall.functionName)
|
||||
if(!fcall.void && resultRegister!=0) {
|
||||
// Call convention: result value is in r0, so put it in the required register instead.
|
||||
code += VmCodeInstruction(Opcode.LOADR, codeGen.vmType(fcall.type), reg1=resultRegister, reg2=0)
|
||||
code += VmCodeInstruction(Opcode.CALL, labelSymbol=fcall.functionName)
|
||||
if(fcall.type==DataType.FLOAT) {
|
||||
if (!fcall.void && resultFpRegister != 0) {
|
||||
// Call convention: result value is in fr0, so put it in the required register instead.
|
||||
code += VmCodeInstruction(Opcode.LOADR, VmDataType.FLOAT, fpReg1 = resultFpRegister, fpReg2 = 0)
|
||||
}
|
||||
} else {
|
||||
if (!fcall.void && resultRegister != 0) {
|
||||
// Call convention: result value is in r0, so put it in the required register instead.
|
||||
code += VmCodeInstruction(Opcode.LOADR, codeGen.vmType(fcall.type), reg1 = resultRegister, reg2 = 0)
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ class VariableAllocator(private val st: SymbolTable, private val program: PtProg
|
||||
}
|
||||
else -> throw InternalCompilerException("weird dt")
|
||||
}
|
||||
mm.add(Pair(variable.scopedName, "${location.toHex()} $typeStr $value"))
|
||||
mm.add(Pair(variable.scopedName, "$location $typeStr $value"))
|
||||
}
|
||||
return mm
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package prog8.optimizer
|
||||
import prog8.ast.IStatementContainer
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.expressions.AugmentAssignmentOperators
|
||||
import prog8.ast.expressions.BinaryExpression
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.TypecastExpression
|
||||
@ -13,6 +12,7 @@ import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.AssignmentOrigin
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.AugmentAssignmentOperators
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.target.VMTarget
|
||||
@ -97,7 +97,8 @@ X = BinExpr X = LeftExpr
|
||||
// we can see if we can unwrap the binary expression by working on a new temporary variable
|
||||
// (that has the type of the expression), and then finally doing the typecast.
|
||||
// Once it's outside the typecast, the regular splitting can commence.
|
||||
val (tempVarName, _) = program.getTempVar(origExpr.inferType(program).getOr(DataType.UNDEFINED))
|
||||
val tempvarDt = origExpr.inferType(program).getOr(DataType.UNDEFINED)
|
||||
val (tempVarName, _) = program.getTempVar(tempvarDt)
|
||||
val assignTempVar = Assignment(
|
||||
AssignTarget(IdentifierReference(tempVarName, typecast.position), null, null, typecast.position),
|
||||
typecast.expression, AssignmentOrigin.OPTIMIZER, typecast.position
|
||||
|
@ -11,6 +11,7 @@ import prog8.ast.statements.VarDecl
|
||||
import prog8.ast.statements.VarDeclType
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.AssociativeOperators
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.IntegerDatatypes
|
||||
|
||||
@ -107,9 +108,32 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
* (X + c1) - c2 -> X + (c1-c2)
|
||||
*/
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
val leftconst = expr.left.constValue(program)
|
||||
val rightconst = expr.right.constValue(program)
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
|
||||
if(expr.left.inferType(program) istype DataType.STR) {
|
||||
if(expr.operator=="+" && expr.left is StringLiteral && expr.right is StringLiteral) {
|
||||
// concatenate two strings.
|
||||
val leftString = expr.left as StringLiteral
|
||||
val rightString = expr.right as StringLiteral
|
||||
val concatenated = if(leftString.encoding==rightString.encoding) {
|
||||
leftString.value + rightString.value
|
||||
} else {
|
||||
program.encoding.decodeString(
|
||||
program.encoding.encodeString(leftString.value, leftString.encoding) + program.encoding.encodeString(rightString.value, rightString.encoding),
|
||||
leftString.encoding)
|
||||
}
|
||||
val concatStr = StringLiteral(concatenated, leftString.encoding, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, concatStr, parent))
|
||||
}
|
||||
else if(expr.operator=="*" && rightconst!=null && expr.left is StringLiteral) {
|
||||
// mutiply a string.
|
||||
val part = expr.left as StringLiteral
|
||||
val newStr = StringLiteral(part.value.repeat(rightconst.number.toInt()), part.encoding, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newStr, parent))
|
||||
}
|
||||
}
|
||||
|
||||
if(expr.operator=="==" && rightconst!=null) {
|
||||
val leftExpr = expr.left as? BinaryExpression
|
||||
|
@ -6,10 +6,7 @@ import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.code.core.IntegerDatatypes
|
||||
import prog8.code.core.NumericDatatypes
|
||||
import prog8.code.core.*
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.log2
|
||||
import kotlin.math.pow
|
||||
@ -270,6 +267,8 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
|
||||
override fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(functionCallExpr.target.nameInSource == listOf("lsb")) {
|
||||
if(functionCallExpr.args.isEmpty())
|
||||
return noModifications
|
||||
val arg = functionCallExpr.args[0]
|
||||
if(arg is TypecastExpression) {
|
||||
val valueDt = arg.expression.inferType(program)
|
||||
@ -286,6 +285,8 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
}
|
||||
}
|
||||
else if(functionCallExpr.target.nameInSource == listOf("msb")) {
|
||||
if(functionCallExpr.args.isEmpty())
|
||||
return noModifications
|
||||
val arg = functionCallExpr.args[0]
|
||||
if(arg is TypecastExpression) {
|
||||
val valueDt = arg.expression.inferType(program)
|
||||
|
@ -54,6 +54,12 @@ fun Program.optimizeStatements(errors: IErrorReporter,
|
||||
return optimizationCount
|
||||
}
|
||||
|
||||
fun Program.inlineSubroutines(): Int {
|
||||
val inliner = Inliner(this)
|
||||
inliner.visit(this)
|
||||
return inliner.applyModifications()
|
||||
}
|
||||
|
||||
fun Program.simplifyExpressions(errors: IErrorReporter) : Int {
|
||||
val opti = ExpressionSimplifier(this, errors)
|
||||
opti.visit(this)
|
||||
|
208
codeOptimizers/src/prog8/optimizer/Inliner.kt
Normal file
208
codeOptimizers/src/prog8/optimizer/Inliner.kt
Normal file
@ -0,0 +1,208 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.code.core.InternalCompilerException
|
||||
|
||||
|
||||
private fun isEmptyReturn(stmt: Statement): Boolean = stmt is Return && stmt.value==null
|
||||
|
||||
|
||||
class Inliner(val program: Program): AstWalker() {
|
||||
|
||||
class DetermineInlineSubs(val program: Program): IAstVisitor {
|
||||
private val modifications = mutableListOf<IAstModification>()
|
||||
|
||||
init {
|
||||
visit(program)
|
||||
modifications.forEach { it.perform() }
|
||||
modifications.clear()
|
||||
}
|
||||
|
||||
override fun visit(subroutine: Subroutine) {
|
||||
if(!subroutine.isAsmSubroutine && !subroutine.inline && subroutine.parameters.isEmpty()) {
|
||||
val containsSubsOrVariables = subroutine.statements.any { it is VarDecl || it is Subroutine}
|
||||
if(!containsSubsOrVariables) {
|
||||
if(subroutine.statements.size==1 || (subroutine.statements.size==2 && isEmptyReturn(subroutine.statements[1]))) {
|
||||
// subroutine is possible candidate to be inlined
|
||||
subroutine.inline =
|
||||
when(val stmt=subroutine.statements[0]) {
|
||||
is Return -> {
|
||||
if(stmt.value is NumericLiteral)
|
||||
true
|
||||
else if (stmt.value is IdentifierReference) {
|
||||
makeFullyScoped(stmt.value as IdentifierReference)
|
||||
true
|
||||
} else if(stmt.value!! is IFunctionCall && (stmt.value as IFunctionCall).args.size<=1 && (stmt.value as IFunctionCall).args.all { it is NumericLiteral || it is IdentifierReference }) {
|
||||
when (stmt.value) {
|
||||
is BuiltinFunctionCall -> {
|
||||
makeFullyScoped(stmt.value as BuiltinFunctionCall)
|
||||
true
|
||||
}
|
||||
is FunctionCallExpression -> {
|
||||
makeFullyScoped(stmt.value as FunctionCallExpression)
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
} else
|
||||
false
|
||||
}
|
||||
is Assignment -> {
|
||||
if(stmt.value.isSimple) {
|
||||
val targetInline =
|
||||
if(stmt.target.identifier!=null) {
|
||||
makeFullyScoped(stmt.target.identifier!!)
|
||||
true
|
||||
} else if(stmt.target.memoryAddress?.addressExpression is NumericLiteral || stmt.target.memoryAddress?.addressExpression is IdentifierReference) {
|
||||
if(stmt.target.memoryAddress?.addressExpression is IdentifierReference)
|
||||
makeFullyScoped(stmt.target.memoryAddress?.addressExpression as IdentifierReference)
|
||||
true
|
||||
} else
|
||||
false
|
||||
val valueInline =
|
||||
if(stmt.value is IdentifierReference) {
|
||||
makeFullyScoped(stmt.value as IdentifierReference)
|
||||
true
|
||||
} else if((stmt.value as? DirectMemoryRead)?.addressExpression is NumericLiteral || (stmt.value as? DirectMemoryRead)?.addressExpression is IdentifierReference) {
|
||||
if((stmt.value as? DirectMemoryRead)?.addressExpression is IdentifierReference)
|
||||
makeFullyScoped((stmt.value as? DirectMemoryRead)?.addressExpression as IdentifierReference)
|
||||
true
|
||||
} else
|
||||
false
|
||||
targetInline || valueInline
|
||||
} else
|
||||
false
|
||||
}
|
||||
is BuiltinFunctionCallStatement -> {
|
||||
val inline = stmt.args.size<=1 && stmt.args.all { it is NumericLiteral || it is IdentifierReference }
|
||||
if(inline)
|
||||
makeFullyScoped(stmt)
|
||||
inline
|
||||
}
|
||||
is FunctionCallStatement -> {
|
||||
val inline = stmt.args.size<=1 && stmt.args.all { it is NumericLiteral || it is IdentifierReference }
|
||||
if(inline)
|
||||
makeFullyScoped(stmt)
|
||||
inline
|
||||
}
|
||||
is PostIncrDecr -> {
|
||||
if(stmt.target.identifier!=null) {
|
||||
makeFullyScoped(stmt.target.identifier!!)
|
||||
true
|
||||
}
|
||||
else if(stmt.target.memoryAddress?.addressExpression is NumericLiteral || stmt.target.memoryAddress?.addressExpression is IdentifierReference) {
|
||||
if(stmt.target.memoryAddress?.addressExpression is IdentifierReference)
|
||||
makeFullyScoped(stmt.target.memoryAddress?.addressExpression as IdentifierReference)
|
||||
true
|
||||
} else
|
||||
false
|
||||
}
|
||||
is Jump, is GoSub -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
super.visit(subroutine)
|
||||
}
|
||||
|
||||
private fun makeFullyScoped(identifier: IdentifierReference) {
|
||||
val scoped = (identifier.targetStatement(program)!! as INamedStatement).scopedName
|
||||
val scopedIdent = IdentifierReference(scoped, identifier.position)
|
||||
modifications += IAstModification.ReplaceNode(identifier, scopedIdent, identifier.parent)
|
||||
}
|
||||
|
||||
private fun makeFullyScoped(call: BuiltinFunctionCallStatement) {
|
||||
val scopedArgs = makeScopedArgs(call.args)
|
||||
val scopedCall = BuiltinFunctionCallStatement(call.target.copy(), scopedArgs.toMutableList(), call.position)
|
||||
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
|
||||
}
|
||||
|
||||
private fun makeFullyScoped(call: FunctionCallStatement) {
|
||||
val sub = call.target.targetSubroutine(program)!!
|
||||
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
|
||||
val scopedArgs = makeScopedArgs(call.args)
|
||||
val scopedCall = FunctionCallStatement(scopedName, scopedArgs.toMutableList(), call.void, call.position)
|
||||
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
|
||||
}
|
||||
|
||||
private fun makeFullyScoped(call: BuiltinFunctionCall) {
|
||||
val sub = call.target.targetSubroutine(program)!!
|
||||
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
|
||||
val scopedArgs = makeScopedArgs(call.args)
|
||||
val scopedCall = BuiltinFunctionCall(scopedName, scopedArgs.toMutableList(), call.position)
|
||||
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
|
||||
}
|
||||
|
||||
private fun makeFullyScoped(call: FunctionCallExpression) {
|
||||
val scopedArgs = makeScopedArgs(call.args)
|
||||
val scopedCall = FunctionCallExpression(call.target.copy(), scopedArgs.toMutableList(), call.position)
|
||||
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
|
||||
}
|
||||
|
||||
private fun makeScopedArgs(args: List<Expression>): List<Expression> {
|
||||
return args.map {
|
||||
when (it) {
|
||||
is NumericLiteral -> it.copy()
|
||||
is IdentifierReference -> {
|
||||
val scoped = (it.targetStatement(program)!! as INamedStatement).scopedName
|
||||
IdentifierReference(scoped, it.position)
|
||||
}
|
||||
else -> throw InternalCompilerException("expected only number or identifier arg, otherwise too complex")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun before(program: Program): Iterable<IAstModification> {
|
||||
DetermineInlineSubs(program)
|
||||
return super.before(program)
|
||||
}
|
||||
|
||||
override fun after(gosub: GoSub, parent: Node): Iterable<IAstModification> {
|
||||
val sub = gosub.identifier.targetStatement(program) as? Subroutine
|
||||
if(sub!=null && sub.inline && sub.parameters.isEmpty()) {
|
||||
require(sub.statements.size == 1 || (sub.statements.size == 2 && isEmptyReturn(sub.statements[1])))
|
||||
return if(sub.isAsmSubroutine) {
|
||||
// simply insert the asm for the argument-less routine
|
||||
listOf(IAstModification.ReplaceNode(gosub, sub.statements.single().copy(), parent))
|
||||
} else {
|
||||
// note that we don't have to process any args, because we online inline parameterless subroutines.
|
||||
when (val toInline = sub.statements.first()) {
|
||||
is Return -> noModifications
|
||||
else -> listOf(IAstModification.ReplaceNode(gosub, toInline.copy(), parent))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
val sub = functionCallStatement.target.targetStatement(program) as? Subroutine
|
||||
if(sub!=null && sub.inline && sub.parameters.isEmpty()) {
|
||||
require(sub.statements.size==1 || (sub.statements.size==2 && isEmptyReturn(sub.statements[1])))
|
||||
return if(sub.isAsmSubroutine) {
|
||||
// simply insert the asm for the argument-less routine
|
||||
listOf(IAstModification.ReplaceNode(functionCallStatement, sub.statements.single().copy(), parent))
|
||||
} else {
|
||||
// note that we don't have to process any args, because we online inline parameterless subroutines.
|
||||
when (val toInline = sub.statements.first()) {
|
||||
is Return -> noModifications
|
||||
else -> listOf(IAstModification.ReplaceNode(functionCallStatement, toInline.copy(), parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
// TODO also inline function call expressions, and remove it from the StatementOptimizer
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.code.target.virtual.VirtualMachineDefinition
|
||||
import kotlin.math.floor
|
||||
|
||||
|
||||
@ -19,7 +18,7 @@ class StatementOptimizer(private val program: Program,
|
||||
|
||||
override fun before(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
|
||||
// if the first instruction in the called subroutine is a return statement with a simple value (NOT being a parameter),
|
||||
// remove the jump altogeter and inline the returnvalue directly.
|
||||
// remove the jump altogeter and inline the returnvalue directly. (only if not part of a pipe expression)
|
||||
|
||||
fun scopePrefix(variable: IdentifierReference): IdentifierReference {
|
||||
val target = variable.targetStatement(program) as INamedStatement
|
||||
@ -29,7 +28,7 @@ class StatementOptimizer(private val program: Program,
|
||||
val subroutine = functionCallExpr.target.targetSubroutine(program)
|
||||
if(subroutine!=null) {
|
||||
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||
if(first is Return && first.value?.isSimple==true) {
|
||||
if(first is Return && first.value?.isSimple==true && parent !is IPipe) {
|
||||
val copy = when(val orig = first.value!!) {
|
||||
is AddressOf -> {
|
||||
val scoped = scopePrefix(orig.identifier)
|
||||
|
@ -34,7 +34,7 @@ dependencies {
|
||||
implementation project(':codeGenCpu6502')
|
||||
implementation project(':codeGenVirtual')
|
||||
implementation project(':codeGenExperimental')
|
||||
implementation 'org.antlr:antlr4-runtime:4.9.3'
|
||||
implementation 'org.antlr:antlr4-runtime:4.10.1'
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.4'
|
||||
|
@ -67,6 +67,17 @@ sys {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub internal_stringcopy(uword source @R0, uword target @AY) clobbers (A,Y) {
|
||||
; Called when the compiler wants to assign a string value to another string.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jmp prog8_lib.strcpy
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
|
||||
; note: only works for NON-OVERLAPPING memory regions!
|
||||
; note: can't be inlined because is called from asm as well
|
||||
|
@ -116,8 +116,8 @@ cast_FAC1_as_w_into_ay .proc ; also used for float 2 b
|
||||
; -- cast fac1 to word into A/Y
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr AYINT
|
||||
ldy $66
|
||||
lda $67
|
||||
ldy floats.AYINT_facmo
|
||||
lda floats.AYINT_facmo+1
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
@ -168,9 +168,9 @@ stack_float2w .proc ; also used for float2b
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr AYINT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
lda $66
|
||||
lda floats.AYINT_facmo
|
||||
sta P8ESTACK_HI,x
|
||||
lda $67
|
||||
lda floats.AYINT_facmo+1
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
|
@ -3,6 +3,7 @@
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
|
||||
%option enable_floats
|
||||
%import floats_functions
|
||||
|
||||
floats {
|
||||
; ---- this block contains C-128 compatible floating point related functions ----
|
||||
@ -110,7 +111,6 @@ asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1
|
||||
%asm {{
|
||||
@ -151,43 +151,7 @@ asmsub FREADUY (ubyte value @Y) {
|
||||
}}
|
||||
}
|
||||
|
||||
sub print_f (float value) {
|
||||
; ---- prints the floating point value (without a newline).
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr MOVFM ; load float into fac1
|
||||
jsr FOUT ; fac1 to string in A/Y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
beq +
|
||||
jsr c64.CHROUT
|
||||
iny
|
||||
bne -
|
||||
+ ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub pow(float value, float power) -> float {
|
||||
%asm {{
|
||||
phx
|
||||
phy
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr floats.CONUPK
|
||||
lda #<power
|
||||
ldy #>power
|
||||
jsr floats.FPWR
|
||||
ply
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
&uword AYINT_facmo = $66 ; $66/$67 contain result of AYINT
|
||||
|
||||
%asminclude "library:c128/floats.asm"
|
||||
%asminclude "library:c64/floats_funcs.asm"
|
||||
|
@ -274,6 +274,16 @@ asmsub disable_runstop_and_charsetswitch() clobbers(A) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub enable_runstop_and_charsetswitch() clobbers(A) {
|
||||
%asm {{
|
||||
lda #0
|
||||
sta 247 ; enable charset switching
|
||||
lda #110
|
||||
sta 808 ; enable run/stop key
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_irq(uword handler @AY, ubyte useKernal @Pc) clobbers(A) {
|
||||
%asm {{
|
||||
sta _modified+1
|
||||
@ -481,6 +491,13 @@ asmsub init_system_phase2() {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub cleanup_at_exit() {
|
||||
; executed when the main subroutine does rts
|
||||
%asm {{
|
||||
jmp c64.enable_runstop_and_charsetswitch
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub disable_basic() clobbers(A) {
|
||||
%asm {{
|
||||
lda $0a04 ; disable BASIC shadow registers
|
||||
@ -554,6 +571,17 @@ sys {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub internal_stringcopy(uword source @R0, uword target @AY) clobbers (A,Y) {
|
||||
; Called when the compiler wants to assign a string value to another string.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jmp prog8_lib.strcpy
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
|
||||
; note: only works for NON-OVERLAPPING memory regions!
|
||||
; note: can't be inlined because is called from asm as well
|
||||
@ -670,7 +698,10 @@ _longcopy
|
||||
inline asmsub exit(ubyte returnvalue @A) {
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
%asm {{
|
||||
;lda #14
|
||||
;sta $01 ; bank the kernal in TODO c128 how to do this?
|
||||
jsr c64.CLRCHN ; reset i/o channels
|
||||
jsr c64.enable_runstop_and_charsetswitch
|
||||
ldx prog8_lib.orig_stackpointer
|
||||
txs
|
||||
rts ; return to original caller
|
||||
|
@ -113,8 +113,8 @@ cast_FAC1_as_w_into_ay .proc ; also used for float 2 b
|
||||
; -- cast fac1 to word into A/Y
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr AYINT
|
||||
ldy $64
|
||||
lda $65
|
||||
ldy floats.AYINT_facmo
|
||||
lda floats.AYINT_facmo+1
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
@ -165,9 +165,9 @@ stack_float2w .proc ; also used for float2b
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr AYINT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
lda $64
|
||||
lda floats.AYINT_facmo
|
||||
sta P8ESTACK_HI,x
|
||||
lda $65
|
||||
lda floats.AYINT_facmo+1
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
|
@ -5,6 +5,7 @@
|
||||
; indent format: TABS, size=8
|
||||
|
||||
%option enable_floats
|
||||
%import floats_functions
|
||||
|
||||
floats {
|
||||
; ---- this block contains C-64 floating point related functions ----
|
||||
@ -91,7 +92,6 @@ romsub $e2b4 = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
|
||||
romsub $e30e = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
|
||||
|
||||
|
||||
|
||||
asmsub FREADS32() clobbers(A,X,Y) {
|
||||
; ---- fac1 = signed int32 from $62-$65 big endian (MSB FIRST)
|
||||
%asm {{
|
||||
@ -174,42 +174,7 @@ asmsub GETADRAY () clobbers(X) -> uword @ AY {
|
||||
}}
|
||||
}
|
||||
|
||||
sub print_f (float value) {
|
||||
; ---- prints the floating point value (without a newline).
|
||||
%asm {{
|
||||
stx floats_store_reg
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr MOVFM ; load float into fac1
|
||||
jsr FOUT ; fac1 to string in A/Y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
beq +
|
||||
jsr c64.CHROUT
|
||||
iny
|
||||
bne -
|
||||
+ ldx floats_store_reg
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub pow(float value, float power) -> float {
|
||||
%asm {{
|
||||
phx
|
||||
phy
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr floats.CONUPK
|
||||
lda #<power
|
||||
ldy #>power
|
||||
jsr floats.FPWR
|
||||
ply
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
&uword AYINT_facmo = $64 ; $64/$65 contain result of AYINT
|
||||
|
||||
%asminclude "library:c64/floats.asm"
|
||||
%asminclude "library:c64/floats_funcs.asm"
|
||||
|
@ -1,188 +1,6 @@
|
||||
; --- floating point builtin functions
|
||||
|
||||
|
||||
abs_f_stack .proc
|
||||
; -- push abs(AY) on stack
|
||||
jsr floats.MOVFM
|
||||
jsr floats.ABS
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
abs_f_fac1 .proc
|
||||
; -- FAC1 = abs(AY)
|
||||
jsr floats.MOVFM
|
||||
jmp floats.ABS
|
||||
.pend
|
||||
|
||||
func_atan_stack .proc
|
||||
jsr func_atan_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_atan_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr ATN
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_ceil_stack .proc
|
||||
jsr func_ceil_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_ceil_fac1 .proc
|
||||
; -- ceil: tr = int(f); if tr==f -> return else return tr+1
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
ldx #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr MOVMF
|
||||
jsr INT
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr FCOMP
|
||||
cmp #0
|
||||
beq +
|
||||
lda #<FL_ONE_const
|
||||
ldy #>FL_ONE_const
|
||||
jsr FADD
|
||||
+ ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_floor_stack .proc
|
||||
jsr func_floor_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_floor_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr INT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_round_stack .proc
|
||||
jsr func_round_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_round_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr FADDH
|
||||
jsr INT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sin_stack .proc
|
||||
jsr func_sin_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_sin_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr SIN
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cos_stack .proc
|
||||
jsr func_cos_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_cos_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr COS
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_tan_stack .proc
|
||||
jsr func_tan_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_tan_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr TAN
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_rad_stack .proc
|
||||
jsr func_rad_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_rad_fac1 .proc
|
||||
; -- convert degrees to radians (d * pi / 180)
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #<_pi_div_180
|
||||
ldy #>_pi_div_180
|
||||
jsr FMULT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
_pi_div_180 .byte 123, 14, 250, 53, 18 ; pi / 180
|
||||
.pend
|
||||
|
||||
func_deg_stack .proc
|
||||
jsr func_deg_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_deg_fac1 .proc
|
||||
; -- convert radians to degrees (d * (1/ pi * 180))
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #<_one_over_pi_div_180
|
||||
ldy #>_one_over_pi_div_180
|
||||
jsr FMULT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
_one_over_pi_div_180 .byte 134, 101, 46, 224, 211 ; 1 / (pi * 180)
|
||||
.pend
|
||||
|
||||
func_ln_stack .proc
|
||||
jsr func_ln_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_ln_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr LOG
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_log2_stack .proc
|
||||
jsr func_log2_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_log2_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr LOG
|
||||
jsr MOVEF
|
||||
lda #<FL_LOG2_const
|
||||
ldy #>FL_LOG2_const
|
||||
jsr MOVFM
|
||||
jsr FDIVT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sign_f_stack .proc
|
||||
jsr func_sign_f_into_A
|
||||
sta P8ESTACK_LO,x
|
||||
@ -195,33 +13,6 @@ func_sign_f_into_A .proc
|
||||
jmp SIGN
|
||||
.pend
|
||||
|
||||
func_sqrt_stack .proc
|
||||
jsr func_sqrt_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_sqrt_fac1 .proc
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr SQR
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_rndf_stack .proc
|
||||
jsr func_rndf_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_rndf_fac1 .proc
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #1
|
||||
jsr FREADSA
|
||||
jsr RND ; rng into fac1
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_swap_f .proc
|
||||
; -- swap floats pointed to by SCRATCH_ZPWORD1, SCRATCH_ZPWORD2
|
||||
ldy #4
|
||||
@ -353,85 +144,3 @@ func_all_f_stack .proc
|
||||
jsr a_times_5
|
||||
jmp prog8_lib.func_all_b_stack
|
||||
.pend
|
||||
|
||||
func_max_f_stack .proc
|
||||
jsr func_max_f_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_max_f_fac1 .proc
|
||||
; -- max(array) -> fac1, array in P8ZP_SCRATCH_W1, num elts in A
|
||||
_loop_count = P8ZP_SCRATCH_REG
|
||||
stx floats_store_reg
|
||||
sta _loop_count
|
||||
lda #255
|
||||
sta _minmax_cmp+1 ; modifying
|
||||
lda #<_largest_neg_float
|
||||
ldy #>_largest_neg_float
|
||||
_minmax_entry jsr MOVFM
|
||||
- lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jsr FCOMP
|
||||
_minmax_cmp cmp #255 ; modified
|
||||
bne +
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jsr MOVFM
|
||||
+ lda P8ZP_SCRATCH_W1
|
||||
clc
|
||||
adc #5
|
||||
sta P8ZP_SCRATCH_W1
|
||||
bcc +
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
+ dec _loop_count
|
||||
bne -
|
||||
ldx floats_store_reg
|
||||
rts
|
||||
_largest_neg_float .byte 255,255,255,255,255 ; largest negative float -1.7014118345e+38
|
||||
.pend
|
||||
|
||||
func_min_f_stack .proc
|
||||
jsr func_min_f_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_min_f_fac1 .proc
|
||||
; -- min(array) -> fac1, array in P8ZP_SCRATCH_W1, num elts in A
|
||||
sta func_max_f_fac1._loop_count
|
||||
lda #1
|
||||
sta func_max_f_fac1._minmax_cmp+1
|
||||
lda #<_largest_pos_float
|
||||
ldy #>_largest_pos_float
|
||||
jmp func_max_f_fac1._minmax_entry
|
||||
_largest_pos_float .byte 255,127,255,255,255 ; largest positive float
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
func_sum_f_stack .proc
|
||||
jsr func_sum_f_fac1
|
||||
jmp push_fac1
|
||||
.pend
|
||||
|
||||
func_sum_f_fac1 .proc
|
||||
; -- sum(array) -> fac1, array in P8ZP_SCRATCH_W1, num elts in A
|
||||
_loop_count = P8ZP_SCRATCH_REG
|
||||
stx floats_store_reg
|
||||
sta _loop_count
|
||||
lda #<FL_ZERO_const
|
||||
ldy #>FL_ZERO_const
|
||||
jsr MOVFM
|
||||
- lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jsr FADD
|
||||
lda P8ZP_SCRATCH_W1
|
||||
clc
|
||||
adc #5
|
||||
sta P8ZP_SCRATCH_W1
|
||||
bcc +
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
+ dec _loop_count
|
||||
bne -
|
||||
ldx floats_store_reg
|
||||
rts
|
||||
.pend
|
||||
|
@ -300,6 +300,13 @@ asmsub init_system_phase2() {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub cleanup_at_exit() {
|
||||
; executed when the main subroutine does rts
|
||||
%asm {{
|
||||
jmp c64.enable_runstop_and_charsetswitch
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub disable_runstop_and_charsetswitch() clobbers(A) {
|
||||
%asm {{
|
||||
lda #$80
|
||||
@ -310,6 +317,16 @@ asmsub disable_runstop_and_charsetswitch() clobbers(A) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub enable_runstop_and_charsetswitch() clobbers(A) {
|
||||
%asm {{
|
||||
lda #0
|
||||
sta 657 ; enable charset switching
|
||||
lda #237
|
||||
sta 808 ; enable run/stop key
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_irq(uword handler @AY, ubyte useKernal @Pc) clobbers(A) {
|
||||
%asm {{
|
||||
sta _modified+1
|
||||
@ -519,6 +536,17 @@ sys {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub internal_stringcopy(uword source @R0, uword target @AY) clobbers (A,Y) {
|
||||
; Called when the compiler wants to assign a string value to another string.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jmp prog8_lib.strcpy
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
|
||||
; note: only works for NON-OVERLAPPING memory regions!
|
||||
; note: can't be inlined because is called from asm as well
|
||||
@ -635,7 +663,10 @@ _longcopy
|
||||
inline asmsub exit(ubyte returnvalue @A) {
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
%asm {{
|
||||
lda #31
|
||||
sta $01 ; bank the kernal in
|
||||
jsr c64.CLRCHN ; reset i/o channels
|
||||
jsr c64.enable_runstop_and_charsetswitch
|
||||
ldx prog8_lib.orig_stackpointer
|
||||
txs
|
||||
rts ; return to original caller
|
||||
|
@ -267,7 +267,7 @@ inline asmsub str2ubyte(str string @AY) clobbers(Y) -> ubyte @A {
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub str2byte(str string @AY) clobbers(Y) -> ubyte @A {
|
||||
inline asmsub str2byte(str string @AY) clobbers(Y) -> byte @A {
|
||||
; -- returns in A the signed byte value of the string number argument in AY
|
||||
; the number may be preceded by a + or - sign but may NOT contain spaces
|
||||
; (any non-digit character will terminate the number string that is parsed)
|
||||
|
@ -5,6 +5,7 @@
|
||||
; indent format: TABS, size=8
|
||||
|
||||
%option enable_floats
|
||||
%import floats_functions
|
||||
|
||||
floats {
|
||||
; ---- this block contains C-64 compatible floating point related functions ----
|
||||
@ -110,7 +111,6 @@ asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1
|
||||
%asm {{
|
||||
@ -151,7 +151,6 @@ asmsub FREADUY (ubyte value @Y) {
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub RND() clobbers(A,X,Y) {
|
||||
%asm {{
|
||||
lda #0
|
||||
@ -162,43 +161,7 @@ asmsub RND() clobbers(A,X,Y) {
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
sub print_f (float value) {
|
||||
; ---- prints the floating point value (without a newline).
|
||||
%asm {{
|
||||
phx
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr MOVFM ; load float into fac1
|
||||
jsr FOUT ; fac1 to string in A/Y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
beq +
|
||||
jsr c64.CHROUT
|
||||
iny
|
||||
bne -
|
||||
+ plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub pow(float value, float power) -> float {
|
||||
%asm {{
|
||||
phx
|
||||
phy
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr floats.CONUPK
|
||||
lda #<power
|
||||
ldy #>power
|
||||
jsr floats.FPWR
|
||||
ply
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
&uword AYINT_facmo = $c6 ; $c6/$c7 contain result of AYINT
|
||||
|
||||
%asminclude "library:c64/floats.asm"
|
||||
%asminclude "library:c64/floats_funcs.asm"
|
||||
|
@ -15,14 +15,12 @@
|
||||
; mode 1 = bitmap 320 x 240 monochrome
|
||||
; mode 2 = bitmap 320 x 240 x 4c (TODO not yet implemented)
|
||||
; mode 3 = bitmap 320 x 240 x 16c (TODO not yet implemented)
|
||||
; mode 4 = bitmap 320 x 240 x 256c
|
||||
; mode 4 = bitmap 320 x 240 x 256c (like SCREEN $80 but using this api instead of kernal)
|
||||
; mode 5 = bitmap 640 x 480 monochrome
|
||||
; mode 6 = bitmap 640 x 480 x 4c
|
||||
; higher color dephts in highres are not supported due to lack of VRAM
|
||||
|
||||
|
||||
; TODO can we make a FB vector table and emulation routines for the Cx16s' GRAPH_init() call? to replace the builtin 320x200 fb driver?
|
||||
|
||||
gfx2 {
|
||||
|
||||
; read-only control variables:
|
||||
@ -133,24 +131,24 @@ gfx2 {
|
||||
monochrome_dont_stipple_flag = not enable
|
||||
}
|
||||
|
||||
sub rect(uword x, uword y, uword width, uword height, ubyte color) {
|
||||
if width==0 or height==0
|
||||
sub rect(uword x, uword y, uword rwidth, uword rheight, ubyte color) {
|
||||
if rwidth==0 or rheight==0
|
||||
return
|
||||
horizontal_line(x, y, width, color)
|
||||
if height==1
|
||||
horizontal_line(x, y, rwidth, color)
|
||||
if rheight==1
|
||||
return
|
||||
horizontal_line(x, y+height-1, width, color)
|
||||
vertical_line(x, y+1, height-2, color)
|
||||
if width==1
|
||||
horizontal_line(x, y+rheight-1, rwidth, color)
|
||||
vertical_line(x, y+1, rheight-2, color)
|
||||
if rwidth==1
|
||||
return
|
||||
vertical_line(x+width-1, y+1, height-2, color)
|
||||
vertical_line(x+rwidth-1, y+1, rheight-2, color)
|
||||
}
|
||||
|
||||
sub fillrect(uword x, uword y, uword width, uword height, ubyte color) {
|
||||
if width==0
|
||||
sub fillrect(uword x, uword y, uword rwidth, uword rheight, ubyte color) {
|
||||
if rwidth==0
|
||||
return
|
||||
repeat height {
|
||||
horizontal_line(x, y, width, color)
|
||||
repeat rheight {
|
||||
horizontal_line(x, y, rwidth, color)
|
||||
y++
|
||||
}
|
||||
}
|
||||
@ -290,7 +288,7 @@ _done
|
||||
}
|
||||
}
|
||||
|
||||
sub vertical_line(uword x, uword y, uword height, ubyte color) {
|
||||
sub vertical_line(uword x, uword y, uword lheight, ubyte color) {
|
||||
when active_mode {
|
||||
1, 5 -> {
|
||||
; monochrome, lo-res
|
||||
@ -303,7 +301,7 @@ _done
|
||||
set_both_strides(11) ; 40 increment = 1 line in 320 px monochrome
|
||||
else
|
||||
set_both_strides(12) ; 80 increment = 1 line in 640 px monochrome
|
||||
repeat height {
|
||||
repeat lheight {
|
||||
%asm {{
|
||||
lda cx16.VERA_DATA0
|
||||
ora cx16.r15L
|
||||
@ -314,14 +312,14 @@ _done
|
||||
; draw stippled line.
|
||||
if x&1 {
|
||||
y++
|
||||
height--
|
||||
lheight--
|
||||
}
|
||||
position2(x,y,true)
|
||||
if active_mode==1
|
||||
set_both_strides(12) ; 80 increment = 2 line in 320 px monochrome
|
||||
else
|
||||
set_both_strides(13) ; 160 increment = 2 line in 640 px monochrome
|
||||
repeat height/2 {
|
||||
repeat lheight/2 {
|
||||
%asm {{
|
||||
lda cx16.VERA_DATA0
|
||||
ora cx16.r15L
|
||||
@ -336,7 +334,7 @@ _done
|
||||
set_both_strides(11) ; 40 increment = 1 line in 320 px monochrome
|
||||
else
|
||||
set_both_strides(12) ; 80 increment = 1 line in 640 px monochrome
|
||||
repeat height {
|
||||
repeat lheight {
|
||||
%asm {{
|
||||
lda cx16.VERA_DATA0
|
||||
and cx16.r15L
|
||||
@ -351,7 +349,7 @@ _done
|
||||
position(x,y)
|
||||
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | (14<<4)
|
||||
%asm {{
|
||||
ldy height
|
||||
ldy lheight
|
||||
beq +
|
||||
lda color
|
||||
- sta cx16.VERA_DATA0
|
||||
@ -363,14 +361,14 @@ _done
|
||||
6 -> {
|
||||
; highres 4c
|
||||
; use TWO vera adress pointers simultaneously one for reading, one for writing, so auto-increment is possible
|
||||
if height==0
|
||||
if lheight==0
|
||||
return
|
||||
position2(x,y,true)
|
||||
set_both_strides(13) ; 160 increment = 1 line in 640 px 4c mode
|
||||
color &= 3
|
||||
color <<= gfx2.plot.shift4c[lsb(x) & 3]
|
||||
ubyte @shared mask = gfx2.plot.mask4c[lsb(x) & 3]
|
||||
repeat height {
|
||||
repeat lheight {
|
||||
%asm {{
|
||||
lda cx16.VERA_DATA0
|
||||
and mask
|
||||
|
@ -641,6 +641,14 @@ asmsub init_system_phase2() {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub cleanup_at_exit() {
|
||||
; executed when the main subroutine does rts
|
||||
; just an rts here, nothing special to clean up on cx16
|
||||
%asm {{
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_irq(uword handler @AY, ubyte useKernal @Pc) clobbers(A) {
|
||||
%asm {{
|
||||
sta _modified+1
|
||||
@ -840,6 +848,17 @@ sys {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub internal_stringcopy(uword source @R0, uword target @AY) clobbers (A,Y) {
|
||||
; Called when the compiler wants to assign a string value to another string.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jmp prog8_lib.strcpy
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
|
||||
; note: only works for NON-OVERLAPPING memory regions!
|
||||
; If you have to copy overlapping memory regions, consider using
|
||||
|
@ -195,7 +195,7 @@ sub iso_off() {
|
||||
|
||||
|
||||
asmsub scroll_left() clobbers(A, Y) {
|
||||
; ---- scroll the whole screen 1 character to the left TODO optimize this more?
|
||||
; ---- scroll the whole screen 1 character to the left
|
||||
; contents of the rightmost column are unchanged, you should clear/refill this yourself
|
||||
%asm {{
|
||||
phx
|
||||
@ -241,7 +241,7 @@ _lx ldx #0 ; modified
|
||||
}
|
||||
|
||||
asmsub scroll_right() clobbers(A) {
|
||||
; ---- scroll the whole screen 1 character to the right TODO optimize this more?
|
||||
; ---- scroll the whole screen 1 character to the right
|
||||
; contents of the leftmost column are unchanged, you should clear/refill this yourself
|
||||
%asm {{
|
||||
phx
|
||||
@ -295,7 +295,7 @@ _lx ldx #0 ; modified
|
||||
}
|
||||
|
||||
asmsub scroll_up() clobbers(A, Y) {
|
||||
; ---- scroll the whole screen 1 character up TODO optimize this more?
|
||||
; ---- scroll the whole screen 1 character up
|
||||
; contents of the bottom row are unchanged, you should refill/clear this yourself
|
||||
%asm {{
|
||||
phx
|
||||
@ -345,7 +345,7 @@ _nextline
|
||||
}
|
||||
|
||||
asmsub scroll_down() clobbers(A, Y) {
|
||||
; ---- scroll the whole screen 1 character down TODO optimize this more?
|
||||
; ---- scroll the whole screen 1 character down
|
||||
; contents of the top row are unchanged, you should refill/clear this yourself
|
||||
%asm {{
|
||||
phx
|
||||
@ -635,7 +635,7 @@ asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A) {
|
||||
asl a
|
||||
sta cx16.VERA_ADDR_L
|
||||
tya
|
||||
clc
|
||||
; clc
|
||||
adc #>VERA_TEXTMATRIX_ADDR
|
||||
sta cx16.VERA_ADDR_M
|
||||
pla
|
||||
@ -655,7 +655,7 @@ asmsub getchr (ubyte col @A, ubyte row @Y) -> ubyte @ A {
|
||||
pla
|
||||
sta cx16.VERA_ADDR_L
|
||||
tya
|
||||
clc
|
||||
; clc
|
||||
adc #>VERA_TEXTMATRIX_ADDR
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda cx16.VERA_DATA0
|
||||
@ -677,7 +677,7 @@ asmsub setclr (ubyte col @X, ubyte row @Y, ubyte color @A) clobbers(A) {
|
||||
ina
|
||||
sta cx16.VERA_ADDR_L
|
||||
tya
|
||||
clc
|
||||
; clc
|
||||
adc #>VERA_TEXTMATRIX_ADDR
|
||||
sta cx16.VERA_ADDR_M
|
||||
pla
|
||||
@ -698,7 +698,7 @@ asmsub getclr (ubyte col @A, ubyte row @Y) -> ubyte @ A {
|
||||
pla
|
||||
sta cx16.VERA_ADDR_L
|
||||
tya
|
||||
clc
|
||||
; clc
|
||||
adc #>VERA_TEXTMATRIX_ADDR
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda cx16.VERA_DATA0
|
||||
@ -707,36 +707,29 @@ asmsub getclr (ubyte col @A, ubyte row @Y) -> ubyte @ A {
|
||||
}
|
||||
|
||||
sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
|
||||
; ---- set char+color at the given position on the screen TODO optimize this better
|
||||
; note: color handling is the same as on the C64: it only sets the foreground color.
|
||||
; use setcc2 if you want Cx-16 specific feature of setting both Bg+Fg colors.
|
||||
; ---- set char+color at the given position on the screen
|
||||
; note: color handling is the same as on the C64: it only sets the foreground color and leaves the background color as is.
|
||||
; Use setcc2 if you want Cx-16 specific feature of setting both Bg+Fg colors (is faster as well).
|
||||
%asm {{
|
||||
phx
|
||||
lda column
|
||||
asl a
|
||||
tax
|
||||
ldy row
|
||||
lda charcolor
|
||||
and #$0f
|
||||
sta P8ZP_SCRATCH_B1
|
||||
stz cx16.VERA_CTRL
|
||||
lda #VERA_TEXTMATRIX_BANK
|
||||
sta cx16.VERA_ADDR_H
|
||||
stx cx16.VERA_ADDR_L
|
||||
tya
|
||||
clc
|
||||
;clc
|
||||
adc #>VERA_TEXTMATRIX_ADDR
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda char
|
||||
sta cx16.VERA_DATA0
|
||||
inx
|
||||
lda #VERA_TEXTMATRIX_BANK
|
||||
sta cx16.VERA_ADDR_H
|
||||
stx cx16.VERA_ADDR_L
|
||||
tya
|
||||
clc
|
||||
adc #>VERA_TEXTMATRIX_ADDR
|
||||
sta cx16.VERA_ADDR_M
|
||||
inc cx16.VERA_ADDR_L
|
||||
lda charcolor
|
||||
and #$0f
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda cx16.VERA_DATA0
|
||||
and #$f0
|
||||
ora P8ZP_SCRATCH_B1
|
||||
@ -747,9 +740,9 @@ sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
|
||||
}
|
||||
|
||||
sub setcc2 (ubyte column, ubyte row, ubyte char, ubyte colors) {
|
||||
; ---- set char+color at the given position on the screen TODO optimize this
|
||||
; ---- set char+color at the given position on the screen
|
||||
; note: on the CommanderX16 this allows you to set both Fg and Bg colors;
|
||||
; use the high nybble in A to set the Bg color!
|
||||
; use the high nybble in A to set the Bg color! Is a bit faster than setcc() too.
|
||||
%asm {{
|
||||
phx
|
||||
lda column
|
||||
@ -761,19 +754,12 @@ sub setcc2 (ubyte column, ubyte row, ubyte char, ubyte colors) {
|
||||
sta cx16.VERA_ADDR_H
|
||||
stx cx16.VERA_ADDR_L
|
||||
tya
|
||||
clc
|
||||
; clc
|
||||
adc #>VERA_TEXTMATRIX_ADDR
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda char
|
||||
sta cx16.VERA_DATA0
|
||||
inx
|
||||
lda #VERA_TEXTMATRIX_BANK
|
||||
sta cx16.VERA_ADDR_H
|
||||
stx cx16.VERA_ADDR_L
|
||||
tya
|
||||
clc
|
||||
adc #>VERA_TEXTMATRIX_ADDR
|
||||
sta cx16.VERA_ADDR_M
|
||||
inc cx16.VERA_ADDR_L
|
||||
lda colors
|
||||
sta cx16.VERA_DATA0
|
||||
plx
|
||||
|
235
compiler/res/prog8lib/floats_functions.p8
Normal file
235
compiler/res/prog8lib/floats_functions.p8
Normal file
@ -0,0 +1,235 @@
|
||||
floats {
|
||||
; the floating point functions shared across compiler targets
|
||||
%option merge
|
||||
|
||||
sub print_f(float value) {
|
||||
; ---- prints the floating point value (without a newline).
|
||||
%asm {{
|
||||
stx floats_store_reg
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr MOVFM ; load float into fac1
|
||||
jsr FOUT ; fac1 to string in A/Y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
beq +
|
||||
jsr c64.CHROUT
|
||||
iny
|
||||
bne -
|
||||
+ ldx floats_store_reg
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub pow(float value, float power) -> float {
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr floats.CONUPK
|
||||
lda #<power
|
||||
ldy #>power
|
||||
jsr floats.FPWR
|
||||
ldx P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub fabs(float value) -> float {
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr MOVFM
|
||||
jsr ABS
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub sin(float angle) -> float {
|
||||
%asm {{
|
||||
lda #<angle
|
||||
ldy #>angle
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr SIN
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub cos(float angle) -> float {
|
||||
%asm {{
|
||||
lda #<angle
|
||||
ldy #>angle
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr COS
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub tan(float value) -> float {
|
||||
%asm {{
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr TAN
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub atan(float value) -> float {
|
||||
%asm {{
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr ATN
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub ln(float value) -> float {
|
||||
%asm {{
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr LOG
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub log2(float value) -> float {
|
||||
%asm {{
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr LOG
|
||||
jsr MOVEF
|
||||
lda #<FL_LOG2_const
|
||||
ldy #>FL_LOG2_const
|
||||
jsr MOVFM
|
||||
jsr FDIVT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub sqrt(float value) -> float {
|
||||
%asm {{
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr SQR
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub rad(float angle) -> float {
|
||||
; -- convert degrees to radians (d * pi / 180)
|
||||
%asm {{
|
||||
lda #<angle
|
||||
ldy #>angle
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #<_pi_div_180
|
||||
ldy #>_pi_div_180
|
||||
jsr FMULT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
_pi_div_180 .byte 123, 14, 250, 53, 18 ; pi / 180
|
||||
}}
|
||||
}
|
||||
|
||||
sub deg(float angle) -> float {
|
||||
; -- convert radians to degrees (d * (1/ pi * 180))
|
||||
%asm {{
|
||||
lda #<angle
|
||||
ldy #>angle
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #<_one_over_pi_div_180
|
||||
ldy #>_one_over_pi_div_180
|
||||
jsr FMULT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
_one_over_pi_div_180 .byte 134, 101, 46, 224, 211 ; 1 / (pi * 180)
|
||||
}}
|
||||
}
|
||||
|
||||
sub round(float value) -> float {
|
||||
%asm {{
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr FADDH
|
||||
jsr INT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub floor(float value) -> float {
|
||||
%asm {{
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr INT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub ceil(float value) -> float {
|
||||
; -- ceil: tr = int(f); if tr==f -> return else return tr+1
|
||||
%asm {{
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr MOVFM
|
||||
stx P8ZP_SCRATCH_REG
|
||||
ldx #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr MOVMF
|
||||
jsr INT
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr FCOMP
|
||||
cmp #0
|
||||
beq +
|
||||
lda #<FL_ONE_const
|
||||
ldy #>FL_ONE_const
|
||||
jsr FADD
|
||||
+ ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub rndf() -> float {
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #1
|
||||
jsr FREADSA
|
||||
jsr RND ; rng into fac1
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
@ -4,4 +4,180 @@
|
||||
|
||||
math {
|
||||
%asminclude "library:math.asm"
|
||||
|
||||
asmsub sin8u(ubyte angle @A) clobbers(Y) -> ubyte @A {
|
||||
%asm {{
|
||||
tay
|
||||
lda _sinecos8u,y
|
||||
rts
|
||||
_sinecos8u .byte trunc(128.0 + 127.5 * sin(range(256+64) * rad(360.0/256.0)))
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub cos8u(ubyte angle @A) clobbers(Y) -> ubyte @A {
|
||||
%asm {{
|
||||
tay
|
||||
lda sin8u._sinecos8u+64,y
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub sin8(ubyte angle @A) clobbers(Y) -> byte @A {
|
||||
%asm {{
|
||||
tay
|
||||
lda _sinecos8,y
|
||||
rts
|
||||
_sinecos8 .char trunc(127.0 * sin(range(256+64) * rad(360.0/256.0)))
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub cos8(ubyte angle @A) clobbers(Y) -> byte @A {
|
||||
%asm {{
|
||||
tay
|
||||
lda sin8._sinecos8+64,y
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub sin16(ubyte angle @A) -> word @AY {
|
||||
%asm {{
|
||||
tay
|
||||
lda _sinecos8lo,y
|
||||
pha
|
||||
lda _sinecos8hi,y
|
||||
tay
|
||||
pla
|
||||
rts
|
||||
_ := trunc(32767.0 * sin(range(256+64) * rad(360.0/256.0)))
|
||||
_sinecos8lo .byte <_
|
||||
_sinecos8hi .byte >_
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub cos16(ubyte angle @A) -> word @AY {
|
||||
%asm {{
|
||||
tay
|
||||
lda sin16._sinecos8lo+64,y
|
||||
pha
|
||||
lda sin16._sinecos8hi+64,y
|
||||
tay
|
||||
pla
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub sin16u(ubyte angle @A) -> uword @AY {
|
||||
%asm {{
|
||||
tay
|
||||
lda _sinecos8ulo,y
|
||||
pha
|
||||
lda _sinecos8uhi,y
|
||||
tay
|
||||
pla
|
||||
rts
|
||||
_ := trunc(32768.0 + 32767.5 * sin(range(256+64) * rad(360.0/256.0)))
|
||||
_sinecos8ulo .byte <_
|
||||
_sinecos8uhi .byte >_
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub cos16u(ubyte angle @A) -> uword @AY {
|
||||
%asm {{
|
||||
tay
|
||||
lda sin16u._sinecos8ulo+64,y
|
||||
pha
|
||||
lda sin16u._sinecos8uhi+64,y
|
||||
tay
|
||||
pla
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub sinr8u(ubyte radians @A) clobbers(Y) -> ubyte @A {
|
||||
%asm {{
|
||||
tay
|
||||
lda _sinecosR8u,y
|
||||
rts
|
||||
_sinecosR8u .byte trunc(128.0 + 127.5 * sin(range(180+45) * rad(360.0/180.0)))
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub cosr8u(ubyte radians @A) clobbers(Y) -> ubyte @A {
|
||||
%asm {{
|
||||
tay
|
||||
lda sinr8u._sinecosR8u+45,y
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub sinr8(ubyte radians @A) clobbers(Y) -> byte @A {
|
||||
%asm {{
|
||||
tay
|
||||
lda _sinecosR8,y
|
||||
rts
|
||||
_sinecosR8 .char trunc(127.0 * sin(range(180+45) * rad(360.0/180.0)))
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub cosr8(ubyte radians @A) clobbers(Y) -> byte @A {
|
||||
%asm {{
|
||||
tay
|
||||
lda sinr8._sinecosR8+45,y
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub sinr16(ubyte radians @A) -> word @AY {
|
||||
%asm {{
|
||||
tay
|
||||
lda _sinecosR8lo,y
|
||||
pha
|
||||
lda _sinecosR8hi,y
|
||||
tay
|
||||
pla
|
||||
rts
|
||||
_ := trunc(32767.0 * sin(range(180+45) * rad(360.0/180.0)))
|
||||
_sinecosR8lo .byte <_
|
||||
_sinecosR8hi .byte >_
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub cosr16(ubyte radians @A) -> word @AY {
|
||||
%asm {{
|
||||
tay
|
||||
lda sinr16._sinecosR8lo+45,y
|
||||
pha
|
||||
lda sinr16._sinecosR8hi+45,y
|
||||
tay
|
||||
pla
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub sinr16u(ubyte radians @A) -> uword @AY {
|
||||
%asm {{
|
||||
tay
|
||||
lda _sinecosR8ulo,y
|
||||
pha
|
||||
lda _sinecosR8uhi,y
|
||||
tay
|
||||
pla
|
||||
rts
|
||||
_ := trunc(32768.0 + 32767.5 * sin(range(180+45) * rad(360.0/180.0)))
|
||||
_sinecosR8ulo .byte <_
|
||||
_sinecosR8uhi .byte >_
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub cosr16u(ubyte radians @A) -> uword @AY {
|
||||
%asm {{
|
||||
tay
|
||||
lda sinr16u._sinecosR8ulo+45,y
|
||||
pha
|
||||
lda sinr16u._sinecosR8uhi+45,y
|
||||
tay
|
||||
pla
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
@ -84,304 +84,18 @@ func_all_w_stack .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sin8_into_A .proc
|
||||
tay
|
||||
lda _sinecos8,y
|
||||
rts
|
||||
_sinecos8 .char trunc(127.0 * sin(range(256+64) * rad(360.0/256.0)))
|
||||
.pend
|
||||
|
||||
func_sinr8_into_A .proc
|
||||
tay
|
||||
lda _sinecosR8,y
|
||||
rts
|
||||
_sinecosR8 .char trunc(127.0 * sin(range(180+45) * rad(360.0/180.0)))
|
||||
.pend
|
||||
|
||||
func_sin8u_into_A .proc
|
||||
tay
|
||||
lda _sinecos8u,y
|
||||
rts
|
||||
_sinecos8u .byte trunc(128.0 + 127.5 * sin(range(256+64) * rad(360.0/256.0)))
|
||||
.pend
|
||||
|
||||
func_sinr8u_into_A .proc
|
||||
tay
|
||||
lda _sinecosR8u,y
|
||||
rts
|
||||
_sinecosR8u .byte trunc(128.0 + 127.5 * sin(range(180+45) * rad(360.0/180.0)))
|
||||
.pend
|
||||
|
||||
func_sin8_stack .proc
|
||||
tay
|
||||
lda func_sin8_into_A._sinecos8,y
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sinr8_stack .proc
|
||||
tay
|
||||
lda func_sinr8_into_A._sinecosR8,y
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sin8u_stack .proc
|
||||
tay
|
||||
lda func_sin8u_into_A._sinecos8u,y
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sinr8u_stack .proc
|
||||
tay
|
||||
lda func_sinr8u_into_A._sinecosR8u,y
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cos8_into_A .proc
|
||||
tay
|
||||
lda func_sin8_into_A._sinecos8+64,y
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cosr8_into_A .proc
|
||||
tay
|
||||
lda func_sinr8_into_A._sinecosR8+45,y
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cos8u_into_A .proc
|
||||
tay
|
||||
lda func_sin8u_into_A._sinecos8u+64,y
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cosr8u_into_A .proc
|
||||
tay
|
||||
lda func_sinr8u_into_A._sinecosR8u+45,y
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cos8_stack .proc
|
||||
tay
|
||||
lda func_sin8_into_A._sinecos8+64,y
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cosr8_stack .proc
|
||||
tay
|
||||
lda func_sinr8_into_A._sinecosR8+45,y
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cos8u_stack .proc
|
||||
tay
|
||||
lda func_sin8u_into_A._sinecos8u+64,y
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cosr8u_stack .proc
|
||||
tay
|
||||
lda func_sinr8u_into_A._sinecosR8u+45,y
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sin16_into_AY .proc
|
||||
tay
|
||||
lda _sinecos8lo,y
|
||||
pha
|
||||
lda _sinecos8hi,y
|
||||
tay
|
||||
pla
|
||||
rts
|
||||
_ := trunc(32767.0 * sin(range(256+64) * rad(360.0/256.0)))
|
||||
_sinecos8lo .byte <_
|
||||
_sinecos8hi .byte >_
|
||||
.pend
|
||||
|
||||
func_sinr16_into_AY .proc
|
||||
tay
|
||||
lda _sinecosR8lo,y
|
||||
pha
|
||||
lda _sinecosR8hi,y
|
||||
tay
|
||||
pla
|
||||
rts
|
||||
_ := trunc(32767.0 * sin(range(180+45) * rad(360.0/180.0)))
|
||||
_sinecosR8lo .byte <_
|
||||
_sinecosR8hi .byte >_
|
||||
.pend
|
||||
|
||||
func_sin16u_into_AY .proc
|
||||
tay
|
||||
lda _sinecos8ulo,y
|
||||
pha
|
||||
lda _sinecos8uhi,y
|
||||
tay
|
||||
pla
|
||||
rts
|
||||
_ := trunc(32768.0 + 32767.5 * sin(range(256+64) * rad(360.0/256.0)))
|
||||
_sinecos8ulo .byte <_
|
||||
_sinecos8uhi .byte >_
|
||||
.pend
|
||||
|
||||
func_sinr16u_into_AY .proc
|
||||
tay
|
||||
lda _sinecosR8ulo,y
|
||||
pha
|
||||
lda _sinecosR8uhi,y
|
||||
tay
|
||||
pla
|
||||
rts
|
||||
_ := trunc(32768.0 + 32767.5 * sin(range(180+45) * rad(360.0/180.0)))
|
||||
_sinecosR8ulo .byte <_
|
||||
_sinecosR8uhi .byte >_
|
||||
.pend
|
||||
|
||||
func_sin16_stack .proc
|
||||
tay
|
||||
lda func_sin16_into_AY._sinecos8lo,y
|
||||
sta P8ESTACK_LO,x
|
||||
lda func_sin16_into_AY._sinecos8hi,y
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sinr16_stack .proc
|
||||
tay
|
||||
lda func_sinr16_into_AY._sinecosR8lo,y
|
||||
sta P8ESTACK_LO,x
|
||||
lda func_sinr16_into_AY._sinecosR8hi,y
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sin16u_stack .proc
|
||||
tay
|
||||
lda func_sin16u_into_AY._sinecos8ulo,y
|
||||
sta P8ESTACK_LO,x
|
||||
lda func_sin16u_into_AY._sinecos8uhi,y
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sinr16u_stack .proc
|
||||
tay
|
||||
lda func_sinr16u_into_AY._sinecosR8ulo,y
|
||||
sta P8ESTACK_LO,x
|
||||
lda func_sinr16u_into_AY._sinecosR8uhi,y
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cos16_into_AY .proc
|
||||
tay
|
||||
lda func_sin16_into_AY._sinecos8lo+64,y
|
||||
pha
|
||||
lda func_sin16_into_AY._sinecos8hi+64,y
|
||||
tay
|
||||
pla
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cosr16_into_AY .proc
|
||||
tay
|
||||
lda func_sinr16_into_AY._sinecosR8lo+45,y
|
||||
pha
|
||||
lda func_sinr16_into_AY._sinecosR8hi+45,y
|
||||
tay
|
||||
pla
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cos16u_into_AY .proc
|
||||
tay
|
||||
lda func_sin16u_into_AY._sinecos8ulo+64,y
|
||||
pha
|
||||
lda func_sin16u_into_AY._sinecos8uhi+64,y
|
||||
tay
|
||||
pla
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cosr16u_into_AY .proc
|
||||
tay
|
||||
lda func_sinr16u_into_AY._sinecosR8ulo+45,y
|
||||
pha
|
||||
lda func_sinr16u_into_AY._sinecosR8uhi+45,y
|
||||
tay
|
||||
pla
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cos16_stack .proc
|
||||
tay
|
||||
lda func_sin16_into_AY._sinecos8lo+64,y
|
||||
sta P8ESTACK_LO,x
|
||||
lda func_sin16_into_AY._sinecos8hi+64,y
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cosr16_stack .proc
|
||||
tay
|
||||
lda func_sinr16_into_AY._sinecosR8lo+45,y
|
||||
sta P8ESTACK_LO,x
|
||||
lda func_sinr16_into_AY._sinecosR8hi+45,y
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cos16u_stack .proc
|
||||
tay
|
||||
lda func_sin16u_into_AY._sinecos8ulo+64,y
|
||||
sta P8ESTACK_LO,x
|
||||
lda func_sin16u_into_AY._sinecos8uhi+64,y
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cosr16u_stack .proc
|
||||
tay
|
||||
lda func_sinr16u_into_AY._sinecosR8ulo+45,y
|
||||
sta P8ESTACK_LO,x
|
||||
lda func_sinr16u_into_AY._sinecosR8uhi+45,y
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
abs_b_stack .proc
|
||||
; -- push abs(A) on stack (as byte)
|
||||
; -- push abs(A) on stack (as unsigned word)
|
||||
jsr abs_b_into_A
|
||||
sta P8ESTACK_LO,x
|
||||
stz p8ESTACK_HI,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
abs_b_into_A .proc
|
||||
; -- A = abs(A)
|
||||
abs_b_into_AY .proc
|
||||
; -- AY = abs(A) (abs always returns unsigned word)
|
||||
ldy #0
|
||||
cmp #0
|
||||
bmi +
|
||||
rts
|
||||
@ -548,394 +262,6 @@ func_rndw_stack .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_min_ub_into_A .proc
|
||||
; -- min(ubarray) -> A. (array in P8ZP_SCRATCH_W1, num elements in A)
|
||||
tay
|
||||
dey
|
||||
lda #255
|
||||
sta P8ZP_SCRATCH_B1
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
cmp P8ZP_SCRATCH_B1
|
||||
bcs +
|
||||
sta P8ZP_SCRATCH_B1
|
||||
+ dey
|
||||
cpy #255
|
||||
bne -
|
||||
lda P8ZP_SCRATCH_B1
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_min_ub_stack .proc
|
||||
jsr func_min_ub_into_A
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_min_b_into_A .proc
|
||||
; -- min(barray) -> A. (array in P8ZP_SCRATCH_W1, num elements in A)
|
||||
tay
|
||||
dey
|
||||
lda #127
|
||||
sta P8ZP_SCRATCH_B1
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
clc
|
||||
sbc P8ZP_SCRATCH_B1
|
||||
bvc +
|
||||
eor #$80
|
||||
+ bpl +
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta P8ZP_SCRATCH_B1
|
||||
+ dey
|
||||
cpy #255
|
||||
bne -
|
||||
lda P8ZP_SCRATCH_B1
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_min_b_stack .proc
|
||||
jsr func_min_b_into_A
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_min_uw_into_AY .proc
|
||||
; -- min(uwarray) -> AY (array in P8ZP_SCRATCH_W1, num elements in A)
|
||||
asl a
|
||||
tay
|
||||
dey
|
||||
dey
|
||||
lda #$ff
|
||||
sta _result_minuw
|
||||
sta _result_minuw+1
|
||||
_loop
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
cmp _result_minuw+1
|
||||
bcc _less
|
||||
bne _gtequ
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
cmp _result_minuw
|
||||
bcs _gtequ
|
||||
_less lda (P8ZP_SCRATCH_W1),y
|
||||
sta _result_minuw
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta _result_minuw+1
|
||||
dey
|
||||
_gtequ dey
|
||||
dey
|
||||
cpy #254
|
||||
bne _loop
|
||||
lda _result_minuw
|
||||
ldy _result_minuw+1
|
||||
rts
|
||||
_result_minuw .word 0
|
||||
.pend
|
||||
|
||||
func_min_w_into_AY .proc
|
||||
; -- min(warray) -> AY (array in P8ZP_SCRATCH_W1, num elements in A)
|
||||
asl a
|
||||
tay
|
||||
dey
|
||||
dey
|
||||
lda #$ff
|
||||
sta _result_minw
|
||||
lda #$7f
|
||||
sta _result_minw+1
|
||||
_loop
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
cmp _result_minw
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
sbc _result_minw+1
|
||||
bvc +
|
||||
eor #$80
|
||||
+ bpl _gtequ
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta _result_minw
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta _result_minw+1
|
||||
dey
|
||||
_gtequ dey
|
||||
dey
|
||||
cpy #254
|
||||
bne _loop
|
||||
lda _result_minw
|
||||
ldy _result_minw+1
|
||||
rts
|
||||
_result_minw .word 0
|
||||
.pend
|
||||
|
||||
func_min_uw_stack .proc
|
||||
jsr func_min_uw_into_AY
|
||||
sta P8ESTACK_LO,x
|
||||
tya
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_min_w_stack .proc
|
||||
jsr func_min_w_into_AY
|
||||
sta P8ESTACK_LO,x
|
||||
tya
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_max_ub_into_A .proc
|
||||
; -- max(ubarray) -> A (array in P8ZP_SCRATCH_W1, num elements in A)
|
||||
tay
|
||||
dey
|
||||
lda #0
|
||||
sta P8ZP_SCRATCH_B1
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
cmp P8ZP_SCRATCH_B1
|
||||
bcc +
|
||||
sta P8ZP_SCRATCH_B1
|
||||
+ dey
|
||||
cpy #255
|
||||
bne -
|
||||
lda P8ZP_SCRATCH_B1
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_max_ub_stack .proc
|
||||
jsr func_max_ub_into_A
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_max_b_into_A .proc
|
||||
; -- max(barray) -> A (array in P8ZP_SCRATCH_W1, num elements in A)
|
||||
tay
|
||||
lda #-128
|
||||
sta P8ZP_SCRATCH_B1
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
sec
|
||||
sbc P8ZP_SCRATCH_B1
|
||||
bvc +
|
||||
eor #$80
|
||||
+ bmi +
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta P8ZP_SCRATCH_B1
|
||||
+ dey
|
||||
cpy #255
|
||||
bne -
|
||||
lda P8ZP_SCRATCH_B1
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_max_b_stack .proc
|
||||
jsr func_max_b_into_A
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_max_uw_into_AY .proc
|
||||
; -- max(uwarray) -> AY (array in P8ZP_SCRATCH_W1, num elements in A)
|
||||
asl a
|
||||
tay
|
||||
dey
|
||||
dey
|
||||
lda #0
|
||||
sta _result_maxuw
|
||||
sta _result_maxuw+1
|
||||
_loop
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
cmp _result_maxuw+1
|
||||
bcc _lesseq
|
||||
bne _greater
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
cmp _result_maxuw
|
||||
bcc _lesseq
|
||||
_greater lda (P8ZP_SCRATCH_W1),y
|
||||
sta _result_maxuw
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta _result_maxuw+1
|
||||
dey
|
||||
_lesseq dey
|
||||
dey
|
||||
cpy #254
|
||||
bne _loop
|
||||
lda _result_maxuw
|
||||
ldy _result_maxuw+1
|
||||
rts
|
||||
_result_maxuw .word 0
|
||||
.pend
|
||||
|
||||
func_max_uw_stack .proc
|
||||
jsr func_max_uw_into_AY
|
||||
sta P8ESTACK_LO,x
|
||||
tya
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_max_w_into_AY .proc
|
||||
; -- max(uwarray) -> AY (array in P8ZP_SCRATCH_W1, num elements in A)
|
||||
asl a
|
||||
tay
|
||||
dey
|
||||
dey
|
||||
lda #0
|
||||
sta _result_maxw
|
||||
lda #$80
|
||||
sta _result_maxw+1
|
||||
_loop
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
cmp _result_maxw
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
sbc _result_maxw+1
|
||||
bvc +
|
||||
eor #$80
|
||||
+ bmi _lesseq
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta _result_maxw
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta _result_maxw+1
|
||||
dey
|
||||
_lesseq dey
|
||||
dey
|
||||
cpy #254
|
||||
bne _loop
|
||||
lda _result_maxw
|
||||
ldy _result_maxw+1
|
||||
rts
|
||||
_result_maxw .word 0
|
||||
.pend
|
||||
|
||||
func_max_w_stack .proc
|
||||
jsr func_max_w_into_AY
|
||||
sta P8ESTACK_LO,x
|
||||
tya
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sum_ub_into_AY .proc
|
||||
; -- sum(ubarray) -> AY (array in P8ZP_SCRATCH_W1, num elements in A)
|
||||
tay
|
||||
dey
|
||||
lda #0
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
clc
|
||||
adc P8ZP_SCRATCH_W2
|
||||
sta P8ZP_SCRATCH_W2
|
||||
bcc +
|
||||
inc P8ZP_SCRATCH_W2+1
|
||||
+ dey
|
||||
cpy #255
|
||||
bne -
|
||||
lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sum_ub_stack .proc
|
||||
jsr func_sum_ub_into_AY
|
||||
sta P8ESTACK_LO,x
|
||||
tya
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
func_sum_b_into_AY .proc
|
||||
; -- sum(barray) -> AY (array in P8ZP_SCRATCH_W1, num elements in A)
|
||||
tay
|
||||
dey
|
||||
lda #0
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
_loop lda (P8ZP_SCRATCH_W1),y
|
||||
pha
|
||||
clc
|
||||
adc P8ZP_SCRATCH_W2
|
||||
sta P8ZP_SCRATCH_W2
|
||||
; sign extend the high byte
|
||||
pla
|
||||
and #$80
|
||||
beq +
|
||||
lda #$ff
|
||||
+ adc P8ZP_SCRATCH_W2+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
dey
|
||||
cpy #255
|
||||
bne _loop
|
||||
lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sum_b_stack .proc
|
||||
jsr func_sum_b_into_AY
|
||||
sta P8ESTACK_LO,x
|
||||
tya
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sum_uw_into_AY .proc
|
||||
; -- sum(uwarray) -> AY (array in P8ZP_SCRATCH_W1, num elements in A)
|
||||
asl a
|
||||
tay
|
||||
dey
|
||||
dey
|
||||
lda #0
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
iny
|
||||
clc
|
||||
adc P8ZP_SCRATCH_W2
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
adc P8ZP_SCRATCH_W2+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
dey
|
||||
dey
|
||||
dey
|
||||
cpy #254
|
||||
bne -
|
||||
lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sum_uw_stack .proc
|
||||
jsr func_sum_uw_into_AY
|
||||
sta P8ESTACK_LO,x
|
||||
tya
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
func_sum_w_into_AY = func_sum_uw_into_AY
|
||||
func_sum_w_stack = func_sum_uw_stack
|
||||
|
||||
|
||||
func_sort_ub .proc
|
||||
; 8bit unsigned sort
|
||||
|
257
compiler/res/prog8lib/virtual/conv.p8
Normal file
257
compiler/res/prog8lib/virtual/conv.p8
Normal file
@ -0,0 +1,257 @@
|
||||
; Number conversions routines.
|
||||
;
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
|
||||
|
||||
conv {
|
||||
|
||||
; ----- number conversions to decimal strings ----
|
||||
|
||||
str string_out = "????????????????" ; result buffer for the string conversion routines
|
||||
|
||||
sub str_ub0(ubyte value) {
|
||||
; ---- convert the ubyte in A in decimal string form, with left padding 0s (3 positions total)
|
||||
ubyte hundreds = value / 100
|
||||
value -= hundreds*100
|
||||
ubyte tens = value / 10
|
||||
value -= tens*10
|
||||
string_out[0] = hundreds+'0'
|
||||
string_out[1] = tens+'0'
|
||||
string_out[2] = value+'0'
|
||||
string_out[3] = 0
|
||||
}
|
||||
|
||||
sub str_ub(ubyte value) {
|
||||
; ---- convert the ubyte in A in decimal string form, without left padding 0s
|
||||
internal_str_ub(value, string_out)
|
||||
}
|
||||
|
||||
sub str_b(byte value) {
|
||||
; ---- convert the byte in A in decimal string form, without left padding 0s
|
||||
uword out_ptr = &string_out
|
||||
if value<0 {
|
||||
@(out_ptr) = '-'
|
||||
out_ptr++
|
||||
value = -value
|
||||
}
|
||||
internal_str_ub(value as ubyte, out_ptr)
|
||||
}
|
||||
|
||||
sub internal_str_ub(ubyte value, uword out_ptr) {
|
||||
ubyte hundreds = value / 100
|
||||
value -= hundreds*100
|
||||
ubyte tens = value / 10
|
||||
value -= tens*10
|
||||
if hundreds
|
||||
goto output_hundreds
|
||||
if tens
|
||||
goto output_tens
|
||||
goto output_ones
|
||||
output_hundreds:
|
||||
@(out_ptr) = hundreds+'0'
|
||||
out_ptr++
|
||||
output_tens:
|
||||
@(out_ptr) = tens+'0'
|
||||
out_ptr++
|
||||
output_ones:
|
||||
@(out_ptr) = value+'0'
|
||||
out_ptr++
|
||||
@(out_ptr) = 0
|
||||
}
|
||||
|
||||
str hex_digits = "0123456789abcdef"
|
||||
|
||||
sub str_ubhex (ubyte value) {
|
||||
; ---- convert the ubyte in A in hex string form
|
||||
string_out[0] = hex_digits[value>>4]
|
||||
string_out[1] = hex_digits[value&15]
|
||||
string_out[2] = 0
|
||||
}
|
||||
|
||||
sub str_ubbin (ubyte value) {
|
||||
; ---- convert the ubyte in A in binary string form
|
||||
uword out_ptr = &string_out
|
||||
repeat 8 {
|
||||
rol(value)
|
||||
if_cc
|
||||
@(out_ptr) = '0'
|
||||
else
|
||||
@(out_ptr) = '1'
|
||||
out_ptr++
|
||||
}
|
||||
@(out_ptr) = 0
|
||||
}
|
||||
|
||||
sub str_uwbin (uword value) {
|
||||
; ---- convert the uword in A/Y in binary string form
|
||||
uword out_ptr = &string_out
|
||||
repeat 16 {
|
||||
rol(value)
|
||||
if_cc
|
||||
@(out_ptr) = '0'
|
||||
else
|
||||
@(out_ptr) = '1'
|
||||
out_ptr++
|
||||
}
|
||||
@(out_ptr) = 0
|
||||
}
|
||||
|
||||
sub str_uwhex (uword value) {
|
||||
; ---- convert the uword in A/Y in hexadecimal string form (4 digits)
|
||||
ubyte bits = msb(value)
|
||||
string_out[0] = hex_digits[bits>>4]
|
||||
string_out[1] = hex_digits[bits&15]
|
||||
bits = lsb(value)
|
||||
string_out[2] = hex_digits[bits>>4]
|
||||
string_out[3] = hex_digits[bits&15]
|
||||
string_out[4] = 0
|
||||
}
|
||||
|
||||
sub str_uw0 (uword value) {
|
||||
; ---- convert the uword in A/Y in decimal string form, with left padding 0s (5 positions total)
|
||||
ubyte tenthousands = (value / 10000) as ubyte
|
||||
value -= 10000*tenthousands
|
||||
ubyte thousands = (value / 1000) as ubyte
|
||||
value -= 1000*thousands
|
||||
ubyte hundreds = (value / 100) as ubyte
|
||||
value -= 100 as uword * hundreds
|
||||
ubyte tens = (value / 10) as ubyte
|
||||
value -= 10*tens
|
||||
string_out[0] = tenthousands+'0'
|
||||
string_out[1] = thousands+'0'
|
||||
string_out[2] = hundreds+'0'
|
||||
string_out[3] = tens+'0'
|
||||
string_out[4] = value as ubyte + '0'
|
||||
string_out[5] = 0
|
||||
}
|
||||
|
||||
sub str_uw (uword value) {
|
||||
; ---- convert the uword in A/Y in decimal string form, without left padding 0s
|
||||
internal_str_uw(value, string_out)
|
||||
}
|
||||
|
||||
sub str_w (word value) {
|
||||
; ---- convert the (signed) word in A/Y in decimal string form, without left padding 0's
|
||||
uword out_ptr = &string_out
|
||||
if value<0 {
|
||||
@(out_ptr) = '-'
|
||||
out_ptr++
|
||||
value = -value
|
||||
}
|
||||
internal_str_uw(value as uword, out_ptr)
|
||||
}
|
||||
|
||||
sub internal_str_uw(uword value, uword out_ptr) {
|
||||
ubyte tenthousands = (value / 10000) as ubyte
|
||||
value -= 10000*tenthousands
|
||||
ubyte thousands = (value / 1000) as ubyte
|
||||
value -= 1000*thousands
|
||||
ubyte hundreds = (value / 100) as ubyte
|
||||
value -= 100 as uword * hundreds
|
||||
ubyte tens = (value / 10) as ubyte
|
||||
value -= 10*tens
|
||||
if tenthousands
|
||||
goto output_tenthousands
|
||||
if thousands
|
||||
goto output_thousands
|
||||
if hundreds
|
||||
goto output_hundreds
|
||||
if tens
|
||||
goto output_tens
|
||||
goto output_ones
|
||||
output_tenthousands:
|
||||
@(out_ptr) = tenthousands+'0'
|
||||
out_ptr++
|
||||
output_thousands:
|
||||
@(out_ptr) = thousands+'0'
|
||||
out_ptr++
|
||||
output_hundreds:
|
||||
@(out_ptr) = hundreds+'0'
|
||||
out_ptr++
|
||||
output_tens:
|
||||
@(out_ptr) = tens+'0'
|
||||
out_ptr++
|
||||
output_ones:
|
||||
@(out_ptr) = value as ubyte + '0'
|
||||
out_ptr++
|
||||
@(out_ptr) = 0
|
||||
}
|
||||
|
||||
|
||||
; ---- string conversion to numbers -----
|
||||
|
||||
sub str2ubyte(str string) -> ubyte {
|
||||
; -- returns in A the unsigned byte value of the string number argument in AY
|
||||
; the number may NOT be preceded by a + sign and may NOT contain spaces
|
||||
; (any non-digit character will terminate the number string that is parsed)
|
||||
return str2uword(string) as ubyte
|
||||
}
|
||||
|
||||
sub str2byte(str string) -> byte {
|
||||
; -- returns in A the signed byte value of the string number argument in AY
|
||||
; the number may be preceded by a + or - sign but may NOT contain spaces
|
||||
; (any non-digit character will terminate the number string that is parsed)
|
||||
return str2word(string) as byte
|
||||
}
|
||||
|
||||
sub str2uword(str string) -> uword {
|
||||
; -- returns the unsigned word value of the string number argument in AY
|
||||
; the number may NOT be preceded by a + sign and may NOT contain spaces
|
||||
; (any non-digit character will terminate the number string that is parsed)
|
||||
%asm {{
|
||||
loadm.w r0, {conv.str2uword.string}
|
||||
syscall 11
|
||||
return
|
||||
}}
|
||||
}
|
||||
|
||||
sub str2word(str string) -> word {
|
||||
; -- returns the signed word value of the string number argument in AY
|
||||
; the number may be preceded by a + or - sign but may NOT contain spaces
|
||||
; (any non-digit character will terminate the number string that is parsed)
|
||||
%asm {{
|
||||
loadm.w r0, {conv.str2word.string}
|
||||
syscall 12
|
||||
return
|
||||
}}
|
||||
}
|
||||
|
||||
sub hex2uword(str string) -> uword {
|
||||
; -- hexadecimal string (with or without '$') to uword.
|
||||
; stops parsing at the first character that's not a hex digit (except leading $)
|
||||
uword result
|
||||
ubyte char
|
||||
if @(string)=='$'
|
||||
string++
|
||||
repeat {
|
||||
char = @(string)
|
||||
if char==0
|
||||
return result
|
||||
result <<= 4
|
||||
if char>='0' and char<='9'
|
||||
result |= char-'0'
|
||||
else
|
||||
result |= char-'a'+10
|
||||
string++
|
||||
}
|
||||
}
|
||||
|
||||
sub bin2uword(str string) -> uword {
|
||||
; -- binary string (with or without '%') to uword.
|
||||
; stops parsing at the first character that's not a 0 or 1. (except leading %)
|
||||
uword result
|
||||
ubyte char
|
||||
if @(string)=='%'
|
||||
string++
|
||||
repeat {
|
||||
char = @(string)
|
||||
if char==0
|
||||
return result
|
||||
result <<= 1
|
||||
if char=='1'
|
||||
result |= 1
|
||||
string++
|
||||
}
|
||||
}
|
||||
|
||||
}
|
135
compiler/res/prog8lib/virtual/floats.p8
Normal file
135
compiler/res/prog8lib/virtual/floats.p8
Normal file
@ -0,0 +1,135 @@
|
||||
; Prog8 definitions for floating point handling on the VirtualMachine
|
||||
;
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
|
||||
%option enable_floats
|
||||
|
||||
floats {
|
||||
|
||||
const float PI = 3.141592653589793
|
||||
const float TWOPI = 6.283185307179586
|
||||
|
||||
sub print_f(float value) {
|
||||
; ---- prints the floating point value (without a newline).
|
||||
%asm {{
|
||||
loadm.f fr0,{floats.print_f.value}
|
||||
syscall 25
|
||||
return
|
||||
}}
|
||||
}
|
||||
|
||||
sub pow(float value, float power) -> float {
|
||||
%asm {{
|
||||
loadm.f fr0,{floats.pow.value}
|
||||
loadm.f fr1,{floats.pow.power}
|
||||
fpow.f fr0,fr1
|
||||
return
|
||||
}}
|
||||
}
|
||||
|
||||
sub fabs(float value) -> float {
|
||||
%asm {{
|
||||
loadm.f fr0,{floats.fabs.value}
|
||||
fabs.f fr0,fr0
|
||||
return
|
||||
}}
|
||||
}
|
||||
|
||||
sub sin(float angle) -> float {
|
||||
%asm {{
|
||||
loadm.f fr0,{floats.sin.angle}
|
||||
fsin.f fr0,fr0
|
||||
return
|
||||
}}
|
||||
}
|
||||
|
||||
sub cos(float angle) -> float {
|
||||
%asm {{
|
||||
loadm.f fr0,{floats.cos.angle}
|
||||
fcos.f fr0,fr0
|
||||
return
|
||||
}}
|
||||
}
|
||||
|
||||
sub tan(float value) -> float {
|
||||
%asm {{
|
||||
loadm.f fr0,{floats.tan.value}
|
||||
ftan.f fr0,fr0
|
||||
return
|
||||
}}
|
||||
}
|
||||
|
||||
sub atan(float value) -> float {
|
||||
%asm {{
|
||||
loadm.f fr0,{floats.atan.value}
|
||||
fatan.f fr0,fr0
|
||||
return
|
||||
}}
|
||||
}
|
||||
|
||||
sub ln(float value) -> float {
|
||||
%asm {{
|
||||
loadm.f fr0,{floats.ln.value}
|
||||
fln.f fr0,fr0
|
||||
return
|
||||
}}
|
||||
}
|
||||
|
||||
sub log2(float value) -> float {
|
||||
%asm {{
|
||||
loadm.f fr0,{floats.log2.value}
|
||||
flog.f fr0,fr0
|
||||
return
|
||||
}}
|
||||
}
|
||||
|
||||
sub sqrt(float value) -> float {
|
||||
%asm {{
|
||||
loadm.f fr0,{floats.sqrt.value}
|
||||
fsqrt.f fr0,fr0
|
||||
return
|
||||
}}
|
||||
}
|
||||
|
||||
sub rad(float angle) -> float {
|
||||
; -- convert degrees to radians (d * pi / 180)
|
||||
return angle * PI / 180.0
|
||||
}
|
||||
|
||||
sub deg(float angle) -> float {
|
||||
; -- convert radians to degrees (d * (1/ pi * 180))
|
||||
return angle * 180.0 / PI
|
||||
}
|
||||
|
||||
sub round(float value) -> float {
|
||||
%asm {{
|
||||
loadm.f fr0,{floats.round.value}
|
||||
fround.f fr0,fr0
|
||||
return
|
||||
}}
|
||||
}
|
||||
|
||||
sub floor(float value) -> float {
|
||||
%asm {{
|
||||
loadm.f fr0,{floats.floor.value}
|
||||
ffloor.f fr0,fr0
|
||||
return
|
||||
}}
|
||||
}
|
||||
|
||||
sub ceil(float value) -> float {
|
||||
; -- ceil: tr = int(f); if tr==f -> return else return tr+1
|
||||
%asm {{
|
||||
loadm.f fr0,{floats.ceil.value}
|
||||
fceil.f fr0,fr0
|
||||
return
|
||||
}}
|
||||
}
|
||||
|
||||
sub rndf() -> float {
|
||||
%asm {{
|
||||
rnd.f fr0
|
||||
return
|
||||
}}
|
||||
}
|
||||
}
|
@ -3,5 +3,127 @@
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
|
||||
math {
|
||||
; TODO
|
||||
|
||||
sub sin8u(ubyte angle) -> ubyte {
|
||||
ubyte[256] sintab = [$80, $83, $86, $89, $8c, $8f, $92, $95, $98, $9b, $9e, $a2, $a5, $a7, $aa, $ad, $b0, $b3, $b6, $b9,
|
||||
$bc, $be, $c1, $c4, $c6, $c9, $cb, $ce, $d0, $d3, $d5, $d7, $da, $dc, $de, $e0,
|
||||
$e2, $e4, $e6, $e8, $ea, $eb, $ed, $ee, $f0, $f1, $f3, $f4, $f5, $f6, $f8, $f9,
|
||||
$fa, $fa, $fb, $fc, $fd, $fd, $fe, $fe, $fe, $ff, $ff, $ff, $ff, $ff, $ff, $ff,
|
||||
$fe, $fe, $fe, $fd, $fd, $fc, $fb, $fa, $fa, $f9, $f8, $f6, $f5, $f4, $f3, $f1,
|
||||
$f0, $ee, $ed, $eb, $ea, $e8, $e6, $e4, $e2, $e0, $de, $dc, $da, $d7, $d5, $d3,
|
||||
$d0, $ce, $cb, $c9, $c6, $c4, $c1, $be, $bc, $b9, $b6, $b3, $b0, $ad, $aa, $a7,
|
||||
$a5, $a2, $9e, $9b, $98, $95, $92, $8f, $8c, $89, $86, $83, $80, $7c, $79, $76,
|
||||
$73, $70, $6d, $6a, $67, $64, $61, $5d, $5a, $58, $55, $52, $4f, $4c, $49, $46,
|
||||
$43, $41, $3e, $3b, $39, $36, $34, $31, $2f, $2c, $2a, $28, $25, $23, $21, $1f,
|
||||
$1d, $1b, $19, $17, $15, $14, $12, $11, $0f, $0e, $0c, $0b, $0a, $09, $07, $06,
|
||||
$05, $05, $04, $03, $02, $02, $01, $01, $01, $00, $00, $00, $00, $00, $00, $00,
|
||||
$01, $01, $01, $02, $02, $03, $04, $05, $05, $06, $07, $09, $0a, $0b, $0c, $0e,
|
||||
$0f, $11, $12, $14, $15, $17, $19, $1b, $1d, $1f, $21, $23, $25, $28, $2a, $2c,
|
||||
$2f, $31, $34, $36, $39, $3b, $3e, $41, $43, $46, $49, $4c, $4f, $52, $55, $58,
|
||||
$5a, $5d, $61, $64, $67, $6a, $6d, $70, $73, $76, $79, $7c]
|
||||
return sintab[angle]
|
||||
}
|
||||
|
||||
sub cos8u(ubyte angle) -> ubyte {
|
||||
ubyte[256] costab = [$ff, $ff, $ff, $ff,
|
||||
$fe, $fe, $fe, $fd, $fd, $fc, $fb, $fa, $fa, $f9, $f8, $f6, $f5, $f4, $f3, $f1,
|
||||
$f0, $ee, $ed, $eb, $ea, $e8, $e6, $e4, $e2, $e0, $de, $dc, $da, $d7, $d5, $d3,
|
||||
$d0, $ce, $cb, $c9, $c6, $c4, $c1, $be, $bc, $b9, $b6, $b3, $b0, $ad, $aa, $a7,
|
||||
$a5, $a2, $9e, $9b, $98, $95, $92, $8f, $8c, $89, $86, $83, $80, $7c, $79, $76,
|
||||
$73, $70, $6d, $6a, $67, $64, $61, $5d, $5a, $58, $55, $52, $4f, $4c, $49, $46,
|
||||
$43, $41, $3e, $3b, $39, $36, $34, $31, $2f, $2c, $2a, $28, $25, $23, $21, $1f,
|
||||
$1d, $1b, $19, $17, $15, $14, $12, $11, $0f, $0e, $0c, $0b, $0a, $09, $07, $06,
|
||||
$05, $05, $04, $03, $02, $02, $01, $01, $01, $00, $00, $00, $00, $00, $00, $00,
|
||||
$01, $01, $01, $02, $02, $03, $04, $05, $05, $06, $07, $09, $0a, $0b, $0c, $0e,
|
||||
$0f, $11, $12, $14, $15, $17, $19, $1b, $1d, $1f, $21, $23, $25, $28, $2a, $2c,
|
||||
$2f, $31, $34, $36, $39, $3b, $3e, $41, $43, $46, $49, $4c, $4f, $52, $55, $58,
|
||||
$5a, $5d, $61, $64, $67, $6a, $6d, $70, $73, $76, $79, $7c, $7f, $83, $86, $89,
|
||||
$8c, $8f, $92, $95, $98, $9b, $9e, $a2, $a5, $a7, $aa, $ad, $b0, $b3, $b6, $b9,
|
||||
$bc, $be, $c1, $c4, $c6, $c9, $cb, $ce, $d0, $d3, $d5, $d7, $da, $dc, $de, $e0,
|
||||
$e2, $e4, $e6, $e8, $ea, $eb, $ed, $ee, $f0, $f1, $f3, $f4, $f5, $f6, $f8, $f9,
|
||||
$fa, $fa, $fb, $fc, $fd, $fd, $fe, $fe, $fe, $ff, $ff, $ff ]
|
||||
return costab[angle]
|
||||
}
|
||||
|
||||
sub sin8(ubyte angle) -> byte {
|
||||
return 42 ; TODO
|
||||
}
|
||||
|
||||
sub cos8(ubyte angle) -> byte {
|
||||
return 42 ; TODO
|
||||
}
|
||||
|
||||
sub sin16(ubyte angle) -> word {
|
||||
return 4242 ; TODO
|
||||
}
|
||||
|
||||
sub cos16(ubyte angle) -> word {
|
||||
return 4242 ; TODO
|
||||
}
|
||||
|
||||
sub sin16u(ubyte angle) -> uword {
|
||||
return 4242 ; TODO
|
||||
}
|
||||
|
||||
sub cos16u(ubyte angle) -> uword {
|
||||
return 4242 ; TODO
|
||||
}
|
||||
|
||||
sub sinr8u(ubyte radians) -> ubyte {
|
||||
ubyte[180] sintab = [
|
||||
$80, $84, $88, $8d,
|
||||
$91, $96, $9a, $9e, $a3, $a7, $ab, $af, $b3, $b7, $bb, $bf, $c3, $c7, $ca, $ce,
|
||||
$d1, $d5, $d8, $db, $de, $e1, $e4, $e7, $e9, $ec, $ee, $f0, $f2, $f4, $f6, $f7,
|
||||
$f9, $fa, $fb, $fc, $fd, $fe, $fe, $ff, $ff, $ff, $ff, $ff, $fe, $fe, $fd, $fc,
|
||||
$fb, $fa, $f9, $f7, $f6, $f4, $f2, $f0, $ee, $ec, $e9, $e7, $e4, $e1, $de, $db,
|
||||
$d8, $d5, $d1, $ce, $ca, $c7, $c3, $bf, $bb, $b7, $b3, $af, $ab, $a7, $a3, $9e,
|
||||
$9a, $96, $91, $8d, $88, $84, $80, $7b, $77, $72, $6e, $69, $65, $61, $5c, $58,
|
||||
$54, $50, $4c, $48, $44, $40, $3c, $38, $35, $31, $2e, $2a, $27, $24, $21, $1e,
|
||||
$1b, $18, $16, $13, $11, $0f, $0d, $0b, $09, $08, $06, $05, $04, $03, $02, $01,
|
||||
$01, $00, $00, $00, $00, $00, $01, $01, $02, $03, $04, $05, $06, $08, $09, $0b,
|
||||
$0d, $0f, $11, $13, $16, $18, $1b, $1e, $21, $24, $27, $2a, $2e, $31, $35, $38,
|
||||
$3c, $40, $44, $48, $4c, $50, $54, $58, $5c, $61, $65, $69, $6e, $72, $77, $7b]
|
||||
return sintab[radians]
|
||||
}
|
||||
|
||||
sub cosr8u(ubyte radians) -> ubyte {
|
||||
ubyte[180] costab = [
|
||||
$ff, $ff, $ff, $fe, $fe, $fd, $fc,
|
||||
$fb, $fa, $f9, $f7, $f6, $f4, $f2, $f0, $ee, $ec, $e9, $e7, $e4, $e1, $de, $db,
|
||||
$d8, $d5, $d1, $ce, $ca, $c7, $c3, $bf, $bb, $b7, $b3, $af, $ab, $a7, $a3, $9e,
|
||||
$9a, $96, $91, $8d, $88, $84, $80, $7b, $77, $72, $6e, $69, $65, $61, $5c, $58,
|
||||
$54, $50, $4c, $48, $44, $40, $3c, $38, $35, $31, $2e, $2a, $27, $24, $21, $1e,
|
||||
$1b, $18, $16, $13, $11, $0f, $0d, $0b, $09, $08, $06, $05, $04, $03, $02, $01,
|
||||
$01, $00, $00, $00, $00, $00, $01, $01, $02, $03, $04, $05, $06, $08, $09, $0b,
|
||||
$0d, $0f, $11, $13, $16, $18, $1b, $1e, $21, $24, $27, $2a, $2e, $31, $35, $38,
|
||||
$3c, $40, $44, $48, $4c, $50, $54, $58, $5c, $61, $65, $69, $6e, $72, $77, $7b,
|
||||
$7f, $84, $88, $8d, $91, $96, $9a, $9e, $a3, $a7, $ab, $af, $b3, $b7, $bb, $bf,
|
||||
$c3, $c7, $ca, $ce, $d1, $d5, $d8, $db, $de, $e1, $e4, $e7, $e9, $ec, $ee, $f0,
|
||||
$f2, $f4, $f6, $f7, $f9, $fa, $fb, $fc, $fd, $fe, $fe, $ff, $ff ]
|
||||
return costab[radians]
|
||||
}
|
||||
|
||||
sub sinr8(ubyte radians) -> byte {
|
||||
return 42 ; TODO
|
||||
}
|
||||
|
||||
sub cosr8(ubyte radians) -> byte {
|
||||
return 42 ; TODO
|
||||
}
|
||||
|
||||
sub sinr16(ubyte radians) -> word {
|
||||
return 4242 ; TODO
|
||||
}
|
||||
|
||||
sub cosr16(ubyte radians) -> word {
|
||||
return 4242 ; TODO
|
||||
}
|
||||
|
||||
sub sinr16u(ubyte radians) -> uword {
|
||||
return 4242 ; TODO
|
||||
}
|
||||
|
||||
sub cosr16u(ubyte radians) -> uword {
|
||||
return 4242 ; TODO
|
||||
}
|
||||
}
|
||||
|
@ -37,4 +37,19 @@ prog8_lib {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
sub string_compare(str st1, str st2) -> byte {
|
||||
; Compares two strings for sorting.
|
||||
; Returns -1 (255), 0 or 1 depending on wether string1 sorts before, equal or after string2.
|
||||
; Note that you can also directly compare strings and string values with eachother using
|
||||
; comparison operators ==, < etcetera (it will use strcmp for you under water automatically).
|
||||
%asm {{
|
||||
loadm.w r0, {prog8_lib.string_compare.st1}
|
||||
loadm.w r1, {prog8_lib.string_compare.st2}
|
||||
syscall 29
|
||||
return
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
119
compiler/res/prog8lib/virtual/string.p8
Normal file
119
compiler/res/prog8lib/virtual/string.p8
Normal file
@ -0,0 +1,119 @@
|
||||
; 0-terminated string manipulation routines. For the Virtual Machine target.
|
||||
;
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
|
||||
|
||||
string {
|
||||
sub length(str st) -> ubyte {
|
||||
; Returns the number of bytes in the string.
|
||||
; This value is determined during runtime and counts upto the first terminating 0 byte in the string,
|
||||
; regardless of the size of the string during compilation time. Don’t confuse this with len and sizeof!
|
||||
ubyte count = 0
|
||||
while st[count]
|
||||
count++
|
||||
return count
|
||||
}
|
||||
|
||||
sub left(str source, ubyte slen, str target) {
|
||||
; Copies the left side of the source string of the given length to target string.
|
||||
; It is assumed the target string buffer is large enough to contain the result.
|
||||
; Also, you have to make sure yourself that length is smaller or equal to the length of the source string.
|
||||
; Modifies in-place, doesn’t return a value (so can’t be used in an expression).
|
||||
target[slen] = 0
|
||||
ubyte ix
|
||||
for ix in 0 to slen-1 {
|
||||
target[ix] = source[ix]
|
||||
}
|
||||
}
|
||||
|
||||
sub right(str source, ubyte slen, str target) {
|
||||
; Copies the right side of the source string of the given length to target string.
|
||||
; It is assumed the target string buffer is large enough to contain the result.
|
||||
; Also, you have to make sure yourself that length is smaller or equal to the length of the source string.
|
||||
; Modifies in-place, doesn’t return a value (so can’t be used in an expression).
|
||||
ubyte offset = length(source)-slen
|
||||
ubyte ix
|
||||
for ix in 0 to slen-1 {
|
||||
target[ix] = source[ix+offset]
|
||||
}
|
||||
target[ix]=0
|
||||
}
|
||||
|
||||
sub slice(str source, ubyte start, ubyte slen, str target) {
|
||||
; Copies a segment from the source string, starting at the given index,
|
||||
; and of the given length to target string.
|
||||
; It is assumed the target string buffer is large enough to contain the result.
|
||||
; Also, you have to make sure yourself that start and length are within bounds of the strings.
|
||||
; Modifies in-place, doesn’t return a value (so can’t be used in an expression).
|
||||
ubyte ix
|
||||
for ix in 0 to slen-1 {
|
||||
target[ix] = source[ix+start]
|
||||
}
|
||||
target[ix]=0
|
||||
}
|
||||
|
||||
sub find(str st, ubyte character) -> ubyte {
|
||||
; Locates the first position of the given character in the string,
|
||||
; returns Carry set if found + index in A, or Carry clear if not found.
|
||||
ubyte ix
|
||||
for ix in 0 to length(st)-1 {
|
||||
if st[ix]==character {
|
||||
sys.set_carry()
|
||||
return ix
|
||||
}
|
||||
}
|
||||
sys.clear_carry()
|
||||
return 0
|
||||
}
|
||||
|
||||
sub copy(str source, str target) -> ubyte {
|
||||
; Copy a string to another, overwriting that one.
|
||||
; Returns the length of the string that was copied.
|
||||
; Often you don’t have to call this explicitly and can just write string1 = string2
|
||||
; but this function is useful if you’re dealing with addresses for instance.
|
||||
ubyte ix
|
||||
repeat {
|
||||
ubyte char=source[ix]
|
||||
target[ix]=char
|
||||
if not char
|
||||
return ix
|
||||
ix++
|
||||
}
|
||||
}
|
||||
|
||||
sub compare(str st1, str st2) -> byte {
|
||||
; Compares two strings for sorting.
|
||||
; Returns -1 (255), 0 or 1 depending on wether string1 sorts before, equal or after string2.
|
||||
; Note that you can also directly compare strings and string values with eachother using
|
||||
; comparison operators ==, < etcetera (it will use strcmp for you under water automatically).
|
||||
return prog8_lib.string_compare(st1, st2)
|
||||
}
|
||||
|
||||
sub lower(str st) -> ubyte {
|
||||
; Lowercases the petscii string in-place. Returns length of the string.
|
||||
; (for efficiency, non-letter characters > 128 will also not be left intact,
|
||||
; but regular text doesn't usually contain those characters anyway.)
|
||||
ubyte ix
|
||||
repeat {
|
||||
ubyte char=st[ix]
|
||||
if not char
|
||||
return ix
|
||||
if char >= 'A' and char <= 'Z'
|
||||
st[ix] = char | %00100000
|
||||
ix++
|
||||
}
|
||||
}
|
||||
|
||||
sub upper(str st) -> ubyte {
|
||||
; Uppercases the petscii string in-place. Returns length of the string.
|
||||
ubyte ix
|
||||
repeat {
|
||||
ubyte char=st[ix]
|
||||
if not char
|
||||
return ix
|
||||
if char >= 97 and char <= 122
|
||||
st[ix] = char & %11011111
|
||||
ix++
|
||||
}
|
||||
}
|
||||
}
|
@ -8,55 +8,43 @@ sys {
|
||||
|
||||
const ubyte target = 255 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16, 8 = atari800XL, 255 = virtual
|
||||
|
||||
; SYSCALLS
|
||||
; 0 = reset ; resets system
|
||||
; 1 = exit ; stops program and returns statuscode from r0.w
|
||||
; 2 = print_c ; print single character
|
||||
; 3 = print_s ; print 0-terminated string from memory
|
||||
; 4 = print_u8 ; print unsigned int byte
|
||||
; 5 = print_u16 ; print unsigned int word
|
||||
; 6 = input ; reads a line of text entered by the user, r0.w = memory buffer, r1.b = maxlength (0-255, 0=unlimited). Zero-terminates the string. Returns length in r65535.w
|
||||
; 7 = sleep ; sleep amount of milliseconds
|
||||
; 8 = gfx_enable ; enable graphics window r0.b = 0 -> lores 320x240, r0.b = 1 -> hires 640x480
|
||||
; 9 = gfx_clear ; clear graphics window with shade in r0.b
|
||||
; 10 = gfx_plot ; plot pixel in graphics window, r0.w/r1.w contain X and Y coordinates, r2.b contains brightness
|
||||
|
||||
const ubyte SC_RESET = 0
|
||||
const ubyte SC_EXIT = 1
|
||||
const ubyte SC_PRINT_C = 2
|
||||
const ubyte SC_PRINT_S = 3
|
||||
const ubyte SC_PRINT_U8 = 4
|
||||
const ubyte SC_PRINT_u16 = 5
|
||||
const ubyte SC_INPUT = 6
|
||||
const ubyte SC_SLEEP = 7
|
||||
const ubyte SC_GFX_ENABLE = 8
|
||||
const ubyte SC_GFX_CLEAR = 9
|
||||
const ubyte SC_GFX_PLOT = 10
|
||||
const ubyte SC_RND = 11
|
||||
const ubyte SC_WAIT = 12
|
||||
const ubyte SC_WAITVSYNC = 13
|
||||
|
||||
|
||||
sub reset_system() {
|
||||
; Soft-reset the system back to initial power-on Basic prompt.
|
||||
syscall(SC_RESET)
|
||||
%asm {{
|
||||
syscall 0
|
||||
}}
|
||||
}
|
||||
|
||||
sub wait(uword jiffies) {
|
||||
; --- wait approximately the given number of jiffies (1/60th seconds)
|
||||
syscall1(SC_WAIT, jiffies)
|
||||
%asm {{
|
||||
loadm.w r0, {sys.wait.jiffies}
|
||||
syscall 13
|
||||
}}
|
||||
}
|
||||
|
||||
sub waitvsync() {
|
||||
; --- busy wait till the next vsync has occurred (approximately), without depending on custom irq handling.
|
||||
syscall(SC_WAITVSYNC)
|
||||
%asm {{
|
||||
syscall 14
|
||||
}}
|
||||
}
|
||||
|
||||
sub memcopy(uword source, uword target, uword count) {
|
||||
repeat count {
|
||||
@(target) = @(source)
|
||||
sub internal_stringcopy(uword source, uword tgt) {
|
||||
; Called when the compiler wants to assign a string value to another string.
|
||||
while @(source) {
|
||||
@(tgt) = @(source)
|
||||
source++
|
||||
target++
|
||||
tgt++
|
||||
}
|
||||
@(tgt)=0
|
||||
}
|
||||
|
||||
sub memcopy(uword source, uword tgt, uword count) {
|
||||
repeat count {
|
||||
@(tgt) = @(source)
|
||||
source++
|
||||
tgt++
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,7 +64,39 @@ sys {
|
||||
|
||||
sub exit(ubyte returnvalue) {
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
syscall1(SC_EXIT, returnvalue)
|
||||
%asm {{
|
||||
loadm.b r0,{sys.exit.returnvalue}
|
||||
syscall 1
|
||||
}}
|
||||
}
|
||||
|
||||
sub set_carry() {
|
||||
%asm {{
|
||||
sec
|
||||
}}
|
||||
}
|
||||
|
||||
sub clear_carry() {
|
||||
%asm {{
|
||||
clc
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
sub gfx_enable(ubyte mode) {
|
||||
%asm {{
|
||||
loadm.b r0, {sys.gfx_enable.mode}
|
||||
syscall 8
|
||||
}}
|
||||
}
|
||||
|
||||
sub gfx_plot(uword xx, uword yy, ubyte color) {
|
||||
%asm {{
|
||||
loadm.w r0, {sys.gfx_plot.xx}
|
||||
loadm.w r1, {sys.gfx_plot.yy}
|
||||
loadm.b r2, {sys.gfx_plot.color}
|
||||
syscall 10
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,13 +2,16 @@
|
||||
;
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
|
||||
%import syslib
|
||||
|
||||
%import conv
|
||||
|
||||
txt {
|
||||
|
||||
sub clear_screen() {
|
||||
syscall1(3, "\x1b[2J\x1B[H")
|
||||
str @shared sequence = "\x1b[2J\x1B[H"
|
||||
%asm {{
|
||||
load.w r0, {txt.clear_screen.sequence}
|
||||
syscall 3
|
||||
}}
|
||||
}
|
||||
|
||||
sub nl() {
|
||||
@ -28,144 +31,95 @@ sub uppercase() {
|
||||
}
|
||||
|
||||
sub chrout(ubyte char) {
|
||||
syscall1(2, char)
|
||||
%asm {{
|
||||
loadm.b r0, {txt.chrout.char}
|
||||
syscall 2
|
||||
}}
|
||||
}
|
||||
|
||||
sub print (str text) {
|
||||
syscall1(3, text)
|
||||
%asm {{
|
||||
loadm.w r0, {txt.print.text}
|
||||
syscall 3
|
||||
}}
|
||||
}
|
||||
|
||||
sub print_ub0 (ubyte value) {
|
||||
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
|
||||
; TODO use conv module?
|
||||
ubyte hundreds = value / 100
|
||||
value -= hundreds*100
|
||||
ubyte tens = value / 10
|
||||
value -= tens*10
|
||||
chrout(hundreds+'0')
|
||||
chrout(tens+'0')
|
||||
chrout(value+'0')
|
||||
conv.str_ub0(value)
|
||||
print(conv.string_out)
|
||||
}
|
||||
|
||||
sub print_ub (ubyte value) {
|
||||
; ---- print the ubyte in decimal form, without left padding 0s
|
||||
; TODO use conv module?
|
||||
ubyte hundreds = value / 100
|
||||
value -= hundreds*100
|
||||
ubyte tens = value / 10
|
||||
value -= tens*10
|
||||
if hundreds
|
||||
goto print_hundreds
|
||||
if tens
|
||||
goto print_tens
|
||||
goto print_ones
|
||||
print_hundreds:
|
||||
chrout(hundreds+'0')
|
||||
print_tens:
|
||||
chrout(tens+'0')
|
||||
print_ones:
|
||||
chrout(value+'0')
|
||||
conv.str_ub(value)
|
||||
print(conv.string_out)
|
||||
}
|
||||
|
||||
sub print_b (byte value) {
|
||||
; ---- print the byte in decimal form, without left padding 0s
|
||||
if value<0 {
|
||||
chrout('-')
|
||||
value = -value
|
||||
}
|
||||
print_ub(value as ubyte)
|
||||
conv.str_b(value)
|
||||
print(conv.string_out)
|
||||
}
|
||||
|
||||
str hex_digits = "0123456789abcdef"
|
||||
|
||||
sub print_ubhex (ubyte value, ubyte prefix) {
|
||||
; ---- print the ubyte in hex form
|
||||
if prefix
|
||||
chrout('$')
|
||||
|
||||
chrout(hex_digits[value>>4])
|
||||
chrout(hex_digits[value&15])
|
||||
conv.str_ubhex(value)
|
||||
print(conv.string_out)
|
||||
}
|
||||
|
||||
sub print_ubbin (ubyte value, ubyte prefix) {
|
||||
; ---- print the ubyte in binary form
|
||||
; TODO use conv module?
|
||||
if prefix
|
||||
chrout('%')
|
||||
conv.str_ubbin(value)
|
||||
print(conv.string_out)
|
||||
}
|
||||
|
||||
sub print_uwbin (uword value, ubyte prefix) {
|
||||
; ---- print the uword in binary form
|
||||
; TODO use conv module?
|
||||
if prefix
|
||||
chrout('%')
|
||||
conv.str_uwbin(value)
|
||||
print(conv.string_out)
|
||||
}
|
||||
|
||||
sub print_uwhex (uword value, ubyte prefix) {
|
||||
; ---- print the uword in hexadecimal form (4 digits)
|
||||
print_ubhex(msb(value), true)
|
||||
print_ubhex(lsb(value), false)
|
||||
if prefix
|
||||
chrout('$')
|
||||
conv.str_uwhex(value)
|
||||
print(conv.string_out)
|
||||
}
|
||||
|
||||
sub print_uw0 (uword value) {
|
||||
; ---- print the uword value in decimal form, with left padding 0s (5 positions total)
|
||||
; TODO use conv module?
|
||||
ubyte tenthousands = (value / 10000) as ubyte
|
||||
value -= 10000*tenthousands
|
||||
ubyte thousands = (value / 1000) as ubyte
|
||||
value -= 1000*thousands
|
||||
ubyte hundreds = (value / 100) as ubyte
|
||||
value -= 100 as uword * hundreds
|
||||
ubyte tens = (value / 10) as ubyte
|
||||
value -= 10*tens
|
||||
chrout(tenthousands+'0')
|
||||
chrout(thousands+'0')
|
||||
chrout(hundreds+'0')
|
||||
chrout(tens+'0')
|
||||
chrout(value as ubyte + '0')
|
||||
conv.str_uw0(value)
|
||||
print(conv.string_out)
|
||||
}
|
||||
|
||||
sub print_uw (uword value) {
|
||||
; ---- print the uword in decimal form, without left padding 0s
|
||||
ubyte tenthousands = (value / 10000) as ubyte
|
||||
value -= 10000*tenthousands
|
||||
ubyte thousands = (value / 1000) as ubyte
|
||||
value -= 1000*thousands
|
||||
ubyte hundreds = (value / 100) as ubyte
|
||||
value -= 100 as uword * hundreds
|
||||
ubyte tens = (value / 10) as ubyte
|
||||
value -= 10*tens
|
||||
if tenthousands
|
||||
goto print_tenthousands
|
||||
if thousands
|
||||
goto print_thousands
|
||||
if hundreds
|
||||
goto print_hundreds
|
||||
if tens
|
||||
goto print_tens
|
||||
goto print_ones
|
||||
print_tenthousands:
|
||||
chrout(tenthousands+'0')
|
||||
print_thousands:
|
||||
chrout(thousands+'0')
|
||||
print_hundreds:
|
||||
chrout(hundreds+'0')
|
||||
print_tens:
|
||||
chrout(tens+'0')
|
||||
print_ones:
|
||||
chrout(value as ubyte + '0')
|
||||
conv.str_uw(value)
|
||||
print(conv.string_out)
|
||||
}
|
||||
|
||||
sub print_w (word value) {
|
||||
; ---- print the (signed) word in decimal form, without left padding 0's
|
||||
if value<0 {
|
||||
chrout('-')
|
||||
value = -value
|
||||
}
|
||||
print_uw(value as uword)
|
||||
conv.str_w(value)
|
||||
print(conv.string_out)
|
||||
}
|
||||
|
||||
sub input_chars (uword buffer) -> ubyte {
|
||||
; ---- Input a string (max. 80 chars) from the keyboard. Returns length of input. (string is terminated with a 0 byte as well)
|
||||
; It assumes the keyboard is selected as I/O channel!
|
||||
; TODO
|
||||
return 0
|
||||
%asm {{
|
||||
loadm.w r0,{txt.input_chars.buffer}
|
||||
syscall 6
|
||||
return
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
8.0
|
||||
8.1
|
||||
|
@ -4,17 +4,20 @@ import kotlinx.cli.*
|
||||
import prog8.ast.base.AstException
|
||||
import prog8.code.core.CbmPrgLauncherType
|
||||
import prog8.code.target.*
|
||||
import prog8.code.target.virtual.VirtualMachineDefinition
|
||||
import prog8.compiler.CompilationResult
|
||||
import prog8.compiler.CompilerArguments
|
||||
import prog8.compiler.compileProgram
|
||||
import java.io.File
|
||||
import java.nio.file.FileSystems
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.nio.file.StandardWatchEventKinds
|
||||
import java.time.LocalDateTime
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val buildVersion = object {}.javaClass.getResource("/version.txt")?.readText()?.trim()
|
||||
println("\nProg8 compiler v$buildVersion by Irmen de Jong (irmen@razorvine.net)")
|
||||
@ -46,6 +49,7 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler (one of '${C64Target.NAME}', '${C128Target.NAME}', '${Cx16Target.NAME}', '${AtariTarget.NAME}', '${VMTarget.NAME}')")
|
||||
.default(C64Target.NAME)
|
||||
val sourceDirs by cli.option(ArgType.String, fullName="srcdirs", description = "list of extra paths, separated with ${File.pathSeparator}, to search in for imported modules").multiple().delimiter(File.pathSeparator)
|
||||
val startVm by cli.option(ArgType.Boolean, fullName = "vm", description = "load and run a p8-virt listing in the VM instead")
|
||||
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
|
||||
|
||||
try {
|
||||
@ -76,6 +80,10 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
if(startVm==true) {
|
||||
return runVm(moduleFiles.first())
|
||||
}
|
||||
|
||||
if(watchMode==true) {
|
||||
val watchservice = FileSystems.getDefault().newWatchService()
|
||||
val allImportedFiles = mutableSetOf<Path>()
|
||||
@ -184,3 +192,14 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fun runVm(listingFilename: String): Boolean {
|
||||
val name =
|
||||
if(listingFilename.endsWith(".p8virt"))
|
||||
listingFilename.substring(0, listingFilename.length-7)
|
||||
else
|
||||
listingFilename
|
||||
val vmdef = VirtualMachineDefinition()
|
||||
vmdef.launchEmulator(0, Paths.get(name))
|
||||
return true
|
||||
}
|
||||
|
@ -195,8 +195,8 @@ private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuilt
|
||||
override val names = functions.keys
|
||||
override val purefunctionNames = functions.filter { it.value.pure }.map { it.key }.toSet()
|
||||
|
||||
override fun constValue(name: String, args: List<Expression>, position: Position): NumericLiteral? {
|
||||
val func = BuiltinFunctions[name]
|
||||
override fun constValue(funcName: String, args: List<Expression>, position: Position): NumericLiteral? {
|
||||
val func = BuiltinFunctions[funcName]
|
||||
if(func!=null) {
|
||||
val exprfunc = func.constExpressionFunc
|
||||
if(exprfunc!=null) {
|
||||
@ -213,8 +213,7 @@ private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuilt
|
||||
}
|
||||
return null
|
||||
}
|
||||
override fun returnType(name: String, args: MutableList<Expression>) =
|
||||
builtinFunctionReturnType(name, args, program)
|
||||
override fun returnType(funcName: String) = builtinFunctionReturnType(funcName)
|
||||
}
|
||||
|
||||
fun parseImports(filepath: Path,
|
||||
@ -265,7 +264,7 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
|
||||
.flatMap { (it as Directive).args }.toSet()
|
||||
val floatsEnabled = allOptions.any { it.name == "enable_floats" }
|
||||
val noSysInit = allOptions.any { it.name == "no_sysinit" }
|
||||
var zpType: ZeropageType =
|
||||
val zpType: ZeropageType =
|
||||
if (zpoption == null)
|
||||
if (floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE
|
||||
else
|
||||
@ -351,9 +350,10 @@ private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, e
|
||||
val optsDone1 = program.simplifyExpressions(errors)
|
||||
val optsDone2 = program.splitBinaryExpressions(compilerOptions)
|
||||
val optsDone3 = program.optimizeStatements(errors, functions, compTarget)
|
||||
val optsDone4 = program.inlineSubroutines()
|
||||
program.constantFold(errors, compTarget) // because simplified statements and expressions can result in more constants that can be folded away
|
||||
errors.report()
|
||||
if (optsDone1 + optsDone2 + optsDone3 == 0)
|
||||
if (optsDone1 + optsDone2 + optsDone3 + optsDone4 == 0)
|
||||
break
|
||||
}
|
||||
errors.report()
|
||||
@ -435,16 +435,15 @@ internal fun asmGeneratorFor(program: Program,
|
||||
{
|
||||
if(options.experimentalCodegen) {
|
||||
if (options.compTarget.machine.cpu in arrayOf(CpuType.CPU6502, CpuType.CPU65c02)) {
|
||||
|
||||
// TODO for now, use the new Intermediary Ast for this experimental codegen:
|
||||
val intermediateAst = IntermediateAstMaker(program).transform()
|
||||
return prog8.codegen.experimental.AsmGen(intermediateAst, symbolTable, options, errors)
|
||||
}
|
||||
} else {
|
||||
if (options.compTarget.machine.cpu in arrayOf(CpuType.CPU6502, CpuType.CPU65c02))
|
||||
// TODO rewrite 6502 codegen on new Intermediary Ast
|
||||
return prog8.codegen.cpu6502.AsmGen(program, symbolTable, options, errors)
|
||||
if (options.compTarget.name == VMTarget.NAME) {
|
||||
// TODO for now, use the new Intermediary Ast for this codegen:
|
||||
val intermediateAst = IntermediateAstMaker(program).transform()
|
||||
return prog8.codegen.virtual.CodeGen(intermediateAst, symbolTable, options, errors)
|
||||
}
|
||||
|
@ -29,12 +29,12 @@ internal class ErrorReporter: IErrorReporter {
|
||||
MessageSeverity.WARNING -> System.out
|
||||
MessageSeverity.ERROR -> System.err
|
||||
}
|
||||
when(it.severity) {
|
||||
MessageSeverity.ERROR -> printer.print("\u001b[91mERROR\u001B[0m ") // bright red
|
||||
MessageSeverity.WARNING -> printer.print("\u001b[93mWARN\u001B[0m ") // bright yellow
|
||||
}
|
||||
val msg = "${it.position.toClickableStr()} ${it.message}".trim()
|
||||
if(msg !in alreadyReportedMessages) {
|
||||
when(it.severity) {
|
||||
MessageSeverity.ERROR -> printer.print("\u001b[91mERROR\u001B[0m ") // bright red
|
||||
MessageSeverity.WARNING -> printer.print("\u001b[93mWARN\u001B[0m ") // bright yellow
|
||||
}
|
||||
printer.println(msg)
|
||||
alreadyReportedMessages.add(msg)
|
||||
when(it.severity) {
|
||||
|
@ -4,10 +4,9 @@ import com.github.michaelbull.result.Ok
|
||||
import com.github.michaelbull.result.Result
|
||||
import com.github.michaelbull.result.getOrElse
|
||||
import com.github.michaelbull.result.mapError
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.IStatementContainer
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.determineGosubArguments
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.code.ast.*
|
||||
@ -96,7 +95,7 @@ class IntermediateAstMaker(val program: Program) {
|
||||
return PtNop(srcAssign.position)
|
||||
}
|
||||
|
||||
val assign = PtAssignment(srcAssign.isAugmentable, srcAssign.position)
|
||||
val assign = PtAssignment(srcAssign.position)
|
||||
assign.add(transform(srcAssign.target))
|
||||
assign.add(transformExpression(srcAssign.value))
|
||||
return assign
|
||||
@ -131,8 +130,21 @@ class IntermediateAstMaker(val program: Program) {
|
||||
}
|
||||
|
||||
private fun transform(srcBlock: Block): PtBlock {
|
||||
var alignment = PtBlock.BlockAlignment.NONE
|
||||
var forceOutput = false
|
||||
val directives = srcBlock.statements.filterIsInstance<Directive>()
|
||||
for (directive in directives.filter { it.directive == "%option" }) {
|
||||
for (arg in directive.args) {
|
||||
when (arg.name) {
|
||||
"align_word" -> alignment = PtBlock.BlockAlignment.WORD
|
||||
"align_page" -> alignment = PtBlock.BlockAlignment.PAGE
|
||||
"force_output" -> forceOutput=true
|
||||
else -> throw FatalAstException("weird directive option: ${arg.name}")
|
||||
}
|
||||
}
|
||||
}
|
||||
val (vardecls, statements) = srcBlock.statements.partition { it is VarDecl }
|
||||
val block = PtBlock(srcBlock.name, srcBlock.address, srcBlock.isInLibrary, srcBlock.position)
|
||||
val block = PtBlock(srcBlock.name, srcBlock.address, srcBlock.isInLibrary, forceOutput, alignment, srcBlock.position)
|
||||
if(vardecls.isNotEmpty()) block.add(makeScopeVarsDecls(vardecls, srcBlock.position))
|
||||
for (stmt in statements)
|
||||
block.add(transformStatement(stmt))
|
||||
@ -148,8 +160,9 @@ class IntermediateAstMaker(val program: Program) {
|
||||
}
|
||||
|
||||
private fun transform(srcNode: BuiltinFunctionCallStatement): PtBuiltinFunctionCall {
|
||||
val type = builtinFunctionReturnType(srcNode.name, srcNode.args, program).getOr(DataType.UNDEFINED)
|
||||
val call = PtBuiltinFunctionCall(srcNode.name, true, type, srcNode.position)
|
||||
val type = builtinFunctionReturnType(srcNode.name).getOr(DataType.UNDEFINED)
|
||||
val noSideFx = BuiltinFunctions.getValue(srcNode.name).pure
|
||||
val call = PtBuiltinFunctionCall(srcNode.name, true, noSideFx, type, srcNode.position)
|
||||
for (arg in srcNode.args)
|
||||
call.add(transformExpression(arg))
|
||||
return call
|
||||
@ -184,7 +197,7 @@ class IntermediateAstMaker(val program: Program) {
|
||||
PtInlineAssembly(assembly, directive.position)
|
||||
}
|
||||
else -> {
|
||||
// other directives don't output any code
|
||||
// other directives don't output any code (but could end up in option flags somewhere else)
|
||||
PtNop(directive.position)
|
||||
}
|
||||
}
|
||||
@ -211,8 +224,14 @@ class IntermediateAstMaker(val program: Program) {
|
||||
|
||||
private fun transform(srcCall: FunctionCallExpression): PtFunctionCall {
|
||||
val (target, _) = targetOf(srcCall.target)
|
||||
val type = srcCall.inferType(program).getOrElse { throw FatalAstException("unknown dt") }
|
||||
val call = PtFunctionCall(target, false, type, srcCall.position)
|
||||
val type = srcCall.inferType(program).getOrElse {
|
||||
if((srcCall.parent as? Pipe)?.segments?.last() === srcCall)
|
||||
// for a pipe, the last segment is allowed to be a call to a function not returning anything.
|
||||
DataType.UNDEFINED
|
||||
else
|
||||
throw FatalAstException("unknown dt $srcCall")
|
||||
}
|
||||
val call = PtFunctionCall(target, type==DataType.UNDEFINED, type, srcCall.position)
|
||||
for (arg in srcCall.args)
|
||||
call.add(transformExpression(arg))
|
||||
return call
|
||||
@ -222,32 +241,10 @@ class IntermediateAstMaker(val program: Program) {
|
||||
// Gather the Goto and any preceding parameter assignments back into a single Function call node.
|
||||
// (the reason it was split up in the first place, is because the Compiler Ast optimizers
|
||||
// can then work on any complex expressions that are used as arguments.)
|
||||
val parent = gosub.parent as IStatementContainer
|
||||
val gosubIdx = parent.statements.indexOf(gosub)
|
||||
val previousNodes = parent.statements.subList(0, gosubIdx).reversed()
|
||||
val paramValues = mutableMapOf<String, Expression>()
|
||||
for (node in previousNodes) {
|
||||
if(node !is Assignment || node.origin!=AssignmentOrigin.PARAMETERASSIGN)
|
||||
break
|
||||
paramValues[node.target.identifier!!.nameInSource.last()] = node.value
|
||||
}
|
||||
// instead of just assigning to the parameters, another way is to use push()/pop()
|
||||
if(previousNodes.isNotEmpty()) {
|
||||
val first = previousNodes[0] as? IFunctionCall
|
||||
if(first!=null && (first.target.nameInSource.singleOrNull() in arrayOf("pop", "popw"))) {
|
||||
val numPops = previousNodes.indexOfFirst { (it as? IFunctionCall)?.target?.nameInSource?.singleOrNull() !in arrayOf("pop", "popw") }
|
||||
val pops = previousNodes.subList(0, numPops)
|
||||
val pushes = previousNodes.subList(numPops, numPops+numPops).reversed()
|
||||
for ((push, pop) in pushes.zip(pops)) {
|
||||
val name = ((pop as IFunctionCall).args.single() as IdentifierReference).nameInSource.last()
|
||||
val arg = (push as IFunctionCall).args.single()
|
||||
paramValues[name] = arg
|
||||
}
|
||||
}
|
||||
}
|
||||
val arguments = determineGosubArguments(gosub)
|
||||
|
||||
val parameters = gosub.identifier.targetSubroutine(program)!!.parameters
|
||||
if(paramValues.size != parameters.size)
|
||||
if(arguments.size != parameters.size)
|
||||
throw FatalAstException("mismatched number of parameter assignments for function call")
|
||||
|
||||
val target = transform(gosub.identifier)
|
||||
@ -255,7 +252,7 @@ class IntermediateAstMaker(val program: Program) {
|
||||
|
||||
// put arguments in correct order for the parameters
|
||||
parameters.forEach {
|
||||
val argument = paramValues.getValue(it.name)
|
||||
val argument = arguments.getValue(it.name)
|
||||
call.add(transformExpression(argument))
|
||||
}
|
||||
|
||||
@ -291,8 +288,7 @@ class IntermediateAstMaker(val program: Program) {
|
||||
PtLabel(label.name, label.position)
|
||||
|
||||
private fun transform(srcPipe: Pipe): PtPipe {
|
||||
val type = srcPipe.segments.last().inferType(program).getOrElse { throw FatalAstException("unknown dt") }
|
||||
val pipe = PtPipe(type, true, srcPipe.position)
|
||||
val pipe = PtPipe(DataType.UNDEFINED, true, srcPipe.position)
|
||||
pipe.add(transformExpression(srcPipe.source))
|
||||
for (segment in srcPipe.segments)
|
||||
pipe.add(transformExpression(segment))
|
||||
@ -431,7 +427,8 @@ class IntermediateAstMaker(val program: Program) {
|
||||
|
||||
private fun transform(srcCall: BuiltinFunctionCall): PtBuiltinFunctionCall {
|
||||
val type = srcCall.inferType(program).getOrElse { throw FatalAstException("unknown dt") }
|
||||
val call = PtBuiltinFunctionCall(srcCall.name, false, type, srcCall.position)
|
||||
val noSideFx = BuiltinFunctions.getValue(srcCall.name).pure
|
||||
val call = PtBuiltinFunctionCall(srcCall.name, false, noSideFx, type, srcCall.position)
|
||||
for (arg in srcCall.args)
|
||||
call.add(transformExpression(arg))
|
||||
return call
|
||||
|
@ -4,12 +4,13 @@ import com.github.michaelbull.result.*
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.SyntaxError
|
||||
import prog8.ast.statements.Block
|
||||
import prog8.ast.statements.Directive
|
||||
import prog8.ast.statements.DirectiveArg
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.code.core.Position
|
||||
import prog8.parser.Prog8Parser
|
||||
import prog8.code.core.SourceCode
|
||||
import prog8.parser.Prog8Parser
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.*
|
||||
@ -107,6 +108,18 @@ class ModuleImporter(private val program: Program,
|
||||
)
|
||||
|
||||
removeDirectivesFromImportedModule(importedModule)
|
||||
|
||||
// modules can contain blocks with "merge" option.
|
||||
// their content has to be merged into already existing block with the same name.
|
||||
val blocks = importedModule.statements.filterIsInstance<Block>()
|
||||
for(block in blocks) {
|
||||
if("merge" in block.options()) {
|
||||
val existingBlock = program.allBlocks.first { it.name==block.name}
|
||||
existingBlock.statements.addAll(block.statements.filter { it !is Directive})
|
||||
importedModule.statements.remove(block)
|
||||
}
|
||||
}
|
||||
|
||||
return importedModule
|
||||
}
|
||||
|
||||
|
@ -244,9 +244,18 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
override fun visit(inlineAssembly: InlineAssembly) {
|
||||
val assembly = inlineAssembly.assembly
|
||||
if(" rti" in assembly || "\trti" in assembly || " rts" in assembly || "\trts" in assembly ||
|
||||
" jmp" in assembly || "\tjmp" in assembly || " bra" in assembly || "\tbra" in assembly )
|
||||
count++
|
||||
if(compilerOptions.compTarget.name!=VMTarget.NAME) {
|
||||
if (" rti" in assembly || "\trti" in assembly || " rts" in assembly || "\trts" in assembly ||
|
||||
" jmp" in assembly || "\tjmp" in assembly || " bra" in assembly || "\tbra" in assembly
|
||||
)
|
||||
count++
|
||||
} else {
|
||||
if(" return" in assembly || "\treturn" in assembly
|
||||
|| " jump" in assembly || "\tjump" in assembly
|
||||
|| " jumpi" in assembly || "\tjumpi" in assembly
|
||||
)
|
||||
count++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -289,8 +298,10 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
if(compilerOptions.compTarget.name!=VMTarget.NAME && subroutine.inline && !subroutine.isAsmSubroutine)
|
||||
err("subroutine inlining is currently only supported on asmsub routines")
|
||||
// Most code generation targets only support subroutine inlining on asmsub subroutines
|
||||
// So we reset the flag here to be sure it doesn't cause problems down the line in the codegen.
|
||||
if(!subroutine.isAsmSubroutine && compilerOptions.compTarget.name!=VMTarget.NAME)
|
||||
subroutine.inline = false
|
||||
|
||||
if(subroutine.parent !is Block && subroutine.parent !is Subroutine)
|
||||
err("subroutines can only be defined in the scope of a block or within another subroutine")
|
||||
@ -496,7 +507,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(constVal==null) {
|
||||
val sourceDatatype = assignment.value.inferType(program)
|
||||
if (sourceDatatype.isUnknown) {
|
||||
if (assignment.value !is FunctionCallExpression)
|
||||
if (assignment.value !is FunctionCallExpression && assignment.value !is PipeExpression)
|
||||
errors.err("assignment value is invalid or has no proper datatype, maybe forgot '&' (address-of)", assignment.value.position)
|
||||
} else {
|
||||
checkAssignmentCompatible(targetDatatype.getOr(DataType.UNDEFINED),
|
||||
@ -640,6 +651,9 @@ internal class AstChecker(private val program: Program,
|
||||
if(parameter==null)
|
||||
err("string var must be initialized with a string literal")
|
||||
}
|
||||
|
||||
if(decl.value !is StringLiteral)
|
||||
err("string var must be initialized with a string literal")
|
||||
}
|
||||
|
||||
if(compilerOptions.zeropage==ZeropageType.DONTUSE && decl.zeropage == ZeropageWish.REQUIRE_ZEROPAGE)
|
||||
@ -727,7 +741,7 @@ internal class AstChecker(private val program: Program,
|
||||
err("this directive may only occur in a block or at module level")
|
||||
if(directive.args.isEmpty())
|
||||
err("missing option directive argument(s)")
|
||||
else if(directive.args.map{it.name in arrayOf("enable_floats", "force_output", "no_sysinit", "align_word", "align_page")}.any { !it })
|
||||
else if(directive.args.map{it.name in arrayOf("enable_floats", "force_output", "no_sysinit", "align_word", "align_page", "merge")}.any { !it })
|
||||
err("invalid option directive argument(s)")
|
||||
}
|
||||
else -> throw SyntaxError("invalid directive ${directive.directive}", directive.position)
|
||||
@ -875,9 +889,9 @@ internal class AstChecker(private val program: Program,
|
||||
if(rightDt!in NumericDatatypes && rightDt != DataType.STR)
|
||||
errors.err("right operand is not numeric or str", expr.right.position)
|
||||
if(leftDt!=rightDt) {
|
||||
if(leftDt==DataType.STR && rightDt in IntegerDatatypes) {
|
||||
if(leftDt==DataType.STR && rightDt in IntegerDatatypes && expr.operator=="*") {
|
||||
// only exception allowed: str * constvalue
|
||||
if(expr.right.constValue(program)!=null)
|
||||
if(expr.right.constValue(program)==null)
|
||||
errors.err("can only use string repeat with a constant number value", expr.left.position)
|
||||
} else {
|
||||
errors.err("left and right operands aren't the same type", expr.left.position)
|
||||
@ -986,14 +1000,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
else if(targetStatement is BuiltinFunctionPlaceholder) {
|
||||
val args = if(functionCallExpr.parent is IPipe) {
|
||||
// pipe segment, add implicit first argument
|
||||
val firstArgDt = BuiltinFunctions.getValue(targetStatement.name).parameters.first().possibleDatatypes.first()
|
||||
listOf(defaultZero(firstArgDt, functionCallExpr.position)) + functionCallExpr.args
|
||||
} else {
|
||||
functionCallExpr.args
|
||||
}
|
||||
if(builtinFunctionReturnType(targetStatement.name, args, program).isUnknown) {
|
||||
if(builtinFunctionReturnType(targetStatement.name).isUnknown) {
|
||||
if(functionCallExpr.parent is Expression || functionCallExpr.parent is Assignment)
|
||||
errors.err("function doesn't return a value", functionCallExpr.position)
|
||||
}
|
||||
@ -1069,10 +1076,8 @@ internal class AstChecker(private val program: Program,
|
||||
errors.err("swap requires args of numerical type", position)
|
||||
}
|
||||
else if(target.name=="all" || target.name=="any") {
|
||||
if((args[0] as? AddressOf)?.identifier?.targetVarDecl(program)?.datatype == DataType.STR) {
|
||||
errors.err("any/all on a string is useless (is always true unless the string is empty)", position)
|
||||
}
|
||||
if(args[0].inferType(program).getOr(DataType.STR) == DataType.STR) {
|
||||
if((args[0] as? AddressOf)?.identifier?.targetVarDecl(program)?.datatype == DataType.STR
|
||||
|| args[0].inferType(program).getOr(DataType.STR) == DataType.STR) {
|
||||
errors.err("any/all on a string is useless (is always true unless the string is empty)", position)
|
||||
}
|
||||
}
|
||||
@ -1112,9 +1117,15 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(pipe: PipeExpression) = process(pipe)
|
||||
override fun visit(pipe: PipeExpression) {
|
||||
process(pipe)
|
||||
super.visit(pipe)
|
||||
}
|
||||
|
||||
override fun visit(pipe: Pipe) = process(pipe)
|
||||
override fun visit(pipe: Pipe) {
|
||||
process(pipe)
|
||||
super.visit(pipe)
|
||||
}
|
||||
|
||||
private fun process(pipe: IPipe) {
|
||||
if(pipe.source in pipe.segments)
|
||||
@ -1123,6 +1134,14 @@ internal class AstChecker(private val program: Program,
|
||||
throw FatalAstException("pipe is missing one or more expressions")
|
||||
if(pipe.segments.any { it !is IFunctionCall })
|
||||
throw FatalAstException("pipe segments can only be function calls")
|
||||
|
||||
if(compilerOptions.compTarget !is VMTarget) {
|
||||
pipe.segments.forEach {
|
||||
it as IFunctionCall
|
||||
if (it.args.size > 0)
|
||||
errors.err("only unary functions supported in pipe expressions for now", it.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(postIncrDecr: PostIncrDecr) {
|
||||
@ -1505,7 +1524,7 @@ internal fun checkUnusedReturnValues(call: FunctionCallStatement, target: Statem
|
||||
else
|
||||
errors.warn("result values of subroutine call are discarded (use void?)", call.position)
|
||||
} else if (target is BuiltinFunctionPlaceholder) {
|
||||
val rt = builtinFunctionReturnType(target.name, call.args, program)
|
||||
val rt = builtinFunctionReturnType(target.name)
|
||||
if (rt.isKnown)
|
||||
errors.warn("result value of a function call is discarded (use void?)", call.position)
|
||||
}
|
||||
|
@ -24,6 +24,10 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
errors.err("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position)
|
||||
}
|
||||
|
||||
private fun nameShadowWarning(name: String, position: Position, existing: Statement) {
|
||||
errors.warn("name '$name' shadows occurrence at ${existing.position.file} line ${existing.position.line}", position)
|
||||
}
|
||||
|
||||
override fun visit(block: Block) {
|
||||
if(block.name in compTarget.machine.opcodeNames)
|
||||
errors.err("can't use a cpu opcode name as a symbol: '${block.name}'", block.position)
|
||||
@ -50,9 +54,13 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
if(decl.name in compTarget.machine.opcodeNames)
|
||||
errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position)
|
||||
|
||||
val existing = decl.definingScope.lookup(listOf(decl.name))
|
||||
if (existing != null && existing !== decl)
|
||||
nameError(decl.name, decl.position, existing)
|
||||
val existing = decl.parent.definingScope.lookup(listOf(decl.name))
|
||||
if (existing != null && existing !== decl && existing is VarDecl) {
|
||||
if(existing.parent!==decl.parent)
|
||||
nameShadowWarning(decl.name, decl.position, existing)
|
||||
else
|
||||
nameError(decl.name, decl.position, existing)
|
||||
}
|
||||
|
||||
if(decl.definingBlock.name==decl.name)
|
||||
nameError(decl.name, decl.position, decl.definingBlock)
|
||||
@ -74,8 +82,12 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
// checkResult.add(NameError("builtin function name cannot be used as parameter", subroutine.position))
|
||||
|
||||
val existing = subroutine.lookup(listOf(subroutine.name))
|
||||
if (existing != null && existing !== subroutine)
|
||||
nameError(subroutine.name, subroutine.position, existing)
|
||||
if (existing != null && existing !== subroutine) {
|
||||
if(existing.parent!==existing.parent)
|
||||
nameShadowWarning(subroutine.name, subroutine.position, existing)
|
||||
else
|
||||
nameError(subroutine.name, subroutine.position, existing)
|
||||
}
|
||||
|
||||
// check that there are no local symbols (variables, labels, subs) that redefine the subroutine's parameters.
|
||||
val symbolsInSub = subroutine.allDefinedSymbols
|
||||
|
@ -119,9 +119,9 @@ class AstPreprocessor(val program: Program, val errors: IErrorReporter, val comp
|
||||
val newSegments = psrc.segments
|
||||
newSegments += pipe.segments.single()
|
||||
return listOf(IAstModification.ReplaceNode(pipe as Node, Pipe(newSource, newSegments, pipe.position), parent))
|
||||
}
|
||||
|
||||
return process(pipe, parent)
|
||||
} else if(pipe.source is IPipe)
|
||||
throw InternalCompilerException("pipe source should have been adjusted to be a normal expression")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(pipeExpr: PipeExpression, parent: Node): Iterable<IAstModification> {
|
||||
@ -132,46 +132,8 @@ class AstPreprocessor(val program: Program, val errors: IErrorReporter, val comp
|
||||
val newSegments = psrc.segments
|
||||
newSegments += pipeExpr.segments.single()
|
||||
return listOf(IAstModification.ReplaceNode(pipeExpr as Node, PipeExpression(newSource, newSegments, pipeExpr.position), parent))
|
||||
}
|
||||
|
||||
return process(pipeExpr, parent)
|
||||
}
|
||||
|
||||
private fun process(pipe: IPipe, parent: Node): Iterable<IAstModification> {
|
||||
if(pipe.source is IPipe)
|
||||
} else if(pipeExpr.source is IPipe)
|
||||
throw InternalCompilerException("pipe source should have been adjusted to be a normal expression")
|
||||
|
||||
return noModifications
|
||||
|
||||
// TODO don't use artifical inserted args, fix the places that check for arg numbers instead.
|
||||
// add the "missing" first argument to each function call in the pipe segments
|
||||
// so that all function call related checks just pass
|
||||
// might have to remove it again when entering code generation pass, or just replace it there
|
||||
// with the proper output value of the previous pipe segment.
|
||||
|
||||
// val mutations = mutableListOf<IAstModification>()
|
||||
// var valueDt = pipe.source.inferType(program).getOrElse { throw FatalAstException("invalid dt") }
|
||||
// pipe.segments.forEach { call->
|
||||
// val dummyFirstArg = when (valueDt) {
|
||||
// DataType.UBYTE -> FunctionCallExpression(IdentifierReference(listOf("rnd"), pipe.position), mutableListOf(), pipe.position)
|
||||
// DataType.UWORD -> FunctionCallExpression(IdentifierReference(listOf("rndw"), pipe.position), mutableListOf(), pipe.position)
|
||||
// DataType.BYTE, DataType.WORD -> IdentifierReference(
|
||||
// getTempRegisterName(InferredTypes.InferredType.known(valueDt)),
|
||||
// pipe.position
|
||||
// ) // there's no builtin function we can abuse that returns a signed byte or word type // TODO maybe use a typecasted expression around rnd?
|
||||
// DataType.FLOAT -> FunctionCallExpression(IdentifierReference(listOf("rndf"), pipe.position), mutableListOf(), pipe.position)
|
||||
// else -> throw FatalAstException("invalid dt")
|
||||
// }
|
||||
//
|
||||
// mutations += IAstModification.SetExpression(
|
||||
// { newexpr -> call.args.add(0, newexpr) },
|
||||
// dummyFirstArg, parent
|
||||
// )
|
||||
//
|
||||
// if(call!==pipe.segments.last())
|
||||
// valueDt = call.inferType(program).getOrElse { throw FatalAstException("invalid dt") }
|
||||
// }
|
||||
// return mutations
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import prog8.ast.Program
|
||||
import prog8.ast.expressions.ArrayIndexedExpression
|
||||
import prog8.ast.expressions.BinaryExpression
|
||||
import prog8.ast.expressions.DirectMemoryRead
|
||||
import prog8.ast.expressions.StringLiteral
|
||||
import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.DirectMemoryWrite
|
||||
import prog8.ast.statements.Subroutine
|
||||
@ -39,65 +38,9 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
val leftStr = expr.left as? StringLiteral
|
||||
val rightStr = expr.right as? StringLiteral
|
||||
if(expr.operator == "+") {
|
||||
val concatenatedString = concatString(expr)
|
||||
if(concatenatedString!=null)
|
||||
return listOf(IAstModification.ReplaceNode(expr, concatenatedString, parent))
|
||||
}
|
||||
else if(expr.operator == "*") {
|
||||
if (leftStr!=null) {
|
||||
val amount = expr.right.constValue(program)
|
||||
if(amount!=null) {
|
||||
val string = leftStr.value.repeat(amount.number.toInt())
|
||||
val strval = StringLiteral(string, leftStr.encoding, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, strval, parent))
|
||||
}
|
||||
}
|
||||
else if (rightStr!=null) {
|
||||
val amount = expr.right.constValue(program)
|
||||
if(amount!=null) {
|
||||
val string = rightStr.value.repeat(amount.number.toInt())
|
||||
val strval = StringLiteral(string, rightStr.encoding, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, strval, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||
return replacePointerVarIndexWithMemreadOrMemwrite(program, arrayIndexedExpression, parent)
|
||||
}
|
||||
|
||||
private fun concatString(expr: BinaryExpression): StringLiteral? {
|
||||
val rightStrval = expr.right as? StringLiteral
|
||||
val leftStrval = expr.left as? StringLiteral
|
||||
return when {
|
||||
expr.operator!="+" -> null
|
||||
expr.left is BinaryExpression && rightStrval!=null -> {
|
||||
val subStrVal = concatString(expr.left as BinaryExpression)
|
||||
if(subStrVal==null)
|
||||
null
|
||||
else
|
||||
StringLiteral("${subStrVal.value}${rightStrval.value}", subStrVal.encoding, rightStrval.position)
|
||||
}
|
||||
expr.right is BinaryExpression && leftStrval!=null -> {
|
||||
val subStrVal = concatString(expr.right as BinaryExpression)
|
||||
if(subStrVal==null)
|
||||
null
|
||||
else
|
||||
StringLiteral("${leftStrval.value}${subStrVal.value}", subStrVal.encoding, leftStrval.position)
|
||||
}
|
||||
leftStrval!=null && rightStrval!=null -> {
|
||||
StringLiteral("${leftStrval.value}${rightStrval.value}", leftStrval.encoding, leftStrval.position)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -7,7 +7,6 @@ import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.code.ast.PtIdentifier
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.VMTarget
|
||||
|
||||
@ -125,7 +124,7 @@ internal class BeforeAsmAstChanger(val program: Program,
|
||||
|
||||
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernal routine.
|
||||
// and if an assembly block doesn't contain a rts/rti, and some other situations.
|
||||
if (!subroutine.isAsmSubroutine && (!subroutine.inline || !options.optimize)) {
|
||||
if (!subroutine.isAsmSubroutine) {
|
||||
if(subroutine.statements.isEmpty() ||
|
||||
(subroutine.amountOfRtsInAsm() == 0
|
||||
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
|
||||
@ -228,11 +227,9 @@ internal class BeforeAsmAstChanger(val program: Program,
|
||||
|
||||
// TODO: somehow figure out if the expr will result in stack-evaluation STILL after being split off,
|
||||
// in that case: do *not* split it off but just keep it as it is (otherwise code size increases)
|
||||
// TODO: this should be replaced by a general expression-evaluation optimization step.
|
||||
// the actual conditional expression in the statement should be no more than VARIABLE <COMPARISON-OPERATOR> SIMPLE-EXPRESSION
|
||||
// NOTE: do NOT move this to an earler ast transform phase (such as StatementReorderer or StatementOptimizer) - it WILL result in larger code.
|
||||
|
||||
if(options.compTarget.name==VMTarget.NAME)
|
||||
if(options.compTarget.name==VMTarget.NAME) // don't apply this optimizer for Vm target
|
||||
return CondExprSimplificationResult(null, null, null, null)
|
||||
|
||||
var leftAssignment: Assignment? = null
|
||||
|
@ -4,6 +4,7 @@ import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.expressions.ArrayLiteral
|
||||
import prog8.ast.expressions.BinaryExpression
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.StringLiteral
|
||||
import prog8.ast.statements.VarDecl
|
||||
@ -27,6 +28,10 @@ internal class LiteralsToAutoVars(private val program: Program,
|
||||
return noModifications
|
||||
}
|
||||
if(string.parent !is VarDecl && string.parent !is WhenChoice) {
|
||||
val binExpr = string.parent as? BinaryExpression
|
||||
if(binExpr!=null &&(binExpr.operator=="+" || binExpr.operator=="*"))
|
||||
return noModifications // allow string concatenation or repeats later, based on just string literals
|
||||
|
||||
// replace the literal string by an identifier reference to the interned string
|
||||
val parentFunc = (string.parent as? IFunctionCall)?.target
|
||||
if(parentFunc!=null) {
|
||||
|
@ -316,6 +316,9 @@ internal class StatementReorderer(val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
if(valueType.isString && (targetType istype DataType.STR || targetType istype DataType.ARRAY_B || targetType istype DataType.ARRAY_UB))
|
||||
return copyStringValue(assignment)
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@ -409,8 +412,22 @@ internal class StatementReorderer(val program: Program,
|
||||
return listOf(IAstModification.ReplaceNode(assign, memcopy, assign.parent))
|
||||
}
|
||||
|
||||
private fun copyStringValue(assign: Assignment): List<IAstModification> {
|
||||
val identifier = assign.target.identifier!!
|
||||
val strcopy = FunctionCallStatement(IdentifierReference(listOf("sys", "internal_stringcopy"), assign.position),
|
||||
mutableListOf(
|
||||
assign.value as? IdentifierReference ?: assign.value,
|
||||
identifier
|
||||
),
|
||||
true,
|
||||
assign.position
|
||||
)
|
||||
return listOf(IAstModification.ReplaceNode(assign, strcopy, assign.parent))
|
||||
}
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
val function = functionCallStatement.target.targetStatement(program)!!
|
||||
val function = functionCallStatement.target.targetStatement(program)
|
||||
?: throw FatalAstException("no target for $functionCallStatement")
|
||||
checkUnusedReturnValues(functionCallStatement, function, program, errors)
|
||||
return tryReplaceCallWithGosub(functionCallStatement, parent, program, options)
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.code.*
|
||||
import prog8.code.core.ArrayDatatypes
|
||||
import prog8.code.core.ElementToArrayTypes
|
||||
import prog8.code.core.Position
|
||||
import java.util.*
|
||||
|
||||
|
@ -114,6 +114,10 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
if(valuetype in IterableDatatypes && targettype==DataType.UWORD)
|
||||
// special case, don't typecast STR/arrays to UWORD, we support those assignments "directly"
|
||||
return noModifications
|
||||
if((assignment.value as? BinaryExpression)?.operator in ComparisonOperators) {
|
||||
// special case, treat a boolean comparison result as the same type as the target value to avoid needless casts later
|
||||
return noModifications
|
||||
}
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
addTypecastOrCastedValueModification(modifications, assignment.value, targettype, assignment)
|
||||
return modifications
|
||||
|
@ -11,10 +11,7 @@ import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.ArrayDatatypes
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.code.core.*
|
||||
|
||||
|
||||
internal class VariousCleanups(val program: Program, val errors: IErrorReporter, val options: CompilationOptions): AstWalker() {
|
||||
@ -94,13 +91,47 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
|
||||
}
|
||||
|
||||
override fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
// try to replace a multi-comparison expression (if x==1 or x==2 or x==3 ... ) by a simple containment check.
|
||||
// but only if the containment check is the top-level expression.
|
||||
if(parent is BinaryExpression)
|
||||
return noModifications
|
||||
if(expr.operator == "or") {
|
||||
val leftBinExpr = expr.left as? BinaryExpression
|
||||
val rightBinExpr = expr.right as? BinaryExpression
|
||||
if(leftBinExpr!=null && leftBinExpr.operator=="==" && rightBinExpr!=null && rightBinExpr.operator=="==") {
|
||||
if(leftBinExpr.right is NumericLiteral && rightBinExpr.right is NumericLiteral) {
|
||||
if(leftBinExpr.left isSameAs rightBinExpr.left)
|
||||
errors.warn("consider using 'in' or 'when' to test for multiple values", expr.position)
|
||||
val leftBinExpr1 = expr.left as? BinaryExpression
|
||||
val rightBinExpr1 = expr.right as? BinaryExpression
|
||||
|
||||
if(rightBinExpr1?.operator=="==" && rightBinExpr1.right is NumericLiteral && leftBinExpr1!=null) {
|
||||
val needle = rightBinExpr1.left
|
||||
val values = mutableListOf(rightBinExpr1.right as NumericLiteral)
|
||||
|
||||
fun isMultiComparisonRecurse(expr: BinaryExpression): Boolean {
|
||||
if(expr.operator=="==") {
|
||||
if(expr.right is NumericLiteral && expr.left isSameAs needle) {
|
||||
values.add(expr.right as NumericLiteral)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
if(expr.operator!="or")
|
||||
return false
|
||||
val leftBinExpr = expr.left as? BinaryExpression
|
||||
val rightBinExpr = expr.right as? BinaryExpression
|
||||
if(leftBinExpr==null || rightBinExpr==null || rightBinExpr.right !is NumericLiteral || !rightBinExpr.left.isSameAs(needle))
|
||||
return false
|
||||
if(rightBinExpr.operator=="==")
|
||||
values.add(rightBinExpr.right as NumericLiteral)
|
||||
else
|
||||
return false
|
||||
return isMultiComparisonRecurse(leftBinExpr)
|
||||
}
|
||||
|
||||
if(isMultiComparisonRecurse(leftBinExpr1)) {
|
||||
// replace it!
|
||||
val valueCopies = values.sortedBy { it.number }.map { it.copy() }
|
||||
val elementType = needle.inferType(program).getOrElse { throw FatalAstException("invalid needle dt") }
|
||||
val arrayType = ElementToArrayTypes.getValue(elementType)
|
||||
val valuesArray = ArrayLiteral(InferredTypes.InferredType.known(arrayType), valueCopies.toTypedArray(), expr.position)
|
||||
val containment = ContainmentCheck(needle, valuesArray, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, containment, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.IPipe
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.FunctionCallExpression
|
||||
@ -114,12 +117,12 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
|
||||
}
|
||||
|
||||
override fun visit(pipe: PipeExpression) {
|
||||
processPipe(pipe.source, pipe.segments, pipe)
|
||||
processPipe(pipe.source, pipe.segments)
|
||||
if(errors.noErrors()) {
|
||||
val last = (pipe.segments.last() as IFunctionCall).target
|
||||
when (val target = last.targetStatement(program)!!) {
|
||||
is BuiltinFunctionPlaceholder -> {
|
||||
if (!BuiltinFunctions.getValue(target.name).hasReturn)
|
||||
if (BuiltinFunctions.getValue(target.name).returnType==null)
|
||||
errors.err("invalid pipe expression; last term doesn't return a value", last.position)
|
||||
}
|
||||
is Subroutine -> {
|
||||
@ -135,13 +138,13 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
|
||||
}
|
||||
|
||||
override fun visit(pipe: Pipe) {
|
||||
processPipe(pipe.source, pipe.segments, pipe)
|
||||
processPipe(pipe.source, pipe.segments)
|
||||
if(errors.noErrors()) {
|
||||
super.visit(pipe)
|
||||
}
|
||||
}
|
||||
|
||||
private fun processPipe(source: Expression, segments: List<Expression>, scope: Node) {
|
||||
private fun processPipe(source: Expression, segments: List<Expression>) {
|
||||
|
||||
val sourceArg = (source as? IFunctionCall)?.args?.firstOrNull()
|
||||
if(sourceArg!=null && segments.any { (it as IFunctionCall).args.firstOrNull() === sourceArg})
|
||||
@ -158,9 +161,9 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
|
||||
when (target) {
|
||||
is BuiltinFunctionPlaceholder -> {
|
||||
val func = BuiltinFunctions.getValue(target.name)
|
||||
if(func.parameters.size!=1)
|
||||
errors.err("can only use unary function", funccall.position)
|
||||
else if(!func.hasReturn && funccall !== segments.last())
|
||||
if(func.parameters.isEmpty())
|
||||
errors.err("function must have at least one parameter", funccall.position)
|
||||
else if(func.returnType==null && funccall !== segments.last())
|
||||
errors.err("function must return a single value", funccall.position)
|
||||
|
||||
val paramDts = func.parameters.firstOrNull()?.possibleDatatypes
|
||||
@ -168,15 +171,12 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
|
||||
errors.err("pipe value datatype $valueDt incompatible with function argument ${paramDts.toList()}", funccall.position)
|
||||
|
||||
if(errors.noErrors()) {
|
||||
// type can depend on the argument(s) of the function. For now, we only deal with unary functions,
|
||||
// so we know there must be a single argument. Take its type from the previous expression in the pipe chain.
|
||||
val zero = defaultZero(valueDt, funccall.position)
|
||||
valueDt = builtinFunctionReturnType(func.name, listOf(zero), program).getOrElse { DataType.UNDEFINED }
|
||||
valueDt = builtinFunctionReturnType(func.name).getOrElse { DataType.UNDEFINED }
|
||||
}
|
||||
}
|
||||
is Subroutine -> {
|
||||
if(target.parameters.size!=1)
|
||||
errors.err("can only use unary function", funccall.position)
|
||||
if(target.parameters.isEmpty())
|
||||
errors.err("function must have at least one parameter", funccall.position)
|
||||
else if(target.returntypes.size!=1 && funccall !== segments.last())
|
||||
errors.err("function must return a single value", funccall.position)
|
||||
|
||||
|
@ -11,10 +11,10 @@ import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import prog8.ast.Program
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.compiler.ModuleImporter
|
||||
import prog8.parser.ParseError
|
||||
import prog8.code.core.SourceCode
|
||||
import prog8.code.core.internedStringsModuleName
|
||||
import prog8.compiler.ModuleImporter
|
||||
import prog8.parser.ParseError
|
||||
import prog8tests.helpers.*
|
||||
import kotlin.io.path.*
|
||||
|
||||
@ -39,9 +39,9 @@ class TestModuleImporter: FunSpec({
|
||||
|
||||
context("WithInvalidPath") {
|
||||
test("testNonexisting") {
|
||||
val dirRel = assumeDirectory(".", workingDir.relativize(fixturesDir))
|
||||
val dirRel = Helpers.assumeDirectory(".", Helpers.workingDir.relativize(Helpers.fixturesDir))
|
||||
val importer = makeImporter(null, dirRel.invariantSeparatorsPathString)
|
||||
val srcPathRel = assumeNotExists(dirRel, "i_do_not_exist")
|
||||
val srcPathRel = Helpers.assumeNotExists(dirRel, "i_do_not_exist")
|
||||
val srcPathAbs = srcPathRel.absolute()
|
||||
val error1 = importer.importModule(srcPathRel).getErrorOrElse { fail("should have import error") }
|
||||
withClue(".file should be normalized") {
|
||||
@ -62,7 +62,7 @@ class TestModuleImporter: FunSpec({
|
||||
}
|
||||
|
||||
test("testDirectory") {
|
||||
val srcPathRel = assumeDirectory(workingDir.relativize(fixturesDir))
|
||||
val srcPathRel = Helpers.assumeDirectory(Helpers.workingDir.relativize(Helpers.fixturesDir))
|
||||
val srcPathAbs = srcPathRel.absolute()
|
||||
val searchIn = Path(".", "$srcPathRel").invariantSeparatorsPathString
|
||||
val importer = makeImporter(null, searchIn)
|
||||
@ -95,11 +95,11 @@ class TestModuleImporter: FunSpec({
|
||||
|
||||
test("testAbsolute") {
|
||||
val searchIn = listOf(
|
||||
Path(".").div(workingDir.relativize(fixturesDir)), // we do want a dot "." in front
|
||||
Path(".").div(Helpers.workingDir.relativize(Helpers.fixturesDir)), // we do want a dot "." in front
|
||||
).map { it.invariantSeparatorsPathString }
|
||||
val importer = makeImporter(null, searchIn)
|
||||
val fileName = "ast_simple_main.p8"
|
||||
val path = assumeReadableFile(searchIn[0], fileName)
|
||||
val path = Helpers.assumeReadableFile(searchIn[0], fileName)
|
||||
|
||||
val module = importer.importModule(path.absolute()).getOrElse { throw it }
|
||||
program.modules.size shouldBe 2
|
||||
@ -109,11 +109,11 @@ class TestModuleImporter: FunSpec({
|
||||
|
||||
test("testRelativeToWorkingDir") {
|
||||
val searchIn = listOf(
|
||||
Path(".").div(workingDir.relativize(fixturesDir)), // we do want a dot "." in front
|
||||
Path(".").div(Helpers.workingDir.relativize(Helpers.fixturesDir)), // we do want a dot "." in front
|
||||
).map { it.invariantSeparatorsPathString }
|
||||
val importer = makeImporter(null, searchIn)
|
||||
val fileName = "ast_simple_main.p8"
|
||||
val path = assumeReadableFile(searchIn[0], fileName)
|
||||
val path = Helpers.assumeReadableFile(searchIn[0], fileName)
|
||||
withClue("sanity check: path should NOT be absolute") {
|
||||
path.isAbsolute shouldBe false
|
||||
}
|
||||
@ -126,12 +126,12 @@ class TestModuleImporter: FunSpec({
|
||||
|
||||
test("testRelativeTo1stDirInSearchList") {
|
||||
val searchIn = Path(".")
|
||||
.div(workingDir.relativize(fixturesDir))
|
||||
.div(Helpers.workingDir.relativize(Helpers.fixturesDir))
|
||||
.invariantSeparatorsPathString
|
||||
val importer = makeImporter(null, searchIn)
|
||||
val fileName = "ast_simple_main.p8"
|
||||
val path = Path(".", fileName)
|
||||
assumeReadableFile(searchIn, path)
|
||||
Helpers.assumeReadableFile(searchIn, path)
|
||||
|
||||
val module = importer.importModule(path).getOrElse { throw it }
|
||||
program.modules.size shouldBe 2
|
||||
@ -141,14 +141,14 @@ class TestModuleImporter: FunSpec({
|
||||
|
||||
context("WithBadFile") {
|
||||
test("testWithSyntaxError") {
|
||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||
val searchIn = Helpers.assumeDirectory("./", Helpers.workingDir.relativize(Helpers.fixturesDir))
|
||||
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
||||
val srcPath = assumeReadableFile(fixturesDir, "ast_file_with_syntax_error.p8")
|
||||
val srcPath = Helpers.assumeReadableFile(Helpers.fixturesDir, "ast_file_with_syntax_error.p8")
|
||||
|
||||
val act = { importer.importModule(srcPath) }
|
||||
|
||||
repeat(2) { n -> withClue(count[n] + " call") {
|
||||
shouldThrow<ParseError>() { act() }.let {
|
||||
shouldThrow<ParseError> { act() }.let {
|
||||
it.position.file shouldBe SourceCode.relative(srcPath).toString()
|
||||
withClue("line; should be 1-based") { it.position.line shouldBe 2 }
|
||||
withClue("startCol; should be 0-based") { it.position.startCol shouldBe 6 }
|
||||
@ -160,10 +160,10 @@ class TestModuleImporter: FunSpec({
|
||||
}
|
||||
|
||||
fun doTestImportingFileWithSyntaxError(repetitions: Int) {
|
||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||
val searchIn = Helpers.assumeDirectory("./", Helpers.workingDir.relativize(Helpers.fixturesDir))
|
||||
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
||||
val importing = assumeReadableFile(fixturesDir, "import_file_with_syntax_error.p8")
|
||||
val imported = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
|
||||
val importing = Helpers.assumeReadableFile(Helpers.fixturesDir, "import_file_with_syntax_error.p8")
|
||||
val imported = Helpers.assumeReadableFile(Helpers.fixturesDir, "file_with_syntax_error.p8")
|
||||
|
||||
val act = { importer.importModule(importing) }
|
||||
|
||||
@ -194,11 +194,11 @@ class TestModuleImporter: FunSpec({
|
||||
context("ImportLibraryModule") {
|
||||
context("WithInvalidName") {
|
||||
test("testWithNonExistingName") {
|
||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||
val searchIn = Helpers.assumeDirectory("./", Helpers.workingDir.relativize(Helpers.fixturesDir))
|
||||
val errors = ErrorReporterForTests(false)
|
||||
val importer = makeImporter(errors, searchIn.invariantSeparatorsPathString)
|
||||
val filenameNoExt = assumeNotExists(fixturesDir, "i_do_not_exist").name
|
||||
val filenameWithExt = assumeNotExists(fixturesDir, "i_do_not_exist.p8").name
|
||||
val filenameNoExt = Helpers.assumeNotExists(Helpers.fixturesDir, "i_do_not_exist").name
|
||||
val filenameWithExt = Helpers.assumeNotExists(Helpers.fixturesDir, "i_do_not_exist.p8").name
|
||||
|
||||
repeat(2) { n ->
|
||||
val result = importer.importLibraryModule(filenameNoExt)
|
||||
@ -221,9 +221,9 @@ class TestModuleImporter: FunSpec({
|
||||
context("WithValidName") {
|
||||
context("WithBadFile") {
|
||||
test("testWithSyntaxError") {
|
||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||
val searchIn = Helpers.assumeDirectory("./", Helpers.workingDir.relativize(Helpers.fixturesDir))
|
||||
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
||||
val srcPath = assumeReadableFile(fixturesDir, "ast_file_with_syntax_error.p8")
|
||||
val srcPath = Helpers.assumeReadableFile(Helpers.fixturesDir, "ast_file_with_syntax_error.p8")
|
||||
|
||||
repeat(2) { n -> withClue(count[n] + " call") {
|
||||
shouldThrow<ParseError>()
|
||||
@ -241,15 +241,15 @@ class TestModuleImporter: FunSpec({
|
||||
|
||||
|
||||
fun doTestImportingFileWithSyntaxError(repetitions: Int) {
|
||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||
val searchIn = Helpers.assumeDirectory("./", Helpers.workingDir.relativize(Helpers.fixturesDir))
|
||||
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
||||
val importing = assumeReadableFile(fixturesDir, "import_file_with_syntax_error.p8")
|
||||
val imported = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
|
||||
val importing = Helpers.assumeReadableFile(Helpers.fixturesDir, "import_file_with_syntax_error.p8")
|
||||
val imported = Helpers.assumeReadableFile(Helpers.fixturesDir, "file_with_syntax_error.p8")
|
||||
|
||||
val act = { importer.importLibraryModule(importing.nameWithoutExtension) }
|
||||
|
||||
repeat(repetitions) { n -> withClue(count[n] + " call") {
|
||||
shouldThrow<ParseError>() {
|
||||
shouldThrow<ParseError> {
|
||||
act() }.let {
|
||||
it.position.file shouldBe SourceCode.relative(imported).toString()
|
||||
withClue("line; should be 1-based") { it.position.line shouldBe 2 }
|
||||
|
@ -10,21 +10,20 @@ import prog8.compiler.BuiltinFunctions
|
||||
class TestBuiltinFunctions: FunSpec({
|
||||
|
||||
test("pure func with fixed type") {
|
||||
val func = BuiltinFunctions.getValue("sin8u")
|
||||
func.name shouldBe "sin8u"
|
||||
val func = BuiltinFunctions.getValue("sgn")
|
||||
func.name shouldBe "sgn"
|
||||
func.parameters.size shouldBe 1
|
||||
func.parameters[0].name shouldBe "angle8"
|
||||
func.parameters[0].possibleDatatypes shouldBe arrayOf(DataType.UBYTE)
|
||||
func.parameters[0].name shouldBe "value"
|
||||
func.parameters[0].possibleDatatypes shouldBe NumericDatatypes
|
||||
func.pure shouldBe true
|
||||
func.hasReturn shouldBe true
|
||||
func.returnType shouldBe DataType.UBYTE
|
||||
func.returnType shouldBe DataType.BYTE
|
||||
|
||||
val conv = func.callConvention(listOf(DataType.UBYTE))
|
||||
conv.params.size shouldBe 1
|
||||
conv.params[0].dt shouldBe DataType.UBYTE
|
||||
conv.params[0].reg shouldBe RegisterOrPair.A
|
||||
conv.params[0].variable shouldBe false
|
||||
conv.returns.dt shouldBe DataType.UBYTE
|
||||
conv.returns.dt shouldBe DataType.BYTE
|
||||
conv.returns.floatFac1 shouldBe false
|
||||
conv.returns.reg shouldBe RegisterOrPair.A
|
||||
}
|
||||
@ -34,7 +33,6 @@ class TestBuiltinFunctions: FunSpec({
|
||||
func.name shouldBe "rnd"
|
||||
func.parameters.size shouldBe 0
|
||||
func.pure shouldBe false
|
||||
func.hasReturn shouldBe true
|
||||
func.returnType shouldBe DataType.UBYTE
|
||||
|
||||
val conv = func.callConvention(emptyList())
|
||||
@ -53,7 +51,6 @@ class TestBuiltinFunctions: FunSpec({
|
||||
func.parameters[1].name shouldBe "value"
|
||||
func.parameters[1].possibleDatatypes shouldBe arrayOf(DataType.UBYTE)
|
||||
func.pure shouldBe false
|
||||
func.hasReturn shouldBe false
|
||||
func.returnType shouldBe null
|
||||
|
||||
val conv = func.callConvention(listOf(DataType.UWORD, DataType.UBYTE))
|
||||
@ -68,25 +65,5 @@ class TestBuiltinFunctions: FunSpec({
|
||||
conv.returns.floatFac1 shouldBe false
|
||||
conv.returns.reg shouldBe null
|
||||
}
|
||||
|
||||
test("func with variable return type") {
|
||||
val func = BuiltinFunctions.getValue("abs")
|
||||
func.name shouldBe "abs"
|
||||
func.parameters.size shouldBe 1
|
||||
func.parameters[0].name shouldBe "value"
|
||||
func.parameters[0].possibleDatatypes.toSet() shouldBe NumericDatatypes.toSet()
|
||||
func.pure shouldBe true
|
||||
func.hasReturn shouldBe true
|
||||
func.returnType shouldBe null
|
||||
|
||||
val conv = func.callConvention(listOf(DataType.UWORD))
|
||||
conv.params.size shouldBe 1
|
||||
conv.params[0].dt shouldBe DataType.UWORD
|
||||
conv.params[0].reg shouldBe RegisterOrPair.AY
|
||||
conv.params[0].variable shouldBe false
|
||||
conv.returns.dt shouldBe DataType.UWORD
|
||||
conv.returns.floatFac1 shouldBe false
|
||||
conv.returns.reg shouldBe RegisterOrPair.AY
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -9,10 +9,10 @@ import io.kotest.matchers.string.shouldContain
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.statements.Block
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.code.core.SourceCode
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.compiler.CallGraph
|
||||
import prog8.parser.Prog8Parser.parseModule
|
||||
import prog8.code.core.SourceCode
|
||||
import prog8tests.helpers.*
|
||||
|
||||
class TestCallgraph: FunSpec({
|
||||
|
@ -8,10 +8,8 @@ import prog8.code.target.Cx16Target
|
||||
import prog8.compiler.CompilationResult
|
||||
import prog8.compiler.CompilerArguments
|
||||
import prog8.compiler.compileProgram
|
||||
import prog8tests.helpers.assumeDirectory
|
||||
import prog8tests.helpers.cartesianProduct
|
||||
import prog8tests.helpers.outputDir
|
||||
import prog8tests.helpers.workingDir
|
||||
import prog8tests.helpers.Combinations
|
||||
import prog8tests.helpers.Helpers
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.absolute
|
||||
import kotlin.io.path.exists
|
||||
@ -23,7 +21,7 @@ import kotlin.io.path.exists
|
||||
* from source file loading all the way through to running 64tass.
|
||||
*/
|
||||
|
||||
private val examplesDir = assumeDirectory(workingDir, "../examples")
|
||||
private val examplesDir = Helpers.assumeDirectory(Helpers.workingDir, "../examples")
|
||||
|
||||
private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilationTarget): CompilationResult? {
|
||||
val args = CompilerArguments(
|
||||
@ -37,7 +35,7 @@ private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilat
|
||||
asmListfile = false,
|
||||
experimentalCodegen = false,
|
||||
compilationTarget = target.name,
|
||||
outputDir = outputDir
|
||||
outputDir = Helpers.outputDir
|
||||
)
|
||||
return compileProgram(args)
|
||||
}
|
||||
@ -45,12 +43,12 @@ private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilat
|
||||
private fun prepareTestFiles(source: String, optimize: Boolean, target: ICompilationTarget): Pair<String, Path> {
|
||||
val searchIn = mutableListOf(examplesDir)
|
||||
if (target is Cx16Target) {
|
||||
searchIn.add(0, assumeDirectory(examplesDir, "cx16"))
|
||||
searchIn.add(0, Helpers.assumeDirectory(examplesDir, "cx16"))
|
||||
}
|
||||
val filepath = searchIn.asSequence()
|
||||
.map { it.resolve("$source.p8") }
|
||||
.map { it.normalize().absolute() }
|
||||
.map { workingDir.relativize(it) }
|
||||
.map { Helpers.workingDir.relativize(it) }
|
||||
.first { it.exists() }
|
||||
val displayName = "${examplesDir.relativize(filepath.absolute())}: ${target.name}, optimize=$optimize"
|
||||
return Pair(displayName, filepath)
|
||||
@ -59,7 +57,7 @@ private fun prepareTestFiles(source: String, optimize: Boolean, target: ICompila
|
||||
|
||||
class TestCompilerOnExamplesC64: FunSpec({
|
||||
|
||||
val onlyC64 = cartesianProduct(
|
||||
val onlyC64 = Combinations.cartesianProduct(
|
||||
listOf(
|
||||
"balloonflight",
|
||||
"bdmusic",
|
||||
@ -86,7 +84,7 @@ class TestCompilerOnExamplesC64: FunSpec({
|
||||
|
||||
class TestCompilerOnExamplesCx16: FunSpec({
|
||||
|
||||
val onlyCx16 = cartesianProduct(
|
||||
val onlyCx16 = Combinations.cartesianProduct(
|
||||
listOf(
|
||||
"vtui/testvtui",
|
||||
"amiga",
|
||||
@ -118,7 +116,7 @@ class TestCompilerOnExamplesCx16: FunSpec({
|
||||
|
||||
class TestCompilerOnExamplesBothC64andCx16: FunSpec({
|
||||
|
||||
val bothCx16AndC64 = cartesianProduct(
|
||||
val bothCx16AndC64 = Combinations.cartesianProduct(
|
||||
listOf(
|
||||
"animals",
|
||||
"balls",
|
||||
|
@ -10,7 +10,8 @@ import prog8.ast.expressions.StringLiteral
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
import prog8.ast.statements.Label
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8tests.helpers.*
|
||||
import prog8tests.helpers.Helpers
|
||||
import prog8tests.helpers.compileFile
|
||||
import kotlin.io.path.name
|
||||
|
||||
|
||||
@ -24,11 +25,11 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
|
||||
context("Import") {
|
||||
|
||||
test("testImportFromSameFolder") {
|
||||
val filepath = assumeReadableFile(fixturesDir, "importFromSameFolder.p8")
|
||||
assumeReadableFile(fixturesDir, "foo_bar.p8")
|
||||
val filepath = Helpers.assumeReadableFile(Helpers.fixturesDir, "importFromSameFolder.p8")
|
||||
Helpers.assumeReadableFile(Helpers.fixturesDir, "foo_bar.p8")
|
||||
|
||||
val platform = Cx16Target()
|
||||
val result = compileFile(platform, optimize = false, fixturesDir, filepath.name)!!
|
||||
val result = compileFile(platform, optimize = false, Helpers.fixturesDir, filepath.name)!!
|
||||
|
||||
val program = result.program
|
||||
val startSub = program.entrypoint
|
||||
@ -46,11 +47,11 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
|
||||
|
||||
context("AsmInclude") {
|
||||
test("testAsmIncludeFromSameFolder") {
|
||||
val filepath = assumeReadableFile(fixturesDir, "asmIncludeFromSameFolder.p8")
|
||||
assumeReadableFile(fixturesDir, "foo_bar.asm")
|
||||
val filepath = Helpers.assumeReadableFile(Helpers.fixturesDir, "asmIncludeFromSameFolder.p8")
|
||||
Helpers.assumeReadableFile(Helpers.fixturesDir, "foo_bar.asm")
|
||||
|
||||
val platform = Cx16Target()
|
||||
val result = compileFile(platform, optimize = false, fixturesDir, filepath.name)!!
|
||||
val result = compileFile(platform, optimize = false, Helpers.fixturesDir, filepath.name)!!
|
||||
|
||||
val program = result.program
|
||||
val startSub = program.entrypoint
|
||||
@ -71,17 +72,17 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
|
||||
|
||||
context("Asmbinary") {
|
||||
test("testAsmbinaryDirectiveWithNonExistingFile") {
|
||||
val p8Path = assumeReadableFile(fixturesDir, "asmBinaryNonExisting.p8")
|
||||
assumeNotExists(fixturesDir, "i_do_not_exist.bin")
|
||||
val p8Path = Helpers.assumeReadableFile(Helpers.fixturesDir, "asmBinaryNonExisting.p8")
|
||||
Helpers.assumeNotExists(Helpers.fixturesDir, "i_do_not_exist.bin")
|
||||
|
||||
compileFile(Cx16Target(), false, p8Path.parent, p8Path.name, outputDir) shouldBe null
|
||||
compileFile(Cx16Target(), false, p8Path.parent, p8Path.name, Helpers.outputDir) shouldBe null
|
||||
}
|
||||
|
||||
test("testAsmbinaryDirectiveWithNonReadableFile") {
|
||||
val p8Path = assumeReadableFile(fixturesDir, "asmBinaryNonReadable.p8")
|
||||
assumeDirectory(fixturesDir, "subFolder")
|
||||
val p8Path = Helpers.assumeReadableFile(Helpers.fixturesDir, "asmBinaryNonReadable.p8")
|
||||
Helpers.assumeDirectory(Helpers.fixturesDir, "subFolder")
|
||||
|
||||
compileFile(Cx16Target(), false, p8Path.parent, p8Path.name, outputDir) shouldBe null
|
||||
compileFile(Cx16Target(), false, p8Path.parent, p8Path.name, Helpers.outputDir) shouldBe null
|
||||
}
|
||||
|
||||
val tests = listOf(
|
||||
@ -92,18 +93,18 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
|
||||
tests.forEach {
|
||||
val (where, p8Str, _) = it
|
||||
test("%asmbinary from ${where}folder") {
|
||||
val p8Path = assumeReadableFile(fixturesDir, p8Str)
|
||||
// val binPath = assumeReadableFile(fixturesDir, binStr)
|
||||
val p8Path = Helpers.assumeReadableFile(Helpers.fixturesDir, p8Str)
|
||||
// val binPath = Helpers.assumeReadableFile(Helpers.fixturesDir, binStr)
|
||||
|
||||
// the bug we're testing for (#54) was hidden if outputDir == workingDir
|
||||
withClue("sanity check: workingDir and outputDir should not be the same folder") {
|
||||
outputDir.normalize().toAbsolutePath() shouldNotBe workingDir.normalize().toAbsolutePath()
|
||||
Helpers.outputDir.normalize().toAbsolutePath() shouldNotBe Helpers.workingDir.normalize().toAbsolutePath()
|
||||
}
|
||||
|
||||
withClue("argument to assembler directive .binary " +
|
||||
"should be relative to the generated .asm file (in output dir), " +
|
||||
"NOT relative to .p8 neither current working dir") {
|
||||
compileFile(Cx16Target(), false, p8Path.parent, p8Path.name, outputDir) shouldNotBe null
|
||||
compileFile(Cx16Target(), false, p8Path.parent, p8Path.name, Helpers.outputDir) shouldNotBe null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,8 +17,8 @@ import prog8.code.core.Encoding
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8tests.helpers.Combinations
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.cartesianProduct
|
||||
import prog8tests.helpers.compileText
|
||||
|
||||
|
||||
@ -93,7 +93,7 @@ class TestCompilerOnRanges: FunSpec({
|
||||
}
|
||||
|
||||
context("floatArrayInitializerWithRange") {
|
||||
val combos = cartesianProduct(
|
||||
val combos = Combinations.cartesianProduct(
|
||||
listOf("", "42", "41"), // sizeInDecl
|
||||
listOf("%import floats", ""), // optEnableFloats
|
||||
listOf(Cx16Target(), C64Target()), // platform
|
||||
|
@ -6,10 +6,7 @@ import prog8.code.target.Cx16Target
|
||||
import prog8.compiler.CompilationResult
|
||||
import prog8.compiler.CompilerArguments
|
||||
import prog8.compiler.compileProgram
|
||||
import prog8tests.helpers.assumeReadableFile
|
||||
import prog8tests.helpers.fixturesDir
|
||||
import prog8tests.helpers.outputDir
|
||||
import prog8tests.helpers.workingDir
|
||||
import prog8tests.helpers.Helpers
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.absolute
|
||||
import kotlin.io.path.createTempFile
|
||||
@ -26,7 +23,7 @@ class TestCompilerOptionSourcedirs: FunSpec({
|
||||
lateinit var tempFileInWorkingDir: Path
|
||||
|
||||
beforeSpec {
|
||||
tempFileInWorkingDir = createTempFile(directory = workingDir, prefix = "tmp_", suffix = ".p8")
|
||||
tempFileInWorkingDir = createTempFile(directory = Helpers.workingDir, prefix = "tmp_", suffix = ".p8")
|
||||
.also { it.writeText("""
|
||||
main {
|
||||
sub start() {
|
||||
@ -52,39 +49,39 @@ class TestCompilerOptionSourcedirs: FunSpec({
|
||||
experimentalCodegen = false,
|
||||
compilationTarget = Cx16Target.NAME,
|
||||
sourceDirs,
|
||||
outputDir
|
||||
Helpers.outputDir
|
||||
)
|
||||
return compileProgram(args)
|
||||
}
|
||||
|
||||
test("testAbsoluteFilePathInWorkingDir") {
|
||||
val filepath = assumeReadableFile(tempFileInWorkingDir.absolute())
|
||||
val filepath = Helpers.assumeReadableFile(tempFileInWorkingDir.absolute())
|
||||
compileFile(filepath, listOf()) shouldNotBe null
|
||||
}
|
||||
|
||||
test("testFilePathInWorkingDirRelativeToWorkingDir") {
|
||||
val filepath = assumeReadableFile(workingDir.relativize(tempFileInWorkingDir.absolute()))
|
||||
val filepath = Helpers.assumeReadableFile(Helpers.workingDir.relativize(tempFileInWorkingDir.absolute()))
|
||||
compileFile(filepath, listOf()) shouldNotBe null
|
||||
}
|
||||
|
||||
test("testFilePathInWorkingDirRelativeTo1stInSourcedirs") {
|
||||
val filepath = assumeReadableFile(tempFileInWorkingDir)
|
||||
compileFile(filepath.fileName, listOf(workingDir.toString())) shouldNotBe null
|
||||
val filepath = Helpers.assumeReadableFile(tempFileInWorkingDir)
|
||||
compileFile(filepath.fileName, listOf(Helpers.workingDir.toString())) shouldNotBe null
|
||||
}
|
||||
|
||||
test("testAbsoluteFilePathOutsideWorkingDir") {
|
||||
val filepath = assumeReadableFile(fixturesDir, "ast_simple_main.p8")
|
||||
val filepath = Helpers.assumeReadableFile(Helpers.fixturesDir, "ast_simple_main.p8")
|
||||
compileFile(filepath.absolute(), listOf()) shouldNotBe null
|
||||
}
|
||||
|
||||
test("testFilePathOutsideWorkingDirRelativeToWorkingDir") {
|
||||
val filepath = workingDir.relativize(assumeReadableFile(fixturesDir, "ast_simple_main.p8").absolute())
|
||||
val filepath = Helpers.workingDir.relativize(Helpers.assumeReadableFile(Helpers.fixturesDir, "ast_simple_main.p8").absolute())
|
||||
compileFile(filepath, listOf()) shouldNotBe null
|
||||
}
|
||||
|
||||
test("testFilePathOutsideWorkingDirRelativeTo1stInSourcedirs") {
|
||||
val filepath = assumeReadableFile(fixturesDir, "ast_simple_main.p8")
|
||||
val sourcedirs = listOf("$fixturesDir")
|
||||
val filepath = Helpers.assumeReadableFile(Helpers.fixturesDir, "ast_simple_main.p8")
|
||||
val sourcedirs = listOf("${Helpers.fixturesDir}")
|
||||
compileFile(filepath.fileName, sourcedirs) shouldNotBe null
|
||||
}
|
||||
|
||||
|
@ -10,8 +10,8 @@ import prog8.code.target.C64Target
|
||||
import prog8.compiler.determineCompilationOptions
|
||||
import prog8.compiler.parseImports
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.Helpers
|
||||
import prog8tests.helpers.compileText
|
||||
import prog8tests.helpers.outputDir
|
||||
|
||||
|
||||
class TestImportedModulesOrderAndOptions: FunSpec({
|
||||
@ -41,6 +41,7 @@ main {
|
||||
"syslib",
|
||||
"conv",
|
||||
"floats",
|
||||
"floats_functions",
|
||||
"math",
|
||||
"prog8_lib"
|
||||
)
|
||||
@ -84,7 +85,7 @@ main {
|
||||
}
|
||||
"""
|
||||
val filenameBase = "on_the_fly_test_" + sourceText.hashCode().toUInt().toString(16)
|
||||
val filepath = outputDir.resolve("$filenameBase.p8")
|
||||
val filepath = Helpers.outputDir.resolve("$filenameBase.p8")
|
||||
filepath.toFile().writeText(sourceText)
|
||||
val (program, options, importedfiles) = parseImports(filepath, errors, C64Target(), emptyList())
|
||||
|
||||
@ -97,7 +98,7 @@ main {
|
||||
listOf(
|
||||
internedStringsModuleName,
|
||||
filenameBase,
|
||||
"textio", "syslib", "conv", "floats", "math", "prog8_lib"
|
||||
"textio", "syslib", "conv", "floats", "floats_functions", "math", "prog8_lib"
|
||||
)
|
||||
}
|
||||
options.floats shouldBe true
|
||||
|
@ -12,10 +12,10 @@ import prog8.ast.expressions.PrefixExpression
|
||||
import prog8.ast.statements.*
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.core.SourceCode
|
||||
import prog8.code.core.ZeropageWish
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.compiler.printProgram
|
||||
import prog8.code.core.SourceCode
|
||||
import prog8tests.helpers.DummyFunctions
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import prog8tests.helpers.DummyStringEncoder
|
||||
|
@ -7,6 +7,7 @@ import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import io.kotest.matchers.string.shouldNotBeBlank
|
||||
import io.kotest.matchers.string.shouldStartWith
|
||||
import io.kotest.matchers.types.instanceOf
|
||||
import io.kotest.matchers.types.shouldBeSameInstanceAs
|
||||
import prog8.ast.ParentSentinel
|
||||
@ -169,7 +170,6 @@ class TestOptimization: FunSpec({
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target(), true, source, writeAssembly = false)!!
|
||||
printProgram(result.program)
|
||||
// expected:
|
||||
// word llw
|
||||
// llw = 300
|
||||
@ -253,7 +253,7 @@ class TestOptimization: FunSpec({
|
||||
(initY2.value as NumericLiteral).number shouldBe 11.0
|
||||
}
|
||||
|
||||
test("typecasted assignment from ubyte logical expressoin to uword var") {
|
||||
test("not-typecasted assignment from ubyte logical expression to uword var") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
@ -265,13 +265,11 @@ class TestOptimization: FunSpec({
|
||||
"""
|
||||
val result = compileText(C64Target(), false, src, writeAssembly = false)!!
|
||||
|
||||
// ww = ((( not bb as uword) or not ww) as uword)
|
||||
val wwAssign = result.program.entrypoint.statements.last() as Assignment
|
||||
val expr = wwAssign.value as TypecastExpression
|
||||
val expr = wwAssign.value as BinaryExpression
|
||||
|
||||
wwAssign.target.identifier?.nameInSource shouldBe listOf("ww")
|
||||
expr.type shouldBe DataType.UWORD
|
||||
expr.expression.inferType(result.program) istype DataType.UBYTE shouldBe true
|
||||
expr.inferType(result.program) istype DataType.UWORD shouldBe true
|
||||
}
|
||||
|
||||
test("intermediate assignment steps have correct types for codegen phase (BeforeAsmGenerationAstChanger)") {
|
||||
@ -300,7 +298,7 @@ class TestOptimization: FunSpec({
|
||||
floats = false,
|
||||
noSysInit = true,
|
||||
compTarget = C64Target(),
|
||||
loadAddress = 0u, outputDir= outputDir)
|
||||
loadAddress = 0u, outputDir= Helpers.outputDir)
|
||||
result.program.processAstBeforeAsmGeneration(options, ErrorReporterForTests())
|
||||
|
||||
// assignment is now split into:
|
||||
@ -336,7 +334,7 @@ class TestOptimization: FunSpec({
|
||||
main {
|
||||
sub start() {
|
||||
ubyte r
|
||||
ubyte @shared bb = (cos8(r)/2 + 100) as ubyte
|
||||
ubyte @shared bb = (sgn(r)*2 + 100) as ubyte
|
||||
}
|
||||
}
|
||||
"""
|
||||
@ -345,8 +343,8 @@ class TestOptimization: FunSpec({
|
||||
ubyte r
|
||||
r = 0
|
||||
ubyte bb
|
||||
prog8_lib.retval_interm_b = cos8(r)
|
||||
prog8_lib.retval_interm_b >>= 1
|
||||
prog8_lib.retval_interm_b = sgn(r)
|
||||
prog8_lib.retval_interm_b <<= 1
|
||||
prog8_lib.retval_interm_b += 100
|
||||
bb = prog8_lib.retval_interm_b
|
||||
return
|
||||
@ -426,7 +424,6 @@ class TestOptimization: FunSpec({
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!!
|
||||
printProgram(result.program)
|
||||
result.program.entrypoint.statements.size shouldBe 3
|
||||
val ifstmt = result.program.entrypoint.statements[0] as IfElse
|
||||
ifstmt.truepart.statements.size shouldBe 1
|
||||
@ -606,12 +603,11 @@ class TestOptimization: FunSpec({
|
||||
uword @shared zz
|
||||
zz += 60 ; NOT ok to remove initializer, should evaluate to 60
|
||||
ubyte @shared xx
|
||||
xx = 6+sin8u(xx) ; is not an initializer because it references xx
|
||||
xx = 6+lsb(abs(xx)) ; is not an initializer because it references xx
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!!
|
||||
printProgram(result.program)
|
||||
/* expected result:
|
||||
uword yy
|
||||
yy = 20
|
||||
@ -619,7 +615,7 @@ class TestOptimization: FunSpec({
|
||||
zz = 60
|
||||
ubyte xx
|
||||
xx = 0
|
||||
xx = sin8u(xx)
|
||||
xx = abs(xx)
|
||||
xx += 6
|
||||
*/
|
||||
val stmts = result.program.entrypoint.statements
|
||||
@ -651,8 +647,6 @@ class TestOptimization: FunSpec({
|
||||
yy = 0
|
||||
xx += 10
|
||||
*/
|
||||
printProgram(result.program)
|
||||
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBe 7
|
||||
stmts.filterIsInstance<VarDecl>().size shouldBe 2
|
||||
@ -667,4 +661,100 @@ class TestOptimization: FunSpec({
|
||||
(xxValue.left as? IdentifierReference)?.nameInSource shouldBe listOf("xx")
|
||||
xxValue.right shouldBe NumericLiteral(DataType.UBYTE, 10.0, Position.DUMMY)
|
||||
}
|
||||
|
||||
test("multi-comparison replaced by containment check") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
ubyte source=99
|
||||
ubyte thingy=42
|
||||
|
||||
if source==3 or source==4 or source==99 or source==1
|
||||
thingy++
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!!
|
||||
/*
|
||||
expected result:
|
||||
ubyte[] auto_heap_var = [1,4,99,3]
|
||||
ubyte source
|
||||
source = 99
|
||||
ubyte thingy
|
||||
thingy = 42
|
||||
if source in auto_heap_var
|
||||
thingy++
|
||||
*/
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBe 6
|
||||
val ifStmt = stmts[5] as IfElse
|
||||
val containment = ifStmt.condition as ContainmentCheck
|
||||
(containment.element as IdentifierReference).nameInSource shouldBe listOf("source")
|
||||
(containment.iterable as IdentifierReference).nameInSource.single() shouldStartWith "auto_heap_value"
|
||||
val arrayDecl = stmts[0] as VarDecl
|
||||
arrayDecl.isArray shouldBe true
|
||||
arrayDecl.arraysize?.constIndex() shouldBe 4
|
||||
val arrayValue = arrayDecl.value as ArrayLiteral
|
||||
arrayValue.type shouldBe InferredTypes.InferredType.known(DataType.ARRAY_UB)
|
||||
arrayValue.value shouldBe listOf(
|
||||
NumericLiteral.optimalInteger(1, Position.DUMMY),
|
||||
NumericLiteral.optimalInteger(3, Position.DUMMY),
|
||||
NumericLiteral.optimalInteger(4, Position.DUMMY),
|
||||
NumericLiteral.optimalInteger(99, Position.DUMMY))
|
||||
}
|
||||
|
||||
test("invalid multi-comparison (not all equals) not replaced") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
ubyte source=99
|
||||
ubyte thingy=42
|
||||
|
||||
if source==3 or source==4 or source!=99 or source==1
|
||||
thingy++
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!!
|
||||
printProgram(result.program)
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBe 5
|
||||
val ifStmt = stmts[4] as IfElse
|
||||
ifStmt.condition shouldBe instanceOf<BinaryExpression>()
|
||||
}
|
||||
|
||||
test("invalid multi-comparison (not all same needle) not replaced") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
ubyte source=99
|
||||
ubyte thingy=42
|
||||
|
||||
if source==3 or source==4 or thingy==99 or source==1
|
||||
thingy++
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!!
|
||||
printProgram(result.program)
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBe 5
|
||||
val ifStmt = stmts[4] as IfElse
|
||||
ifStmt.condition shouldBe instanceOf<BinaryExpression>()
|
||||
}
|
||||
|
||||
test("invalid multi-comparison (not all or) not replaced") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
ubyte source=99
|
||||
ubyte thingy=42
|
||||
|
||||
if source==3 or source==4 and source==99 or source==1
|
||||
thingy++
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!!
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBe 5
|
||||
val ifStmt = stmts[4] as IfElse
|
||||
ifStmt.condition shouldBe instanceOf<BinaryExpression>()
|
||||
}
|
||||
})
|
||||
|
@ -13,10 +13,11 @@ import prog8.ast.statements.Pipe
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.core.SourceCode
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.compiler.astprocessing.AstPreprocessor
|
||||
import prog8.parser.Prog8Parser.parseModule
|
||||
import prog8.code.core.SourceCode
|
||||
import prog8tests.helpers.*
|
||||
|
||||
|
||||
@ -133,7 +134,7 @@ class TestPipes: FunSpec({
|
||||
|
||||
9999 |> abs() |> txt.print_uw()
|
||||
9999 |> txt.print_uw()
|
||||
99 |> abs() |> txt.print_ub()
|
||||
99 |> abs() |> lsb() |> txt.print_ub()
|
||||
99 |> txt.print_ub()
|
||||
}
|
||||
|
||||
@ -267,8 +268,8 @@ class TestPipes: FunSpec({
|
||||
uword @shared ww = startvalue(99) |> addword()
|
||||
|> addword()
|
||||
|
||||
ubyte @shared cc = 30 |> sin8u() |> cos8u()
|
||||
cc = cc |> sin8u() |> cos8u()
|
||||
ubyte @shared cc = 30 |> abs() |> sqrt16()
|
||||
cc = cc |> abs() |> sqrt16()
|
||||
}
|
||||
|
||||
sub startvalue(ubyte arg) -> uword {
|
||||
@ -308,9 +309,9 @@ class TestPipes: FunSpec({
|
||||
value.source shouldBe NumericLiteral(DataType.UBYTE, 30.0, Position.DUMMY)
|
||||
value.segments.size shouldBe 2
|
||||
call = value.segments[0] as IFunctionCall
|
||||
call.target.nameInSource shouldBe listOf("sin8u")
|
||||
call.target.nameInSource shouldBe listOf("abs")
|
||||
call = value.segments[1] as IFunctionCall
|
||||
call.target.nameInSource shouldBe listOf("cos8u")
|
||||
call.target.nameInSource shouldBe listOf("sqrt16")
|
||||
|
||||
assigncc = stmts[6] as Assignment
|
||||
val pipecc = assigncc.value as PipeExpression
|
||||
@ -333,8 +334,8 @@ class TestPipes: FunSpec({
|
||||
uword @shared ww = startvalue(99) |> addword()
|
||||
|> addword()
|
||||
|
||||
ubyte @shared cc = 30 |> sin8u() |> cos8u() ; will be optimized away into a const number
|
||||
cc = cc |> sin8u() |> cos8u()
|
||||
ubyte @shared cc = 80 |> abs() |> sqrt16() ; will be optimized away into a const number
|
||||
cc = cc |> abs() |> sqrt16()
|
||||
}
|
||||
|
||||
sub startvalue(ubyte arg) -> uword {
|
||||
@ -366,16 +367,16 @@ class TestPipes: FunSpec({
|
||||
|
||||
var assigncc = stmts[5] as Assignment
|
||||
val value = assigncc.value as NumericLiteral
|
||||
value.number shouldBe 190.0
|
||||
value.number shouldBe 8.0
|
||||
|
||||
assigncc = stmts[6] as Assignment
|
||||
val pipecc = assigncc.value as PipeExpression
|
||||
pipecc.source shouldBe instanceOf<BuiltinFunctionCall>()
|
||||
(pipecc.source as BuiltinFunctionCall).target.nameInSource shouldBe listOf("sin8u")
|
||||
(pipecc.source as BuiltinFunctionCall).target.nameInSource shouldBe listOf("abs")
|
||||
|
||||
pipecc.segments.size shouldBe 1
|
||||
pipecc.segments[0] shouldBe instanceOf<BuiltinFunctionCall>()
|
||||
(pipecc.segments[0] as BuiltinFunctionCall).target.nameInSource shouldBe listOf("cos8u")
|
||||
(pipecc.segments[0] as BuiltinFunctionCall).target.nameInSource shouldBe listOf("sqrt16")
|
||||
}
|
||||
|
||||
test("incorrect type in pipe expression") {
|
||||
@ -411,7 +412,7 @@ class TestPipes: FunSpec({
|
||||
uword ww = 9999
|
||||
ubyte bb = 99
|
||||
ww |> abs() |> txt.print_uw()
|
||||
bb |> abs() |> txt.print_ub()
|
||||
bb |> abs() |> lsb() |> txt.print_ub()
|
||||
}
|
||||
}
|
||||
"""
|
||||
@ -425,8 +426,9 @@ class TestPipes: FunSpec({
|
||||
|
||||
val pipebb = stmts[5] as Pipe
|
||||
pipebb.source shouldBe instanceOf<BuiltinFunctionCall>()
|
||||
pipebb.segments.size shouldBe 1
|
||||
pipebb.segments.size shouldBe 2
|
||||
pipebb.segments[0] shouldBe instanceOf<IFunctionCall>()
|
||||
pipebb.segments[1] shouldBe instanceOf<IFunctionCall>()
|
||||
}
|
||||
|
||||
test("pipe statement with type errors") {
|
||||
@ -455,7 +457,7 @@ class TestPipes: FunSpec({
|
||||
uword ww = startvalue() |> addword()
|
||||
|> addword()
|
||||
|
||||
ubyte cc = 30 |> sin8u(99) |> cos8u(22)
|
||||
ubyte cc = 30 |> abs(99) |> sqrt16(22)
|
||||
}
|
||||
|
||||
sub startvalue(ubyte arg) -> uword {
|
||||
@ -469,7 +471,42 @@ class TestPipes: FunSpec({
|
||||
compileText(C64Target(), optimize = false, text, writeAssembly = false, errors=errors) shouldBe null
|
||||
errors.errors.size shouldBe 3
|
||||
errors.errors[0] shouldContain ":4:32: invalid number of arguments"
|
||||
errors.errors[1] shouldContain ":7:44: invalid number of arguments"
|
||||
errors.errors[2] shouldContain ":7:57: invalid number of arguments"
|
||||
errors.errors[1] shouldContain ":7:42: invalid number of arguments"
|
||||
errors.errors[2] shouldContain ":7:56: invalid number of arguments"
|
||||
}
|
||||
|
||||
test("non-unary funcions in pipe ok for target virtual") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
uword @shared wvalue = add(3,4) |> add(48) |> mkword(234)
|
||||
}
|
||||
sub add(ubyte first, ubyte second) -> ubyte {
|
||||
return first+second
|
||||
}
|
||||
}"""
|
||||
val errors = ErrorReporterForTests()
|
||||
val result = compileText(VMTarget(), optimize = false, text, writeAssembly = true, errors=errors)!!
|
||||
errors.errors.size shouldBe 0
|
||||
errors.warnings.size shouldBe 0
|
||||
result.program.entrypoint.statements.size shouldBe 3
|
||||
}
|
||||
|
||||
test("non-unary funcions in pipe not yet ok for other targets") {
|
||||
// NOTE: once other targets also support this, merge this into the test above
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
uword @shared wvalue = add(3,4) |> add(48) |> mkword(234)
|
||||
}
|
||||
sub add(ubyte first, ubyte second) -> ubyte {
|
||||
return first+second
|
||||
}
|
||||
}"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(C64Target(), optimize = false, text, writeAssembly = true, errors=errors) shouldBe null
|
||||
errors.errors.size shouldBe 2
|
||||
errors.errors[0] shouldContain "only unary"
|
||||
errors.errors[1] shouldContain "only unary"
|
||||
}
|
||||
})
|
||||
|
156
compiler/test/TestPtNumber.kt
Normal file
156
compiler/test/TestPtNumber.kt
Normal file
@ -0,0 +1,156 @@
|
||||
package prog8tests
|
||||
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import prog8.code.ast.PtArray
|
||||
import prog8.code.ast.PtNumber
|
||||
import prog8.code.ast.PtString
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.Encoding
|
||||
import prog8.code.core.Position
|
||||
|
||||
|
||||
class TestPtNumber: FunSpec({
|
||||
|
||||
fun sameValueAndType(lv1: PtNumber, lv2: PtNumber): Boolean {
|
||||
return lv1.type==lv2.type && lv1==lv2
|
||||
}
|
||||
|
||||
val dummyPos = Position("test", 0, 0, 0)
|
||||
|
||||
test("testIdentity") {
|
||||
val v = PtNumber(DataType.UWORD, 12345.0, dummyPos)
|
||||
(v==v) shouldBe true
|
||||
(v != v) shouldBe false
|
||||
(v <= v) shouldBe true
|
||||
(v >= v) shouldBe true
|
||||
(v < v ) shouldBe false
|
||||
(v > v ) shouldBe false
|
||||
|
||||
sameValueAndType(PtNumber(DataType.UWORD, 12345.0, dummyPos), PtNumber(DataType.UWORD, 12345.0, dummyPos)) shouldBe true
|
||||
}
|
||||
|
||||
test("test rounding") {
|
||||
shouldThrow<IllegalArgumentException> {
|
||||
PtNumber(DataType.BYTE, -2.345, dummyPos)
|
||||
}.message shouldContain "refused rounding"
|
||||
shouldThrow<IllegalArgumentException> {
|
||||
PtNumber(DataType.BYTE, -2.6, dummyPos)
|
||||
}.message shouldContain "refused rounding"
|
||||
shouldThrow<IllegalArgumentException> {
|
||||
PtNumber(DataType.UWORD, 2222.345, dummyPos)
|
||||
}.message shouldContain "refused rounding"
|
||||
PtNumber(DataType.UBYTE, 2.0, dummyPos).number shouldBe 2.0
|
||||
PtNumber(DataType.BYTE, -2.0, dummyPos).number shouldBe -2.0
|
||||
PtNumber(DataType.UWORD, 2222.0, dummyPos).number shouldBe 2222.0
|
||||
PtNumber(DataType.FLOAT, 123.456, dummyPos)
|
||||
}
|
||||
|
||||
test("testEqualsAndNotEquals") {
|
||||
(PtNumber(DataType.UBYTE, 100.0, dummyPos) == PtNumber(DataType.UBYTE, 100.0, dummyPos)) shouldBe true
|
||||
(PtNumber(DataType.UBYTE, 100.0, dummyPos) == PtNumber(DataType.UWORD, 100.0, dummyPos)) shouldBe true
|
||||
(PtNumber(DataType.UBYTE, 100.0, dummyPos) == PtNumber(DataType.FLOAT, 100.0, dummyPos)) shouldBe true
|
||||
(PtNumber(DataType.UWORD, 254.0, dummyPos) == PtNumber(DataType.UBYTE, 254.0, dummyPos)) shouldBe true
|
||||
(PtNumber(DataType.UWORD, 12345.0, dummyPos) == PtNumber(DataType.UWORD, 12345.0, dummyPos)) shouldBe true
|
||||
(PtNumber(DataType.UWORD, 12345.0, dummyPos) == PtNumber(DataType.FLOAT, 12345.0, dummyPos)) shouldBe true
|
||||
(PtNumber(DataType.FLOAT, 100.0, dummyPos) == PtNumber(DataType.UBYTE, 100.0, dummyPos)) shouldBe true
|
||||
(PtNumber(DataType.FLOAT, 22239.0, dummyPos) == PtNumber(DataType.UWORD, 22239.0, dummyPos)) shouldBe true
|
||||
(PtNumber(DataType.FLOAT, 9.99, dummyPos) == PtNumber(DataType.FLOAT, 9.99, dummyPos)) shouldBe true
|
||||
|
||||
sameValueAndType(PtNumber(DataType.UBYTE, 100.0, dummyPos), PtNumber(DataType.UBYTE, 100.0, dummyPos)) shouldBe true
|
||||
sameValueAndType(PtNumber(DataType.UBYTE, 100.0, dummyPos), PtNumber(DataType.UWORD, 100.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(PtNumber(DataType.UBYTE, 100.0, dummyPos), PtNumber(DataType.FLOAT, 100.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(PtNumber(DataType.UWORD, 254.0, dummyPos), PtNumber(DataType.UBYTE, 254.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(PtNumber(DataType.UWORD, 12345.0, dummyPos), PtNumber(DataType.UWORD, 12345.0, dummyPos)) shouldBe true
|
||||
sameValueAndType(PtNumber(DataType.UWORD, 12345.0, dummyPos), PtNumber(DataType.FLOAT, 12345.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(PtNumber(DataType.FLOAT, 100.0, dummyPos), PtNumber(DataType.UBYTE, 100.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(PtNumber(DataType.FLOAT, 22239.0, dummyPos), PtNumber(DataType.UWORD, 22239.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(PtNumber(DataType.FLOAT, 9.99, dummyPos), PtNumber(DataType.FLOAT, 9.99, dummyPos)) shouldBe true
|
||||
|
||||
(PtNumber(DataType.UBYTE, 100.0, dummyPos) != PtNumber(DataType.UBYTE, 101.0, dummyPos)) shouldBe true
|
||||
(PtNumber(DataType.UBYTE, 100.0, dummyPos) != PtNumber(DataType.UWORD, 101.0, dummyPos)) shouldBe true
|
||||
(PtNumber(DataType.UBYTE, 100.0, dummyPos) != PtNumber(DataType.FLOAT, 101.0, dummyPos)) shouldBe true
|
||||
(PtNumber(DataType.UWORD, 245.0, dummyPos) != PtNumber(DataType.UBYTE, 246.0, dummyPos)) shouldBe true
|
||||
(PtNumber(DataType.UWORD, 12345.0, dummyPos) != PtNumber(DataType.UWORD, 12346.0, dummyPos)) shouldBe true
|
||||
(PtNumber(DataType.UWORD, 12345.0, dummyPos) != PtNumber(DataType.FLOAT, 12346.0, dummyPos)) shouldBe true
|
||||
(PtNumber(DataType.FLOAT, 9.99, dummyPos) != PtNumber(DataType.UBYTE, 9.0, dummyPos)) shouldBe true
|
||||
(PtNumber(DataType.FLOAT, 9.99, dummyPos) != PtNumber(DataType.UWORD, 9.0, dummyPos)) shouldBe true
|
||||
(PtNumber(DataType.FLOAT, 9.99, dummyPos) != PtNumber(DataType.FLOAT, 9.0, dummyPos)) shouldBe true
|
||||
|
||||
sameValueAndType(PtNumber(DataType.UBYTE, 100.0, dummyPos), PtNumber(DataType.UBYTE, 101.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(PtNumber(DataType.UBYTE, 100.0, dummyPos), PtNumber(DataType.UWORD, 101.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(PtNumber(DataType.UBYTE, 100.0, dummyPos), PtNumber(DataType.FLOAT, 101.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(PtNumber(DataType.UWORD, 245.0, dummyPos), PtNumber(DataType.UBYTE, 246.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(PtNumber(DataType.UWORD, 12345.0, dummyPos), PtNumber(DataType.UWORD, 12346.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(PtNumber(DataType.UWORD, 12345.0, dummyPos), PtNumber(DataType.FLOAT, 12346.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(PtNumber(DataType.FLOAT, 9.99, dummyPos), PtNumber(DataType.UBYTE, 9.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(PtNumber(DataType.FLOAT, 9.99, dummyPos), PtNumber(DataType.UWORD, 9.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(PtNumber(DataType.FLOAT, 9.99, dummyPos), PtNumber(DataType.FLOAT, 9.0, dummyPos)) shouldBe false
|
||||
|
||||
|
||||
}
|
||||
|
||||
test("testEqualsRef") {
|
||||
(PtString("hello", Encoding.PETSCII, dummyPos) == PtString("hello", Encoding.PETSCII, dummyPos)) shouldBe true
|
||||
(PtString("hello", Encoding.PETSCII, dummyPos) != PtString("bye", Encoding.PETSCII, dummyPos)) shouldBe true
|
||||
(PtString("hello", Encoding.SCREENCODES, dummyPos) == PtString("hello", Encoding.SCREENCODES, dummyPos)) shouldBe true
|
||||
(PtString("hello", Encoding.SCREENCODES, dummyPos) != PtString("bye", Encoding.SCREENCODES, dummyPos)) shouldBe true
|
||||
(PtString("hello", Encoding.SCREENCODES, dummyPos) != PtString("hello", Encoding.PETSCII, dummyPos)) shouldBe true
|
||||
|
||||
val lvOne = PtNumber(DataType.UBYTE, 1.0, dummyPos)
|
||||
val lvTwo = PtNumber(DataType.UBYTE, 2.0, dummyPos)
|
||||
val lvThree = PtNumber(DataType.UBYTE, 3.0, dummyPos)
|
||||
val lvOneR = PtNumber(DataType.UBYTE, 1.0, dummyPos)
|
||||
val lvTwoR = PtNumber(DataType.UBYTE, 2.0, dummyPos)
|
||||
val lvThreeR = PtNumber(DataType.UBYTE, 3.0, dummyPos)
|
||||
val lvFour= PtNumber(DataType.UBYTE, 4.0, dummyPos)
|
||||
val lv1 = PtArray(DataType.ARRAY_UB, dummyPos)
|
||||
arrayOf(lvOne, lvTwo, lvThree).forEach { lv1.add(it) }
|
||||
val lv2 = PtArray(DataType.ARRAY_UB, dummyPos)
|
||||
arrayOf(lvOneR, lvTwoR, lvThreeR).forEach { lv2.add(it) }
|
||||
val lv3 = PtArray(DataType.ARRAY_UB, dummyPos)
|
||||
arrayOf(lvOneR, lvTwoR, lvFour).forEach { lv3.add(it) }
|
||||
lv1 shouldBe lv2
|
||||
lv1 shouldNotBe lv3
|
||||
}
|
||||
|
||||
test("testGreaterThan") {
|
||||
(PtNumber(DataType.UBYTE, 100.0, dummyPos) > PtNumber(DataType.UBYTE, 99.0, dummyPos)) shouldBe true
|
||||
(PtNumber(DataType.UWORD, 254.0, dummyPos) > PtNumber(DataType.UWORD, 253.0, dummyPos)) shouldBe true
|
||||
(PtNumber(DataType.FLOAT, 100.0, dummyPos) > PtNumber(DataType.FLOAT, 99.9, dummyPos)) shouldBe true
|
||||
|
||||
(PtNumber(DataType.UBYTE, 100.0, dummyPos) >= PtNumber(DataType.UBYTE, 100.0, dummyPos)) shouldBe true
|
||||
(PtNumber(DataType.UWORD, 254.0, dummyPos) >= PtNumber(DataType.UWORD, 254.0, dummyPos)) shouldBe true
|
||||
(PtNumber(DataType.FLOAT, 100.0, dummyPos) >= PtNumber(DataType.FLOAT, 100.0, dummyPos)) shouldBe true
|
||||
|
||||
(PtNumber(DataType.UBYTE, 100.0, dummyPos) > PtNumber(DataType.UBYTE, 100.0, dummyPos)) shouldBe false
|
||||
(PtNumber(DataType.UWORD, 254.0, dummyPos) > PtNumber(DataType.UWORD, 254.0, dummyPos)) shouldBe false
|
||||
(PtNumber(DataType.FLOAT, 100.0, dummyPos) > PtNumber(DataType.FLOAT, 100.0, dummyPos)) shouldBe false
|
||||
|
||||
(PtNumber(DataType.UBYTE, 100.0, dummyPos) >= PtNumber(DataType.UBYTE, 101.0, dummyPos)) shouldBe false
|
||||
(PtNumber(DataType.UWORD, 254.0, dummyPos) >= PtNumber(DataType.UWORD, 255.0, dummyPos)) shouldBe false
|
||||
(PtNumber(DataType.FLOAT, 100.0, dummyPos) >= PtNumber(DataType.FLOAT, 100.1, dummyPos)) shouldBe false
|
||||
}
|
||||
|
||||
test("testLessThan") {
|
||||
(PtNumber(DataType.UBYTE, 100.0, dummyPos) < PtNumber(DataType.UBYTE, 101.0, dummyPos)) shouldBe true
|
||||
(PtNumber(DataType.UWORD, 254.0, dummyPos) < PtNumber(DataType.UWORD, 255.0, dummyPos)) shouldBe true
|
||||
(PtNumber(DataType.FLOAT, 100.0, dummyPos) < PtNumber(DataType.FLOAT, 100.1, dummyPos)) shouldBe true
|
||||
|
||||
(PtNumber(DataType.UBYTE, 100.0, dummyPos) <= PtNumber(DataType.UBYTE, 100.0, dummyPos)) shouldBe true
|
||||
(PtNumber(DataType.UWORD, 254.0, dummyPos) <= PtNumber(DataType.UWORD, 254.0, dummyPos)) shouldBe true
|
||||
(PtNumber(DataType.FLOAT, 100.0, dummyPos) <= PtNumber(DataType.FLOAT, 100.0, dummyPos)) shouldBe true
|
||||
|
||||
(PtNumber(DataType.UBYTE, 100.0, dummyPos) < PtNumber(DataType.UBYTE, 100.0, dummyPos)) shouldBe false
|
||||
(PtNumber(DataType.UWORD, 254.0, dummyPos) < PtNumber(DataType.UWORD, 254.0, dummyPos)) shouldBe false
|
||||
(PtNumber(DataType.FLOAT, 100.0, dummyPos) < PtNumber(DataType.FLOAT, 100.0, dummyPos)) shouldBe false
|
||||
|
||||
(PtNumber(DataType.UBYTE, 100.0, dummyPos) <= PtNumber(DataType.UBYTE, 99.0, dummyPos)) shouldBe false
|
||||
(PtNumber(DataType.UWORD, 254.0, dummyPos) <= PtNumber(DataType.UWORD, 253.0, dummyPos)) shouldBe false
|
||||
(PtNumber(DataType.FLOAT, 100.0, dummyPos) <= PtNumber(DataType.FLOAT, 99.9, dummyPos)) shouldBe false
|
||||
}
|
||||
|
||||
})
|
@ -8,9 +8,8 @@ import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import prog8.ast.antlr.unescape
|
||||
import prog8.code.core.Encoding
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.core.unescape
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8.code.target.Encoder
|
||||
@ -216,7 +215,7 @@ class TestStringEncodings: FunSpec({
|
||||
|
||||
test("special pass-through") {
|
||||
val passthroughEscaped= """\x00\x1b\x99\xff"""
|
||||
val passthrough = unescape(passthroughEscaped, Position.DUMMY)
|
||||
val passthrough = passthroughEscaped.unescape()
|
||||
passthrough.length shouldBe 4
|
||||
passthrough[0] shouldBe '\u8000'
|
||||
passthrough[1] shouldBe '\u801b'
|
||||
|
@ -22,7 +22,7 @@ class TestSymbolTable: FunSpec({
|
||||
test("symboltable flatten") {
|
||||
val st = makeSt()
|
||||
st.flat[listOf("zzzzz")] shouldBe null
|
||||
st.flat.getValue(listOf("sin")).type shouldBe StNodeType.BUILTINFUNC
|
||||
st.flat.getValue(listOf("msb")).type shouldBe StNodeType.BUILTINFUNC
|
||||
st.flat.getValue(listOf("block2")).type shouldBe StNodeType.BLOCK
|
||||
st.flat.getValue(listOf("block2", "sub2", "subsub", "label")).type shouldBe StNodeType.LABEL
|
||||
st.flat[listOf("block2", "sub2", "subsub", "label", "zzzz")] shouldBe null
|
||||
@ -37,8 +37,8 @@ class TestSymbolTable: FunSpec({
|
||||
default = st.lookupOrElse(listOf("undefined")) { StNode("default", StNodeType.LABEL, Position.DUMMY) }
|
||||
default.name shouldBe "default"
|
||||
|
||||
val sinfunc = st.lookupOrElse("sin") { fail("sin must be found") }
|
||||
sinfunc.type shouldBe StNodeType.BUILTINFUNC
|
||||
val msbFunc = st.lookupOrElse("msb") { fail("msb must be found") }
|
||||
msbFunc.type shouldBe StNodeType.BUILTINFUNC
|
||||
|
||||
val variable = st.lookupOrElse(listOf("block1", "sub2", "v2")) { fail("v2 must be found") }
|
||||
variable.type shouldBe StNodeType.STATICVAR
|
||||
@ -92,7 +92,7 @@ private fun makeSt(): SymbolTable {
|
||||
sub221.add(StNode("label", StNodeType.LABEL, Position.DUMMY))
|
||||
sub22.add(sub221)
|
||||
|
||||
val builtinfunc = StNode("sin", StNodeType.BUILTINFUNC, Position.DUMMY)
|
||||
val builtinfunc = StNode("msb", StNodeType.BUILTINFUNC, Position.DUMMY)
|
||||
st.add(block1)
|
||||
st.add(block2)
|
||||
st.add(builtinfunc)
|
||||
|
@ -338,71 +338,6 @@ main {
|
||||
txt.print_b(bb)
|
||||
txt.nl()
|
||||
|
||||
ub = 0
|
||||
uw = sin16u(ub)
|
||||
txt.print_uw(uw)
|
||||
txt.nl()
|
||||
uw = zero+sin16u(ub)*1+zero
|
||||
txt.print_uw(uw)
|
||||
txt.nl()
|
||||
|
||||
ub = 0
|
||||
uw = cos16u(ub)
|
||||
txt.print_uw(uw)
|
||||
txt.nl()
|
||||
uw = zero+cos16u(ub)*1+zero
|
||||
txt.print_uw(uw)
|
||||
txt.nl()
|
||||
|
||||
ub = 0
|
||||
ww = sin16(ub)
|
||||
txt.print_w(ww)
|
||||
txt.nl()
|
||||
ww = zero+sin16(ub)*1+zero
|
||||
txt.print_w(ww)
|
||||
txt.nl()
|
||||
|
||||
ub = 0
|
||||
ww = cos16(ub)
|
||||
txt.print_w(ww)
|
||||
txt.nl()
|
||||
uw = 0
|
||||
ww = zero+cos16(ub)*1+zero
|
||||
txt.print_w(ww)
|
||||
txt.nl()
|
||||
|
||||
ub2 = 0
|
||||
ub = sin8u(ub2)
|
||||
txt.print_ub(ub)
|
||||
txt.nl()
|
||||
ub = zero+sin8u(ub2)*1+zero
|
||||
txt.print_ub(ub)
|
||||
txt.nl()
|
||||
|
||||
ub2 = 0
|
||||
ub = cos8u(ub2)
|
||||
txt.print_ub(ub)
|
||||
txt.nl()
|
||||
ub = zero+cos8u(ub2)*1+zero
|
||||
txt.print_ub(ub)
|
||||
txt.nl()
|
||||
|
||||
ub2 = 0
|
||||
bb = sin8(ub2)
|
||||
txt.print_b(bb)
|
||||
txt.nl()
|
||||
bb = zero+sin8(ub2)*1+zero
|
||||
txt.print_b(bb)
|
||||
txt.nl()
|
||||
|
||||
ub2 = 0
|
||||
bb = cos8(ub2)
|
||||
txt.print_b(bb)
|
||||
txt.nl()
|
||||
bb = zero+cos8(ub2)*1+zero
|
||||
txt.print_b(bb)
|
||||
txt.nl()
|
||||
|
||||
bb = -100
|
||||
bb = abs(bb)
|
||||
txt.print_b(bb)
|
||||
|
@ -6,10 +6,10 @@ import io.kotest.matchers.string.shouldContain
|
||||
import prog8.ast.AstToSourceTextConverter
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.parser.ParseError
|
||||
import prog8.parser.Prog8Parser.parseModule
|
||||
import prog8.code.core.SourceCode
|
||||
import prog8.code.core.internedStringsModuleName
|
||||
import prog8.parser.ParseError
|
||||
import prog8.parser.Prog8Parser.parseModule
|
||||
import prog8tests.helpers.DummyFunctions
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import prog8tests.helpers.DummyStringEncoder
|
||||
|
@ -10,8 +10,8 @@ import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.expressions.PrefixExpression
|
||||
import prog8.ast.statements.*
|
||||
import prog8.code.core.Position
|
||||
import prog8.parser.Prog8Parser
|
||||
import prog8.code.core.SourceCode
|
||||
import prog8.parser.Prog8Parser
|
||||
import prog8tests.helpers.DummyFunctions
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import prog8tests.helpers.DummyStringEncoder
|
||||
|
@ -6,7 +6,6 @@ import io.kotest.matchers.ints.shouldBeGreaterThan
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.compiler.IntermediateAstMaker
|
||||
import prog8tests.helpers.compileText
|
||||
@ -22,7 +21,7 @@ class TestIntermediateAst: FunSpec({
|
||||
ubyte cc
|
||||
ubyte[] array = [1,2,3]
|
||||
cc = 11 in array
|
||||
cc = cc |> sin8u() |> cos8u()
|
||||
cc = cc |> lsb() |> sqrt16()
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
@ -17,15 +17,11 @@ import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.Encoding
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.core.ZeropageWish
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.cbm.PetsciiEncoding
|
||||
import prog8.parser.ParseError
|
||||
import prog8.parser.Prog8Parser.parseModule
|
||||
import prog8.code.core.SourceCode
|
||||
import prog8tests.helpers.*
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.isRegularFile
|
||||
@ -177,8 +173,8 @@ class TestProg8Parser: FunSpec( {
|
||||
|
||||
context("ImportDirectives") {
|
||||
test("should not be looked into by the parser") {
|
||||
val importedNoExt = assumeNotExists(fixturesDir, "i_do_not_exist")
|
||||
assumeNotExists(fixturesDir, "i_do_not_exist.p8")
|
||||
val importedNoExt = Helpers.assumeNotExists(Helpers.fixturesDir, "i_do_not_exist")
|
||||
Helpers.assumeNotExists(Helpers.fixturesDir, "i_do_not_exist.p8")
|
||||
val text = "%import ${importedNoExt.name}"
|
||||
val module = parseModule(SourceCode.Text(text))
|
||||
|
||||
@ -193,7 +189,7 @@ class TestProg8Parser: FunSpec( {
|
||||
}
|
||||
|
||||
test("from an empty file should result in empty Module") {
|
||||
val path = assumeReadableFile(fixturesDir, "ast_empty.p8")
|
||||
val path = Helpers.assumeReadableFile(Helpers.fixturesDir, "ast_empty.p8")
|
||||
val module = parseModule(SourceCode.File(path))
|
||||
module.statements.size shouldBe 0
|
||||
}
|
||||
@ -212,7 +208,7 @@ class TestProg8Parser: FunSpec( {
|
||||
}
|
||||
|
||||
test("parsed from a file") {
|
||||
val path = assumeReadableFile(fixturesDir, "ast_simple_main.p8")
|
||||
val path = Helpers.assumeReadableFile(Helpers.fixturesDir, "ast_simple_main.p8")
|
||||
val module = parseModule(SourceCode.File(path))
|
||||
module.name shouldBe path.nameWithoutExtension
|
||||
}
|
||||
@ -275,7 +271,7 @@ class TestProg8Parser: FunSpec( {
|
||||
}
|
||||
|
||||
test("in ParseError from bad file source code") {
|
||||
val path = assumeReadableFile(fixturesDir, "ast_file_with_syntax_error.p8")
|
||||
val path = Helpers.assumeReadableFile(Helpers.fixturesDir, "ast_file_with_syntax_error.p8")
|
||||
|
||||
val e = shouldThrow<ParseError> { parseModule(SourceCode.File(path)) }
|
||||
assertPosition(e.position, SourceCode.relative(path).toString(), 2, 6)
|
||||
@ -291,13 +287,13 @@ class TestProg8Parser: FunSpec( {
|
||||
}
|
||||
|
||||
test("of Module parsed from a file") {
|
||||
val path = assumeReadableFile(fixturesDir, "ast_simple_main.p8")
|
||||
val path = Helpers.assumeReadableFile(Helpers.fixturesDir, "ast_simple_main.p8")
|
||||
val module = parseModule(SourceCode.File(path))
|
||||
assertPositionOf(module, SourceCode.relative(path).toString(), 1, 0)
|
||||
}
|
||||
|
||||
test("of non-root Nodes parsed from file") {
|
||||
val path = assumeReadableFile(fixturesDir, "ast_simple_main.p8")
|
||||
val path = Helpers.assumeReadableFile(Helpers.fixturesDir, "ast_simple_main.p8")
|
||||
|
||||
val module = parseModule(SourceCode.File(path))
|
||||
val mpf = module.position.file
|
||||
@ -361,7 +357,7 @@ class TestProg8Parser: FunSpec( {
|
||||
}
|
||||
|
||||
test("isn't absolute for filesystem paths") {
|
||||
val path = assumeReadableFile(fixturesDir, "ast_simple_main.p8")
|
||||
val path = Helpers.assumeReadableFile(Helpers.fixturesDir, "ast_simple_main.p8")
|
||||
val module = parseModule(SourceCode.File(path))
|
||||
assertSomethingForAllNodes(module) {
|
||||
Path(it.position.file).isAbsolute shouldBe false
|
||||
|
@ -6,10 +6,7 @@ import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import prog8.code.core.SourceCode
|
||||
import prog8.code.core.SourceCode.Companion.libraryFilePrefix
|
||||
import prog8tests.helpers.assumeNotExists
|
||||
import prog8tests.helpers.assumeReadableFile
|
||||
import prog8tests.helpers.fixturesDir
|
||||
import prog8tests.helpers.resourcesDir
|
||||
import prog8tests.helpers.Helpers
|
||||
import kotlin.io.path.Path
|
||||
|
||||
|
||||
@ -32,26 +29,26 @@ class TestSourceCode: AnnotationSpec() {
|
||||
@Test
|
||||
fun testFromPathWithNonExistingPath() {
|
||||
val filename = "i_do_not_exist.p8"
|
||||
val path = assumeNotExists(fixturesDir, filename)
|
||||
val path = Helpers.assumeNotExists(Helpers.fixturesDir, filename)
|
||||
shouldThrow<NoSuchFileException> { SourceCode.File(path) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromPathWithMissingExtension_p8() {
|
||||
val pathWithoutExt = assumeNotExists(fixturesDir,"simple_main")
|
||||
assumeReadableFile(fixturesDir,"ast_simple_main.p8")
|
||||
val pathWithoutExt = Helpers.assumeNotExists(Helpers.fixturesDir,"simple_main")
|
||||
Helpers.assumeReadableFile(Helpers.fixturesDir,"ast_simple_main.p8")
|
||||
shouldThrow<NoSuchFileException> { SourceCode.File(pathWithoutExt) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromPathWithDirectory() {
|
||||
shouldThrow<FileSystemException> { SourceCode.File(fixturesDir) }
|
||||
shouldThrow<FileSystemException> { SourceCode.File(Helpers.fixturesDir) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromPathWithExistingPath() {
|
||||
val filename = "ast_simple_main.p8"
|
||||
val path = assumeReadableFile(fixturesDir, filename)
|
||||
val path = Helpers.assumeReadableFile(Helpers.fixturesDir, filename)
|
||||
val src = SourceCode.File(path)
|
||||
val expectedOrigin = SourceCode.relative(path).toString()
|
||||
src.origin shouldBe expectedOrigin
|
||||
@ -64,7 +61,7 @@ class TestSourceCode: AnnotationSpec() {
|
||||
fun testFromPathWithExistingNonNormalizedPath() {
|
||||
val filename = "ast_simple_main.p8"
|
||||
val path = Path(".", "test", "..", "test", "fixtures", filename)
|
||||
val srcFile = assumeReadableFile(path).toFile()
|
||||
val srcFile = Helpers.assumeReadableFile(path).toFile()
|
||||
val src = SourceCode.File(path)
|
||||
val expectedOrigin = SourceCode.relative(path).toString()
|
||||
src.origin shouldBe expectedOrigin
|
||||
@ -74,7 +71,7 @@ class TestSourceCode: AnnotationSpec() {
|
||||
@Test
|
||||
fun testFromResourcesWithExistingP8File_withoutLeadingSlash() {
|
||||
val pathString = "prog8lib/math.p8"
|
||||
val srcFile = assumeReadableFile(resourcesDir, pathString).toFile()
|
||||
val srcFile = Helpers.assumeReadableFile(Helpers.resourcesDir, pathString).toFile()
|
||||
val src = SourceCode.Resource(pathString)
|
||||
|
||||
src.origin shouldBe "$libraryFilePrefix/$pathString"
|
||||
@ -86,7 +83,7 @@ class TestSourceCode: AnnotationSpec() {
|
||||
@Test
|
||||
fun testFromResourcesWithExistingP8File_withLeadingSlash() {
|
||||
val pathString = "/prog8lib/math.p8"
|
||||
val srcFile = assumeReadableFile(resourcesDir, pathString.substring(1)).toFile()
|
||||
val srcFile = Helpers.assumeReadableFile(Helpers.resourcesDir, pathString.substring(1)).toFile()
|
||||
val src = SourceCode.Resource(pathString)
|
||||
|
||||
src.origin shouldBe "$libraryFilePrefix$pathString"
|
||||
@ -96,7 +93,7 @@ class TestSourceCode: AnnotationSpec() {
|
||||
@Test
|
||||
fun testFromResourcesWithExistingAsmFile_withoutLeadingSlash() {
|
||||
val pathString = "prog8lib/math.asm"
|
||||
val srcFile = assumeReadableFile(resourcesDir, pathString).toFile()
|
||||
val srcFile = Helpers.assumeReadableFile(Helpers.resourcesDir, pathString).toFile()
|
||||
val src = SourceCode.Resource(pathString)
|
||||
|
||||
src.origin shouldBe "$libraryFilePrefix/$pathString"
|
||||
@ -107,7 +104,7 @@ class TestSourceCode: AnnotationSpec() {
|
||||
@Test
|
||||
fun testFromResourcesWithExistingAsmFile_withLeadingSlash() {
|
||||
val pathString = "/prog8lib/math.asm"
|
||||
val srcFile = assumeReadableFile(resourcesDir, pathString.substring(1)).toFile()
|
||||
val srcFile = Helpers.assumeReadableFile(Helpers.resourcesDir, pathString.substring(1)).toFile()
|
||||
val src = SourceCode.Resource(pathString)
|
||||
|
||||
src.origin shouldBe "$libraryFilePrefix$pathString"
|
||||
@ -117,7 +114,7 @@ class TestSourceCode: AnnotationSpec() {
|
||||
@Test
|
||||
fun testFromResourcesWithNonNormalizedPath() {
|
||||
val pathString = "/prog8lib/../prog8lib/math.p8"
|
||||
val srcFile = assumeReadableFile(resourcesDir, pathString.substring(1)).toFile()
|
||||
val srcFile = Helpers.assumeReadableFile(Helpers.resourcesDir, pathString.substring(1)).toFile()
|
||||
val src = SourceCode.Resource(pathString)
|
||||
|
||||
src.origin shouldBe "$libraryFilePrefix/prog8lib/math.p8"
|
||||
@ -129,14 +126,14 @@ class TestSourceCode: AnnotationSpec() {
|
||||
@Test
|
||||
fun testFromResourcesWithNonExistingFile_withLeadingSlash() {
|
||||
val pathString = "/prog8lib/i_do_not_exist"
|
||||
assumeNotExists(resourcesDir, pathString.substring(1))
|
||||
Helpers.assumeNotExists(Helpers.resourcesDir, pathString.substring(1))
|
||||
|
||||
shouldThrow<NoSuchFileException> { SourceCode.Resource(pathString) }
|
||||
}
|
||||
@Test
|
||||
fun testFromResourcesWithNonExistingFile_withoutLeadingSlash() {
|
||||
val pathString = "prog8lib/i_do_not_exist"
|
||||
assumeNotExists(resourcesDir, pathString)
|
||||
Helpers.assumeNotExists(Helpers.resourcesDir, pathString)
|
||||
|
||||
shouldThrow<NoSuchFileException> { SourceCode.Resource(pathString) }
|
||||
}
|
||||
|
@ -5,8 +5,8 @@ import io.kotest.matchers.shouldBe
|
||||
import prog8.ast.statements.Block
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.code.core.DataType
|
||||
import prog8.parser.Prog8Parser.parseModule
|
||||
import prog8.code.core.SourceCode
|
||||
import prog8.parser.Prog8Parser.parseModule
|
||||
|
||||
|
||||
class TestSubroutines: AnnotationSpec() {
|
||||
|
@ -3,9 +3,14 @@ package prog8tests.ast
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.StringLiteral
|
||||
import prog8.ast.statements.InlineAssembly
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.compiler.printProgram
|
||||
import prog8tests.helpers.compileText
|
||||
|
||||
class TestVarious: FunSpec({
|
||||
@ -61,5 +66,53 @@ main {
|
||||
}"""
|
||||
compileText(C64Target(), false, text, writeAssembly = false) shouldBe null
|
||||
}
|
||||
|
||||
test("simple string comparison still works") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
ubyte @shared value
|
||||
str thing = "????"
|
||||
|
||||
if thing=="name" {
|
||||
value++
|
||||
}
|
||||
|
||||
if thing!="name" {
|
||||
value++
|
||||
}
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target(), optimize=false, src, writeAssembly=true)!!
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBe 6
|
||||
}
|
||||
|
||||
test("string concatenation and repeats") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
str @shared name = "part1" + "part2"
|
||||
str @shared rept = "rep"*4
|
||||
const ubyte times = 3
|
||||
name = "xx1" + "xx2"
|
||||
rept = "xyz" * (times+1)
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target(), optimize=false, src, writeAssembly=true)!!
|
||||
printProgram(result.program)
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBe 6
|
||||
val name1 = stmts[0] as VarDecl
|
||||
val rept1 = stmts[1] as VarDecl
|
||||
(name1.value as StringLiteral).value shouldBe "part1part2"
|
||||
(rept1.value as StringLiteral).value shouldBe "reprepreprep"
|
||||
val name2strcopy = stmts[3] as IFunctionCall
|
||||
val rept2strcopy = stmts[4] as IFunctionCall
|
||||
val name2 = name2strcopy.args.first() as IdentifierReference
|
||||
val rept2 = rept2strcopy.args.first() as IdentifierReference
|
||||
(name2.targetVarDecl(result.program)!!.value as StringLiteral).value shouldBe "xx1xx2"
|
||||
(rept2.targetVarDecl(result.program)!!.value as StringLiteral).value shouldBe "xyzxyzxyzxyz"
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -13,7 +13,6 @@ import prog8.code.target.C64Target
|
||||
import prog8.code.target.c64.C64Zeropage
|
||||
import prog8.codegen.cpu6502.AsmGen
|
||||
import prog8.compiler.astprocessing.SymbolTableMaker
|
||||
import prog8.code.core.SourceCode
|
||||
import prog8tests.helpers.DummyFunctions
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import prog8tests.helpers.DummyStringEncoder
|
||||
|
@ -11,12 +11,12 @@ internal object DummyFunctions : IBuiltinFunctions {
|
||||
override val names: Set<String> = emptySet()
|
||||
override val purefunctionNames: Set<String> = emptySet()
|
||||
override fun constValue(
|
||||
name: String,
|
||||
funcName: String,
|
||||
args: List<Expression>,
|
||||
position: Position,
|
||||
): NumericLiteral? = null
|
||||
|
||||
override fun returnType(name: String, args: MutableList<Expression>) = InferredTypes.InferredType.unknown()
|
||||
override fun returnType(funcName: String) = InferredTypes.InferredType.unknown()
|
||||
}
|
||||
|
||||
internal object DummyMemsizer : IMemSizer {
|
||||
|
@ -19,13 +19,13 @@ internal fun compileFile(
|
||||
optimize: Boolean,
|
||||
fileDir: Path,
|
||||
fileName: String,
|
||||
outputDir: Path = prog8tests.helpers.outputDir,
|
||||
outputDir: Path = prog8tests.helpers.Helpers.outputDir,
|
||||
errors: IErrorReporter? = null,
|
||||
writeAssembly: Boolean = true,
|
||||
optFloatExpr: Boolean = true
|
||||
) : CompilationResult? {
|
||||
val filepath = fileDir.resolve(fileName)
|
||||
assumeReadableFile(filepath)
|
||||
Helpers.assumeReadableFile(filepath)
|
||||
val args = CompilerArguments(
|
||||
filepath,
|
||||
optimize,
|
||||
@ -56,7 +56,7 @@ internal fun compileText(
|
||||
writeAssembly: Boolean = true,
|
||||
optFloatExpr: Boolean = true
|
||||
) : CompilationResult? {
|
||||
val filePath = outputDir.resolve("on_the_fly_test_" + sourceText.hashCode().toUInt().toString(16) + ".p8")
|
||||
val filePath = Helpers.outputDir.resolve("on_the_fly_test_" + sourceText.hashCode().toUInt().toString(16) + ".p8")
|
||||
// we don't assumeNotExists(filePath) - should be ok to just overwrite it
|
||||
filePath.toFile().writeText(sourceText)
|
||||
return compileFile(platform, optimize, filePath.parent, filePath.name, errors=errors, writeAssembly=writeAssembly, optFloatExpr = optFloatExpr)
|
||||
@ -71,7 +71,7 @@ internal fun generateAssembly(
|
||||
floats = true,
|
||||
noSysInit = true,
|
||||
compTarget = C64Target(),
|
||||
loadAddress = 0u, outputDir = outputDir)
|
||||
loadAddress = 0u, outputDir = Helpers.outputDir)
|
||||
coptions.compTarget.machine.zeropage = C64Zeropage(coptions)
|
||||
val st = SymbolTableMaker().makeFrom(program)
|
||||
val errors = ErrorReporterForTests()
|
||||
|
@ -1,50 +1,68 @@
|
||||
package prog8tests.helpers
|
||||
|
||||
fun <T, U> cartesianProduct(c1: Collection<T>, c2: Collection<U>): Sequence<Pair<T, U>> {
|
||||
return c1.flatMap { lhsElem -> c2.map { rhsElem -> lhsElem to rhsElem } }.asSequence()
|
||||
}
|
||||
|
||||
fun <T, U, V> cartesianProduct(c1: Collection<T>, c2: Collection<U>, c3: Collection<V>): Sequence<Triple<T, U, V>> {
|
||||
return sequence {
|
||||
for (a in c1)
|
||||
for (b in c2)
|
||||
for (c in c3)
|
||||
yield(Triple(a,b,c))
|
||||
object Combinations {
|
||||
fun <T, U> cartesianProduct(c1: Collection<T>, c2: Collection<U>): Sequence<Pair<T, U>> {
|
||||
return c1.flatMap { lhsElem -> c2.map { rhsElem -> lhsElem to rhsElem } }.asSequence()
|
||||
}
|
||||
}
|
||||
|
||||
data class Product<out T, out U, out V, out W>(val first: T, val second: U, val third: V, val fourth: W)
|
||||
|
||||
fun <T, U, V, W> cartesianProduct(c1: Collection<T>, c2: Collection<U>, c3: Collection<V>, c4: Collection<W>): Sequence<Product<T, U, V, W>> {
|
||||
return sequence {
|
||||
for (a in c1)
|
||||
for (b in c2)
|
||||
for (c in c3)
|
||||
for (d in c4)
|
||||
yield(Product(a,b,c,d))
|
||||
fun <T, U, V> cartesianProduct(c1: Collection<T>, c2: Collection<U>, c3: Collection<V>): Sequence<Triple<T, U, V>> {
|
||||
return sequence {
|
||||
for (a in c1)
|
||||
for (b in c2)
|
||||
for (c in c3)
|
||||
yield(Triple(a, b, c))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <A, B, R> mapCombinations(dim1: Iterable<A>, dim2: Iterable<B>, combine2: (A, B) -> R) =
|
||||
sequence {
|
||||
for (a in dim1)
|
||||
for (b in dim2)
|
||||
yield(combine2(a, b))
|
||||
}.toList()
|
||||
data class Product<out T, out U, out V, out W>(val first: T, val second: U, val third: V, val fourth: W)
|
||||
|
||||
fun <A, B, C, R> mapCombinations(dim1: Iterable<A>, dim2: Iterable<B>, dim3: Iterable<C>, combine3: (A, B, C) -> R) =
|
||||
sequence {
|
||||
for (a in dim1)
|
||||
for (b in dim2)
|
||||
for (c in dim3)
|
||||
yield(combine3(a, b, c))
|
||||
}.toList()
|
||||
fun <T, U, V, W> cartesianProduct(
|
||||
c1: Collection<T>,
|
||||
c2: Collection<U>,
|
||||
c3: Collection<V>,
|
||||
c4: Collection<W>
|
||||
): Sequence<Product<T, U, V, W>> {
|
||||
return sequence {
|
||||
for (a in c1)
|
||||
for (b in c2)
|
||||
for (c in c3)
|
||||
for (d in c4)
|
||||
yield(Product(a, b, c, d))
|
||||
}
|
||||
}
|
||||
|
||||
fun <A, B, C, D, R> mapCombinations(dim1: Iterable<A>, dim2: Iterable<B>, dim3: Iterable<C>, dim4: Iterable<D>, combine4: (A, B, C, D) -> R) =
|
||||
sequence {
|
||||
for (a in dim1)
|
||||
for (b in dim2)
|
||||
for (c in dim3)
|
||||
for (d in dim4)
|
||||
yield(combine4(a, b, c, d))
|
||||
}.toList()
|
||||
fun <A, B, R> mapCombinations(dim1: Iterable<A>, dim2: Iterable<B>, combine2: (A, B) -> R) =
|
||||
sequence {
|
||||
for (a in dim1)
|
||||
for (b in dim2)
|
||||
yield(combine2(a, b))
|
||||
}.toList()
|
||||
|
||||
fun <A, B, C, R> mapCombinations(
|
||||
dim1: Iterable<A>,
|
||||
dim2: Iterable<B>,
|
||||
dim3: Iterable<C>,
|
||||
combine3: (A, B, C) -> R
|
||||
) =
|
||||
sequence {
|
||||
for (a in dim1)
|
||||
for (b in dim2)
|
||||
for (c in dim3)
|
||||
yield(combine3(a, b, c))
|
||||
}.toList()
|
||||
|
||||
fun <A, B, C, D, R> mapCombinations(
|
||||
dim1: Iterable<A>,
|
||||
dim2: Iterable<B>,
|
||||
dim3: Iterable<C>,
|
||||
dim4: Iterable<D>,
|
||||
combine4: (A, B, C, D) -> R
|
||||
) =
|
||||
sequence {
|
||||
for (a in dim1)
|
||||
for (b in dim2)
|
||||
for (c in dim3)
|
||||
for (d in dim4)
|
||||
yield(combine4(a, b, c, d))
|
||||
}.toList()
|
||||
}
|
@ -7,63 +7,69 @@ import java.nio.file.Path
|
||||
import kotlin.io.path.*
|
||||
|
||||
|
||||
val workingDir = assumeDirectory("").absolute() // Note: "." does NOT work..!
|
||||
val fixturesDir = assumeDirectory(workingDir,"test/fixtures")
|
||||
val resourcesDir = assumeDirectory(workingDir,"res")
|
||||
val outputDir: Path = createIfNotExists(workingDir, "build/tmp/test").also{ assumeDirectory(workingDir, "build/tmp/test") }
|
||||
object Helpers {
|
||||
val workingDir = assumeDirectory("").absolute() // Note: "." does NOT work..!
|
||||
val fixturesDir = assumeDirectory(workingDir, "test/fixtures")
|
||||
val resourcesDir = assumeDirectory(workingDir, "res")
|
||||
val outputDir: Path =
|
||||
createIfNotExists(workingDir, "build/tmp/test").also { assumeDirectory(workingDir, "build/tmp/test") }
|
||||
|
||||
fun createIfNotExists(workingDir: Path, path: String): Path {
|
||||
val dir = workingDir / path
|
||||
if(!dir.toFile().isDirectory)
|
||||
Files.createDirectories(dir)
|
||||
return dir
|
||||
}
|
||||
|
||||
fun assumeNotExists(path: Path): Path {
|
||||
withClue("sanity check: should not exist: ${path.absolute()}") {
|
||||
path.exists() shouldBe false
|
||||
fun createIfNotExists(workingDir: Path, path: String): Path {
|
||||
val dir = workingDir / path
|
||||
if (!dir.toFile().isDirectory)
|
||||
Files.createDirectories(dir)
|
||||
return dir
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
fun assumeNotExists(pathStr: String): Path = assumeNotExists(Path(pathStr))
|
||||
fun assumeNotExists(path: Path, other: String): Path = assumeNotExists(path / other)
|
||||
|
||||
fun assumeReadable(path: Path): Path {
|
||||
withClue("sanity check: should be readable: ${path.absolute()}") {
|
||||
path.isReadable() shouldBe true
|
||||
fun assumeNotExists(path: Path): Path {
|
||||
withClue("sanity check: should not exist: ${path.absolute()}") {
|
||||
path.exists() shouldBe false
|
||||
}
|
||||
return path
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
fun assumeReadableFile(path: Path): Path {
|
||||
withClue("sanity check: should be normal file: ${path.absolute()}") {
|
||||
path.isRegularFile() shouldBe true
|
||||
fun assumeNotExists(pathStr: String): Path = assumeNotExists(Path(pathStr))
|
||||
fun assumeNotExists(path: Path, other: String): Path = assumeNotExists(path / other)
|
||||
|
||||
fun assumeReadable(path: Path): Path {
|
||||
withClue("sanity check: should be readable: ${path.absolute()}") {
|
||||
path.isReadable() shouldBe true
|
||||
}
|
||||
return path
|
||||
}
|
||||
return assumeReadable(path)
|
||||
}
|
||||
|
||||
fun assumeReadableFile(pathStr: String): Path = assumeReadableFile(Path(pathStr))
|
||||
fun assumeReadableFile(pathStr: String, other: Path): Path = assumeReadableFile(Path(pathStr), other)
|
||||
fun assumeReadableFile(pathStr: String, other: String): Path = assumeReadableFile(Path(pathStr), other)
|
||||
fun assumeReadableFile(path: Path, other: String): Path = assumeReadableFile(path / other)
|
||||
fun assumeReadableFile(path: Path, other: Path): Path = assumeReadableFile(path / other)
|
||||
|
||||
fun assumeDirectory(path: Path): Path {
|
||||
withClue("sanity check; should be directory: $path") {
|
||||
path.isDirectory() shouldBe true
|
||||
fun assumeReadableFile(path: Path): Path {
|
||||
withClue("sanity check: should be normal file: ${path.absolute()}") {
|
||||
path.isRegularFile() shouldBe true
|
||||
}
|
||||
return assumeReadable(path)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
fun assumeDirectory(pathStr: String): Path = assumeDirectory(Path(pathStr))
|
||||
fun assumeDirectory(path: Path, other: String): Path = assumeDirectory(path / other)
|
||||
fun assumeDirectory(pathStr: String, other: String): Path = assumeDirectory(Path(pathStr) / other)
|
||||
fun assumeDirectory(pathStr: String, other: Path): Path = assumeDirectory(Path(pathStr) / other)
|
||||
fun assumeReadableFile(pathStr: String): Path = assumeReadableFile(Path(pathStr))
|
||||
fun assumeReadableFile(pathStr: String, other: Path): Path = assumeReadableFile(Path(pathStr), other)
|
||||
fun assumeReadableFile(pathStr: String, other: String): Path = assumeReadableFile(Path(pathStr), other)
|
||||
fun assumeReadableFile(path: Path, other: String): Path = assumeReadableFile(path / other)
|
||||
fun assumeReadableFile(path: Path, other: Path): Path = assumeReadableFile(path / other)
|
||||
|
||||
fun assumeDirectory(path: Path): Path {
|
||||
withClue("sanity check; should be directory: $path") {
|
||||
path.isDirectory() shouldBe true
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
fun assumeDirectory(pathStr: String): Path = assumeDirectory(Path(pathStr))
|
||||
fun assumeDirectory(path: Path, other: String): Path = assumeDirectory(path / other)
|
||||
fun assumeDirectory(pathStr: String, other: String): Path = assumeDirectory(Path(pathStr) / other)
|
||||
fun assumeDirectory(pathStr: String, other: Path): Path = assumeDirectory(Path(pathStr) / other)
|
||||
|
||||
|
||||
@Deprecated("Directories are checked automatically at init.",
|
||||
ReplaceWith("/* nothing */"))
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun sanityCheckDirectories(workingDirName: String? = null) {
|
||||
}
|
||||
@Deprecated(
|
||||
"Directories are checked automatically at init.",
|
||||
ReplaceWith("/* nothing */")
|
||||
)
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun sanityCheckDirectories(workingDirName: String? = null) {
|
||||
}
|
||||
|
||||
}
|
@ -4,7 +4,7 @@ import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8tests.helpers.*
|
||||
import prog8tests.helpers.Helpers
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.div
|
||||
|
||||
@ -20,21 +20,21 @@ class PathsHelpersTests: FunSpec({
|
||||
context("WithOnePathArg") {
|
||||
|
||||
test("on non-existing path") {
|
||||
val path = fixturesDir / "i_do_not_exist"
|
||||
val path = Helpers.fixturesDir / "i_do_not_exist"
|
||||
withClue("should return the path") {
|
||||
assumeNotExists(path) shouldBe path
|
||||
Helpers.assumeNotExists(path) shouldBe path
|
||||
}
|
||||
}
|
||||
|
||||
test("on existing file") {
|
||||
shouldThrow<java.lang.AssertionError> {
|
||||
assumeNotExists(fixturesDir / "ast_simple_main.p8")
|
||||
Helpers.assumeNotExists(Helpers.fixturesDir / "ast_simple_main.p8")
|
||||
}
|
||||
}
|
||||
|
||||
test("on existing directory") {
|
||||
shouldThrow<java.lang.AssertionError> {
|
||||
assumeNotExists(fixturesDir)
|
||||
Helpers.assumeNotExists(Helpers.fixturesDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -42,22 +42,22 @@ class PathsHelpersTests: FunSpec({
|
||||
context("WithOneStringArg") {
|
||||
|
||||
test("on non-existing path") {
|
||||
val path = fixturesDir / "i_do_not_exist"
|
||||
val path = Helpers.fixturesDir / "i_do_not_exist"
|
||||
withClue("should return the path") {
|
||||
assumeNotExists("$path") shouldBe path
|
||||
Helpers.assumeNotExists("$path") shouldBe path
|
||||
}
|
||||
}
|
||||
|
||||
test("on existing file") {
|
||||
val path = fixturesDir / "ast_simple_main.p8"
|
||||
val path = Helpers.fixturesDir / "ast_simple_main.p8"
|
||||
shouldThrow<java.lang.AssertionError> {
|
||||
assumeNotExists("$path")
|
||||
Helpers.assumeNotExists("$path")
|
||||
}
|
||||
}
|
||||
|
||||
test("on existing directory") {
|
||||
shouldThrow<java.lang.AssertionError> {
|
||||
assumeNotExists("$fixturesDir")
|
||||
Helpers.assumeNotExists("${Helpers.fixturesDir}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -65,21 +65,21 @@ class PathsHelpersTests: FunSpec({
|
||||
context("WithPathAndStringArgs") {
|
||||
|
||||
test("on non-existing path") {
|
||||
val path = fixturesDir / "i_do_not_exist"
|
||||
val path = Helpers.fixturesDir / "i_do_not_exist"
|
||||
withClue("should return the path") {
|
||||
assumeNotExists(fixturesDir / "i_do_not_exist") shouldBe path
|
||||
Helpers.assumeNotExists(Helpers.fixturesDir / "i_do_not_exist") shouldBe path
|
||||
}
|
||||
}
|
||||
|
||||
test("on existing file") {
|
||||
shouldThrow<java.lang.AssertionError> {
|
||||
assumeNotExists(fixturesDir, "ast_simple_main.p8")
|
||||
Helpers.assumeNotExists(Helpers.fixturesDir, "ast_simple_main.p8")
|
||||
}
|
||||
}
|
||||
|
||||
test("on existing directory") {
|
||||
shouldThrow<java.lang.AssertionError> {
|
||||
assumeNotExists(fixturesDir, "..")
|
||||
Helpers.assumeNotExists(Helpers.fixturesDir, "..")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -89,46 +89,46 @@ class PathsHelpersTests: FunSpec({
|
||||
|
||||
context("WithOnePathArg") {
|
||||
test("on non-existing path") {
|
||||
val path = fixturesDir / "i_do_not_exist"
|
||||
val path = Helpers.fixturesDir / "i_do_not_exist"
|
||||
shouldThrow<AssertionError> {
|
||||
assumeDirectory(path)
|
||||
Helpers.assumeDirectory(path)
|
||||
}
|
||||
}
|
||||
|
||||
test("on existing file") {
|
||||
val path = fixturesDir / "ast_simple_main.p8"
|
||||
val path = Helpers.fixturesDir / "ast_simple_main.p8"
|
||||
shouldThrow<AssertionError> {
|
||||
assumeDirectory(path)
|
||||
Helpers.assumeDirectory(path)
|
||||
}
|
||||
}
|
||||
|
||||
test("on existing directory") {
|
||||
val path = workingDir
|
||||
val path = Helpers.workingDir
|
||||
withClue("should return the path") {
|
||||
assumeDirectory(path) shouldBe path
|
||||
Helpers.assumeDirectory(path) shouldBe path
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("WithOneStringArg") {
|
||||
test("on non-existing path") {
|
||||
val path = fixturesDir / "i_do_not_exist"
|
||||
val path = Helpers.fixturesDir / "i_do_not_exist"
|
||||
shouldThrow<AssertionError> {
|
||||
assumeDirectory("$path")
|
||||
Helpers.assumeDirectory("$path")
|
||||
}
|
||||
}
|
||||
|
||||
test("on existing file") {
|
||||
val path = fixturesDir / "ast_simple_main.p8"
|
||||
val path = Helpers.fixturesDir / "ast_simple_main.p8"
|
||||
shouldThrow<AssertionError> {
|
||||
assumeDirectory("$path")
|
||||
Helpers.assumeDirectory("$path")
|
||||
}
|
||||
}
|
||||
|
||||
test("on existing directory") {
|
||||
val path = workingDir
|
||||
val path = Helpers.workingDir
|
||||
withClue("should return the path") {
|
||||
assumeDirectory("$path") shouldBe path
|
||||
Helpers.assumeDirectory("$path") shouldBe path
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -136,20 +136,20 @@ class PathsHelpersTests: FunSpec({
|
||||
context("WithPathAndStringArgs") {
|
||||
test("on non-existing path") {
|
||||
shouldThrow<AssertionError> {
|
||||
assumeDirectory(fixturesDir, "i_do_not_exist")
|
||||
Helpers.assumeDirectory(Helpers.fixturesDir, "i_do_not_exist")
|
||||
}
|
||||
}
|
||||
|
||||
test("on existing file") {
|
||||
shouldThrow<AssertionError> {
|
||||
assumeDirectory(fixturesDir, "ast_simple_main.p8")
|
||||
Helpers.assumeDirectory(Helpers.fixturesDir, "ast_simple_main.p8")
|
||||
}
|
||||
}
|
||||
|
||||
test("on existing directory") {
|
||||
val path = workingDir / ".."
|
||||
val path = Helpers.workingDir / ".."
|
||||
withClue("should return resulting path") {
|
||||
assumeDirectory(workingDir / "..") shouldBe path
|
||||
Helpers.assumeDirectory(Helpers.workingDir / "..") shouldBe path
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -157,20 +157,20 @@ class PathsHelpersTests: FunSpec({
|
||||
context("WithStringAndStringArgs") {
|
||||
test("on non-existing path") {
|
||||
shouldThrow<AssertionError> {
|
||||
assumeDirectory("$fixturesDir", "i_do_not_exist")
|
||||
Helpers.assumeDirectory("${Helpers.fixturesDir}", "i_do_not_exist")
|
||||
}
|
||||
}
|
||||
|
||||
test("on existing file") {
|
||||
shouldThrow<AssertionError> {
|
||||
assumeDirectory("$fixturesDir", "ast_simple_main.p8")
|
||||
Helpers.assumeDirectory("${Helpers.fixturesDir}", "ast_simple_main.p8")
|
||||
}
|
||||
}
|
||||
|
||||
test("on existing directory") {
|
||||
val path = workingDir / ".."
|
||||
val path = Helpers.workingDir / ".."
|
||||
withClue("should return resulting path") {
|
||||
assumeDirectory(workingDir / "..") shouldBe path
|
||||
Helpers.assumeDirectory(Helpers.workingDir / "..") shouldBe path
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -178,20 +178,20 @@ class PathsHelpersTests: FunSpec({
|
||||
context("WithStringAndPathArgs") {
|
||||
test("on non-existing path") {
|
||||
shouldThrow<AssertionError> {
|
||||
assumeDirectory("$fixturesDir", Path("i_do_not_exist"))
|
||||
Helpers.assumeDirectory("${Helpers.fixturesDir}", Path("i_do_not_exist"))
|
||||
}
|
||||
}
|
||||
|
||||
test("on existing file") {
|
||||
shouldThrow<AssertionError> {
|
||||
assumeDirectory("$fixturesDir", Path("ast_simple_main.p8"))
|
||||
Helpers.assumeDirectory("${Helpers.fixturesDir}", Path("ast_simple_main.p8"))
|
||||
}
|
||||
}
|
||||
|
||||
test("on existing directory") {
|
||||
val path = workingDir / ".."
|
||||
val path = Helpers.workingDir / ".."
|
||||
withClue("should return resulting path") {
|
||||
assumeDirectory(workingDir / Path("..")) shouldBe path
|
||||
Helpers.assumeDirectory(Helpers.workingDir / Path("..")) shouldBe path
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -202,22 +202,22 @@ class PathsHelpersTests: FunSpec({
|
||||
context("WithOnePathArg") {
|
||||
|
||||
test("on non-existing path") {
|
||||
val path = fixturesDir / "i_do_not_exist"
|
||||
val path = Helpers.fixturesDir / "i_do_not_exist"
|
||||
shouldThrow<AssertionError> {
|
||||
assumeReadableFile(path)
|
||||
Helpers.assumeReadableFile(path)
|
||||
}
|
||||
}
|
||||
|
||||
test("on readable file") {
|
||||
val path = fixturesDir / "ast_simple_main.p8"
|
||||
val path = Helpers.fixturesDir / "ast_simple_main.p8"
|
||||
withClue("should return the path") {
|
||||
assumeReadableFile(path) shouldBe path
|
||||
Helpers.assumeReadableFile(path) shouldBe path
|
||||
}
|
||||
}
|
||||
|
||||
test("on directory") {
|
||||
shouldThrow<AssertionError> {
|
||||
assumeReadableFile(fixturesDir)
|
||||
Helpers.assumeReadableFile(Helpers.fixturesDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -225,22 +225,22 @@ class PathsHelpersTests: FunSpec({
|
||||
context("WithOneStringArg") {
|
||||
|
||||
test("on non-existing path") {
|
||||
val path = fixturesDir / "i_do_not_exist"
|
||||
val path = Helpers.fixturesDir / "i_do_not_exist"
|
||||
shouldThrow<AssertionError> {
|
||||
assumeReadableFile("$path")
|
||||
Helpers.assumeReadableFile("$path")
|
||||
}
|
||||
}
|
||||
|
||||
test("on readable file") {
|
||||
val path = fixturesDir / "ast_simple_main.p8"
|
||||
val path = Helpers.fixturesDir / "ast_simple_main.p8"
|
||||
withClue("should return the resulting path") {
|
||||
assumeReadableFile("$path") shouldBe path
|
||||
Helpers.assumeReadableFile("$path") shouldBe path
|
||||
}
|
||||
}
|
||||
|
||||
test("on directory") {
|
||||
shouldThrow<AssertionError> {
|
||||
assumeReadableFile("$fixturesDir")
|
||||
Helpers.assumeReadableFile("${Helpers.fixturesDir}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -248,20 +248,20 @@ class PathsHelpersTests: FunSpec({
|
||||
context("WithPathAndStringArgs") {
|
||||
test("on non-existing path") {
|
||||
shouldThrow<java.lang.AssertionError> {
|
||||
assumeReadableFile(fixturesDir, "i_do_not_exist")
|
||||
Helpers.assumeReadableFile(Helpers.fixturesDir, "i_do_not_exist")
|
||||
}
|
||||
}
|
||||
|
||||
test("on readable file") {
|
||||
val path = fixturesDir / "ast_simple_main.p8"
|
||||
val path = Helpers.fixturesDir / "ast_simple_main.p8"
|
||||
withClue("should return the resulting path") {
|
||||
assumeReadableFile(fixturesDir / "ast_simple_main.p8") shouldBe path
|
||||
Helpers.assumeReadableFile(Helpers.fixturesDir / "ast_simple_main.p8") shouldBe path
|
||||
}
|
||||
}
|
||||
|
||||
test("on directory") {
|
||||
shouldThrow<AssertionError> {
|
||||
assumeReadableFile(fixturesDir, "..")
|
||||
Helpers.assumeReadableFile(Helpers.fixturesDir, "..")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -269,19 +269,19 @@ class PathsHelpersTests: FunSpec({
|
||||
context("WithPathAndPathArgs") {
|
||||
test("on non-existing path") {
|
||||
shouldThrow<java.lang.AssertionError> {
|
||||
assumeReadableFile(fixturesDir, Path("i_do_not_exist"))
|
||||
Helpers.assumeReadableFile(Helpers.fixturesDir, Path("i_do_not_exist"))
|
||||
}
|
||||
}
|
||||
|
||||
test("on readable file") {
|
||||
withClue("should return the resulting path") {
|
||||
assumeReadableFile(fixturesDir / Path("ast_simple_main.p8")) shouldBe fixturesDir / "ast_simple_main.p8"
|
||||
Helpers.assumeReadableFile(Helpers.fixturesDir / Path("ast_simple_main.p8")) shouldBe Helpers.fixturesDir / "ast_simple_main.p8"
|
||||
}
|
||||
}
|
||||
|
||||
test("on directory") {
|
||||
shouldThrow<AssertionError> {
|
||||
assumeReadableFile(fixturesDir, Path(".."))
|
||||
Helpers.assumeReadableFile(Helpers.fixturesDir, Path(".."))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -289,19 +289,19 @@ class PathsHelpersTests: FunSpec({
|
||||
context("WithStringAndStringArgs") {
|
||||
test("on non-existing path") {
|
||||
shouldThrow<java.lang.AssertionError> {
|
||||
assumeReadableFile("$fixturesDir", "i_do_not_exist")
|
||||
Helpers.assumeReadableFile("${Helpers.fixturesDir}", "i_do_not_exist")
|
||||
}
|
||||
}
|
||||
|
||||
test("on readable file") {
|
||||
withClue("should return the resulting path") {
|
||||
assumeReadableFile(fixturesDir / "ast_simple_main.p8") shouldBe fixturesDir / "ast_simple_main.p8"
|
||||
Helpers.assumeReadableFile(Helpers.fixturesDir / "ast_simple_main.p8") shouldBe Helpers.fixturesDir / "ast_simple_main.p8"
|
||||
}
|
||||
}
|
||||
|
||||
test("on directory") {
|
||||
shouldThrow<AssertionError> {
|
||||
assumeReadableFile("$fixturesDir", "..")
|
||||
Helpers.assumeReadableFile("${Helpers.fixturesDir}", "..")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ compileTestKotlin {
|
||||
dependencies {
|
||||
implementation project(':codeCore')
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
implementation 'org.antlr:antlr4-runtime:4.9.3'
|
||||
implementation 'org.antlr:antlr4-runtime:4.10.1'
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.14"
|
||||
implementation project(':parser')
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user