Compare commits

...

96 Commits
v8.0 ... v8.1

Author SHA1 Message Date
6ed5f04970 version 8.1 2022-05-25 20:00:26 +02:00
b459b09b2f vm: fix comparison datatype error; primes.p8 works again 2022-05-24 18:26:07 +02:00
3f5877dbcc vm: fix array iteration 2022-05-23 21:24:36 +02:00
e659b91c4d vm: fix storezm/storezx instructions 2022-05-23 21:01:02 +02:00
e09f054058 vm: implemented in-place bit rotate instructions 2022-05-23 20:30:25 +02:00
b646f50265 vm: implemented in-memory bit shift instructions 2022-05-23 20:15:20 +02:00
0a48ef3030 vm: just use new register instead of trying to (ab)use reg 0 2022-05-22 23:38:46 +02:00
ba614801ee cleanup 2022-05-22 23:11:22 +02:00
fd6eb47e68 added inlining certain trivial non-asm subroutine calls 2022-05-22 20:22:09 +02:00
e69aeb8b98 added warning about shadowing variables 2022-05-22 17:34:08 +02:00
26ea1da146 vm: add in-place bitwise or,and,xor 2022-05-20 20:50:27 +02:00
c9e8c7a290 vm: add in-place division 2022-05-19 23:38:16 +02:00
5e4eb92443 vm: add in-place multiply 2022-05-19 23:18:54 +02:00
461b6499ef vm: add in-place add/sub 2022-05-19 22:54:50 +02:00
c769920b6e vm: fix signed divide 2022-05-19 22:24:57 +02:00
181b98ef9e vm: implemented some self-assign instructions 2022-05-18 22:15:42 +02:00
4e1184a400 vm: added some of the sin cos tables in math.p8 2022-05-17 22:56:00 +02:00
e52d9e3210 vm: split off assignment codegen to its own file 2022-05-17 22:38:31 +02:00
dc6475c91b vm: fixed non-byte array indexing 2022-05-17 18:53:33 +02:00
52f9956e92 clarify use of direct-memory in functions that modify in place such as rol/swap 2022-05-16 22:41:31 +02:00
0bf00d1ca4 c64/c128 targets: perform cleanup at program exit such as re-enabling run-stop key and character set switching. 2022-05-15 16:44:26 +02:00
d1a707df57 fix assigning a pointer (uword) to string not copying the correct memory 2022-05-15 16:10:58 +02:00
4dc9b45297 vm: fixed string comparisons, added missing vm string module 2022-05-13 23:10:13 +02:00
6e31eebfb5 vm: ifElse codegen uses proper branching instructions now 2022-05-12 21:26:17 +02:00
a7df828932 vm: codegen uses INCM/DECM if possible 2022-05-12 19:40:31 +02:00
517cf61d11 vm: limit int instructions to just 2 register args 2022-05-11 22:36:47 +02:00
4be7bc8323 vm: limit float instructions to just 2 register args 2022-05-11 22:09:46 +02:00
74c05d00a9 vm: fix comparison operator codegen for floats 2022-05-11 17:07:21 +02:00
677613d30a vm: expressiongen: use resultRegister arg instead of allocating new leftResultReg 2022-05-11 15:58:55 +02:00
bacba629a5 vm: use shift-one instructions in codegen 2022-05-11 15:50:51 +02:00
14e36f1362 vm: fix assignment to array 2022-05-11 15:26:54 +02:00
d43ad849d1 vm: actually use the store-zero instructions in codegen 2022-05-11 15:18:36 +02:00
627aa61184 clean up subroutine inlining, basis for new try 2022-05-09 15:42:58 +02:00
dad5b17ac8 fix regression compiler crash in string comparison 2022-05-08 13:47:24 +02:00
fef52c0112 automatically convert multi-compare expression (if X==1 or X==2..) to contaiment check if X in [1,2,..] 2022-05-08 13:21:34 +02:00
8c4765b386 vm: support non-unary functions in pipe expressions 2022-05-07 20:42:05 +02:00
7c121bfc01 first steps to support multiple args in pipe expressions 2022-05-07 19:00:47 +02:00
942c5cc04b fix crash when optimizing pipe expression too aggressively 2022-05-07 17:29:36 +02:00
348b3036ff now correctly accepts "xxx" * constexpr (where constexpr is not just a single const number) 2022-05-05 23:21:20 +02:00
09d3451d9d vm: accept %asmbinary (but it is eventually ignored in code execution) 2022-05-05 21:43:31 +02:00
b1a49e5f29 vm: implement rest of float instructions 2022-05-04 22:31:45 +02:00
da01a5b4dc vm: implement float to integer cast, any, all, reverse 2022-05-04 22:08:21 +02:00
3f9cdd9b56 vm: fix mul and div instructions 2022-05-04 01:10:59 +02:00
0f9e87d7bb fixed compiler crash when casting float to integer, fixed float to int cast value error on cx16 2022-05-03 23:43:38 +02:00
0869789214 vm: implement float type casts to integer types 2022-05-02 23:38:32 +02:00
10c8cc35c5 vm: implement float divide multiply sub add 2022-05-02 21:53:43 +02:00
30c2e3e8ff vm: fix comparisons codegen 2022-05-02 21:32:45 +02:00
86cc2f1075 vm: implementing more fp instructions 2022-05-02 21:06:14 +02:00
fa357a450b clarify license 2022-05-02 19:46:08 +02:00
b32641db87 remove syscall() builtin functions
vm code can do this via inline assembly
2022-05-01 00:41:30 +02:00
0ee790969d vm: allow inline "assembly" 2022-04-30 23:24:25 +02:00
7844ace934 vm: implementing floating-point 2022-04-29 22:27:02 +02:00
f4993d6e5d vm: fix instruction type checks 2022-04-28 22:19:46 +02:00
0fab806f36 vm: some preparations for floating point 2022-04-27 17:45:58 +02:00
be2113d291 vm: starting to implement floating point instructions 2022-04-26 21:25:59 +02:00
625d5b2313 vm: some preparations for floating point 2022-04-26 21:08:32 +02:00
6471c0c536 upgrade antlr to 4.10.1 2022-04-24 23:29:15 +02:00
47c53fa60a todo 2022-04-23 20:44:59 +02:00
cf50e4f6ec vm: printing of numbers now via conv module.
assigning strings now converted to strcopy function call in the compiler ast.
2022-04-23 02:15:51 +02:00
7eea97d741 - floats: remove all floating point builtin functions and move them to the floats module instead 2022-04-22 00:45:54 +02:00
88b55ab93e vm: add abs() and fix 6502 abs() code. 2022-04-18 21:20:17 +02:00
ee36d47c27 vm: added cmp() and most of the status-branch instructions 2022-04-18 19:59:48 +02:00
6f2fdbe447 added %option merge, also fixed problem with unit test building in newer IntelliJ version 2022-04-15 22:38:32 +02:00
0f36be0001 vm: simple optimizations for +/-/*/div with constants 2022-04-14 22:42:25 +02:00
0f4a197e34 improve ast check on pipe expressions 2022-04-14 00:49:06 +02:00
7dbff5b9e6 abs: remove support for floats. Use floats.fabs() instead.
this solves: can't use abs() etc in pipe expression because return type depends on argument type
2022-04-14 00:38:31 +02:00
220246278a removed sum(), max(), min(). abs() now always returns uword type.
This greatly simplifies internal handling of builtin functions by always having one fixed return type.
2022-04-14 00:21:16 +02:00
349e5a15e9 min/max give proper error for string args
als implmented more vm builtin functions/syscalls
2022-04-13 23:09:25 +02:00
bf7f4bba7b doc 2022-04-13 20:43:07 +02:00
ab1766a559 moved all *integer* builtin trig functions (sin8u, cos8u etc) as regular asmsubs in math module 2022-04-13 00:27:35 +02:00
51bf33040a vm: add many builtin functions 2022-04-11 22:39:33 +02:00
a2c7273801 vm: use memory load instruction better 2022-04-11 20:55:06 +02:00
ec6ac5bf24 vm: added swap() 2022-04-11 01:50:47 +02:00
ec7501782d vm: added 1-bit variants of lsr/lsl opcodes 2022-04-11 00:25:00 +02:00
890b1c2d52 more readable 2022-04-10 22:31:37 +02:00
c25d07259a add block directive options to PtBlock 2022-04-10 21:37:47 +02:00
c960246eee add some utility methods to PtNode to find the defining subroutine/block 2022-04-10 21:20:01 +02:00
a01aee3111 add sideEffects boolean to PtBuiltinFunctionCall 2022-04-10 21:08:54 +02:00
e2e951efdf constValue(expr) convenience function added for new Ast expression nodes 2022-04-10 18:45:33 +02:00
3f6393f732 PtNumber can now be compared 2022-04-10 17:48:03 +02:00
b6eb343234 moving string escaping out of antlr project 2022-04-10 17:31:30 +02:00
207a7e5160 move operator lists 2022-04-10 13:24:17 +02:00
a0face4a28 vm: implementing rol/ror 2022-04-09 11:13:49 +02:00
a8cf9f5cc4 vm: syscalls can now return value 2022-04-05 20:46:34 +02:00
461b38e653 add -vm option to load an existing p8virt file directly in the virtual machine 2022-04-05 18:42:31 +02:00
8e4c0f7c22 vm: add sorting and reverse functions, fix value arg out of range errors 2022-04-05 17:48:49 +02:00
d78bfcc35c vm: more optimal code when array index is constant value 2022-04-05 00:19:37 +02:00
2b7c09e6ee vm: more optimal code for loops ending on 0 2022-04-05 00:08:38 +02:00
036d9dbe59 got rid of unnecessary cast of boolean expressions by making their type dynamically adjust to byte or word 2022-04-04 23:43:55 +02:00
1d342cc6af optimize cx16 textio.setcc()/setcc2() 2022-04-04 22:23:06 +02:00
62b32b2211 todos 2022-04-03 22:56:13 +02:00
ae45ce517e cleanups 2022-04-03 17:33:50 +02:00
5b3ccab7dc vm: support noreinit option 2022-04-03 17:19:50 +02:00
95f16c38a9 removed 'aug' property in PtAssignment , it wasn't used for anything 2022-04-03 15:56:14 +02:00
d616cb283b vm: implemented Pipe expression 2022-04-03 15:25:32 +02:00
9874fe2c23 fix superfluous printing of WARN/ERROR words 2022-04-02 22:16:47 +02:00
159 changed files with 6920 additions and 3771 deletions

View File

@ -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>

View File

@ -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.

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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"
}

View File

@ -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>

View 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("")
}

View File

@ -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(

View 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
}

View File

@ -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")
}
}

View File

@ -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)
}

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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))

View File

@ -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) {

View File

@ -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()) {

View File

@ -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()

View 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
}
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View 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
}

View File

@ -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)

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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

View 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
}}
}
}

View File

@ -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
}}
}
}

View File

@ -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

View 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++
}
}
}

View 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
}}
}
}

View File

@ -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
}
}

View File

@ -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
}}
}
}

View 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. Dont 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, doesnt return a value (so cant 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, doesnt return a value (so cant 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, doesnt return a value (so cant 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 dont have to call this explicitly and can just write string1 = string2
; but this function is useful if youre 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++
}
}
}

View File

@ -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
}}
}
}

View File

@ -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
}}
}
}

View File

@ -1 +1 @@
8.0
8.1

View File

@ -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
}

View File

@ -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)
}

View File

@ -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) {

View File

@ -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

View File

@ -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
}

View File

@ -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)
}

View File

@ -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

View File

@ -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
}
}

View File

@ -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
}
}
}

View File

@ -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

View File

@ -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) {

View File

@ -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)
}

View File

@ -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.*

View File

@ -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

View File

@ -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))
}
}
}

View File

@ -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)

View File

@ -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 }

View File

@ -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
}
})

View File

@ -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({

View File

@ -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",

View File

@ -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
}
}
}

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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>()
}
})

View File

@ -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"
}
})

View 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
}
})

View File

@ -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'

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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()
}
}
"""

View File

@ -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

View File

@ -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) }
}

View File

@ -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() {

View File

@ -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"
}
})

View File

@ -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

View File

@ -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 {

View File

@ -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()

View File

@ -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()
}

View File

@ -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) {
}
}

View File

@ -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}", "..")
}
}
}

View File

@ -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