Compare commits

..
52 Commits
Author SHA1 Message Date
Irmen de Jong 8dbfb8ab76 move community note to start page 2024-09-20 20:12:33 +02:00
Irmen de Jong e29ff1c848 fix name redefinition check for multi-declarations 2024-09-20 19:55:32 +02:00
Irmen de Jong 585f6ffc9b version 10.4.1 2024-09-20 18:43:08 +02:00
Irmen de Jong 46b94c17d6 comment 2024-09-20 17:33:00 +02:00
Irmen de Jong 7af8007447 Merge remote-tracking branch 'origin/master' 2024-09-20 17:28:55 +02:00
markjreed 16a2b2f566 Add 24-bit integer-to-float routine and floating-point jiffy clock reader (#150)
* feat: add routine to convert 24-bit integer from A/X/Y to float in FAC1

* fix: remove duplicate definition

* fix: shift to appropriate exponent
2024-09-20 17:28:37 +02:00
Irmen de Jong ea2a90c3c5 nah 2024-09-19 23:09:59 +02:00
Irmen de Jong 5cda750e5e improve error message for undefined symbol in when choices 2024-09-18 23:00:03 +02:00
Irmen de Jong 4e143d45c8 fix warning 2024-09-18 22:04:25 +02:00
Irmen de Jong 4c50980d81 new skeletons dump 2024-09-18 18:45:43 +02:00
Irmen de Jong 2954f5f04d Add clear_screen and set_screen_mode to gfx_lores. Fix boolean draw vs color param in some monogfx routines. Elaborate some docs. 2024-09-17 22:19:47 +02:00
Irmen de Jong cac4c1eb1e improve callgraph unused subroutine check for routines called from inline asm 2024-09-16 21:49:30 +02:00
Irmen de Jong 0b1f30d98c no more span overdraws in graphics disc routines 2024-09-15 15:38:33 +02:00
Irmen de Jong c7b1e8d772 fixed a variable scopedname issue where it took the fully scoped name instead of just the local name
this made 64tass not strip out that code if it was unused
2024-09-14 23:17:26 +02:00
Irmen de Jong a4f7512d44 oops, was fixed 2024-09-14 22:33:20 +02:00
Irmen de Jong 0d3ad80659 retain type of consts better to avoid precision loss
this also fixed a difference in const calculation where the result could differ if you were using optimzations or not.
2024-09-14 21:06:21 +02:00
Irmen de Jong aba1a73e28 actually use any @zp etc tags on subroutine parameters 2024-09-14 17:42:13 +02:00
Irmen de Jong dca31b2ca3 added gfx_lores module for cx16 for optimized graphics routines for lores 256c screen mode
currently contains a new line() routine
2024-09-14 15:30:39 +02:00
Irmen de Jong 0cb378ca31 added emudbg.cpu_cycles() and emudbg.reset_cpu_cycles() 2024-09-13 23:03:14 +02:00
Irmen de Jong cf551d2cc7 tweak containment check even more 2024-09-13 00:02:26 +02:00
Irmen de Jong ac0c8a68f6 IR: Improve codegen for for loops downto 0/1 2024-09-12 23:00:32 +02:00
Irmen de Jong 5986dcdd2f add new containment check codegen for IR 2024-09-12 22:04:20 +02:00
Irmen de Jong 6be6eb2227 tweak diskio to not always include unused subroutine internal_f_tell() in resulting program 2024-09-11 19:51:53 +02:00
Irmen de Jong d34015eec5 fix gfx2.fill() vera CTRL corruption 2024-09-11 19:10:45 +02:00
Irmen de Jong 255c5bfaca improve containment check for few values 2024-09-11 03:24:30 +02:00
Irmen de Jong 01c6754928 get rid of problematic common-subexpression optimization 2024-09-11 01:10:42 +02:00
Irmen de Jong 8eaf884f69 improve codegen for for loops downto 0,1 when start value is not const 2024-09-10 23:54:44 +02:00
Irmen de Jong 699a2bb7ab improved codegen for for loops downto 0 2024-09-10 21:33:57 +02:00
Irmen de Jong 4a2dcd20d1 fix the "x<2" optimization made a few commits ago to only work on unsigned 2024-09-09 23:06:36 +02:00
Irmen de Jong 4e98fb75d6 support assigning multiple return flags from asmsub in 6502 codegen 2024-09-09 22:56:40 +02:00
Irmen de Jong 64e66e732f cx16/circles example now uses gfx2 2024-09-08 22:52:46 +02:00
Irmen de Jong 7aec627f6b add optimization if x==0 or x==1 -> if x<2 2024-09-08 22:39:48 +02:00
Irmen de Jong 59a2fec176 fix IR containment check 2024-09-08 21:49:13 +02:00
Irmen de Jong edc5a5a94f improve data driven unit tests to use kotest withData() 2024-09-08 16:55:08 +02:00
Irmen de Jong c5b7edad82 added memsizer unit tests 2024-09-08 15:24:47 +02:00
Irmen de Jong 124ffac4e4 readme 2024-09-08 11:50:18 +02:00
Irmen de Jong 6d2a36fb2b testcase improvement 2024-09-06 22:51:26 +02:00
Irmen de Jong 28b43b3e1d added cx16.EXTAPI_kbd_leds definition (new in kernal R48) 2024-09-06 20:47:49 +02:00
Irmen de Jong f7feaf158d added cx16.mouse_present() routine to check for presence of mouse 2024-09-06 18:21:13 +02:00
Irmen de Jong 2396f707c6 fix bug in codegen for certain array lookups using word typed index value (i.e. via a pointer variable) 2024-09-06 18:11:41 +02:00
Irmen de Jong d4d8e1b1ba comment about implementation in life example 2024-09-06 16:39:44 +02:00
Irmen de Jong 44fec2c729 some additional last minute optimization to life example 2024-09-06 15:56:41 +02:00
Irmen de Jong a80a6913e3 some additional last minute tweaks to life example 2024-09-06 15:42:49 +02:00
Irmen de Jong 0eac04c220 added cx16/life.p8 example (Conway's game of life) 2024-09-06 15:21:29 +02:00
Irmen de Jong 29dd758302 Fix compiler crash in for loops with just 1 iteration 2024-09-05 21:26:46 +02:00
Irmen de Jong 5c45adc7f0 graphics module on x16 now uses kernal (R48 or newer) support for drawing circles and ovals 2024-09-05 20:27:12 +02:00
Irmen de Jong ad22cf08cd todo 2024-09-03 17:46:32 +02:00
Irmen de Jong 2c2ae64194 replace java Stack by kotlin ArrayDeque 2024-09-02 00:15:28 +02:00
Irmen de Jong 97c2dadd16 doc update 2024-09-01 20:55:43 +02:00
Irmen de Jong b36e1e3baf change sprite.hide() : now disables sprite instead of moving it offscreen.
added sprite.show() to re-enable it (with z-order 3, as all sprites have by default in this module)
added sprite.zdepth() to set a custom z-depth.
2024-09-01 20:55:43 +02:00
Irmen de Jong 2da35fec17 remove requirement to end subroutine with an EOL, so oneliners are now possible
main { sub start() { cx16.r0++ cx16.r1++ } }
2024-09-01 20:55:43 +02:00
Irmen de Jong bdeac74cfc removed the -nostrictbool compiler option
boolean types and bytes are no longer implicitly interchangeable using this option
2024-09-01 20:53:39 +02:00
123 changed files with 1925 additions and 1145 deletions

No files matched your search

+23
View File
@@ -0,0 +1,23 @@
<component name="libraryTable">
<library name="io.kotest.framework.datatest" type="repository">
<properties maven-id="io.kotest:kotest-framework-datatest:5.9.1" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-datatest/5.9.1/kotest-framework-datatest-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-datatest-jvm/5.9.1/kotest-framework-datatest-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.23/kotlin-stdlib-1.9.23.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.8.0/kotlinx-coroutines-core-jvm-1.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.9.1/kotest-common-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.9.23/kotlin-reflect-1.9.23.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.9.1/kotest-framework-api-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.9.1/kotest-assertions-shared-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.9.1/kotest-assertions-api-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.8.0/kotlinx-coroutines-jdk8-1.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-test-jvm/1.8.0/kotlinx-coroutines-test-jvm-1.8.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
+2
View File
@@ -29,6 +29,8 @@ How to get it/build it
- Or, if you want/need a bleeding edge development version, you can:
- download a build artifact zipfile from a recent [github action build](https://github.com/irmen/prog8/actions).
- you can also compile it yourself from source. [Instructions here](https://prog8.readthedocs.io/en/latest/compiling.html).
Note that if you are not using *gradle* to build it, you might have to perform some manual
tasks once to make it compile fully. These are explained in the linked instructions.
- Alternatively, you can also install the compiler as a package on some linux distros:
- Arch (via AUR): [`prog8`](https://aur.archlinux.org/packages/prog8)
+9 -8
View File
@@ -3,7 +3,8 @@ package prog8.code
import prog8.code.ast.*
import prog8.code.core.*
import prog8.code.target.VMTarget
import java.util.*
import kotlin.collections.ArrayDeque
class SymbolTableMaker(private val program: PtProgram, private val options: CompilationOptions) {
fun make(): SymbolTable {
@@ -13,8 +14,8 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
st.add(StNode(it.key, StNodeType.BUILTINFUNC, PtIdentifier(it.key, it.value.returnType ?: DataType.UNDEFINED, Position.DUMMY)))
}
val scopestack = Stack<StNode>()
scopestack.push(st)
val scopestack = ArrayDeque<StNode>()
scopestack.add(st)
program.children.forEach {
addToSt(it, scopestack)
}
@@ -35,7 +36,7 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
return st
}
private fun addToSt(node: PtNode, scope: Stack<StNode>) {
private fun addToSt(node: PtNode, scope: ArrayDeque<StNode>) {
val stNode = when(node) {
is PtAsmSub -> {
val parameters = node.parameters.map { StRomSubParameter(it.first, it.second.type) }
@@ -104,7 +105,7 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
val size = (node.args[1] as PtNumber).number.toUInt()
val align = (node.args[2] as PtNumber).number.toUInt()
// don't add memory slabs in nested scope, just put them in the top level of the ST
scope.firstElement().add(StMemorySlab("prog8_memoryslab_$slabname", size, align, node))
scope.first().add(StMemorySlab("prog8_memoryslab_$slabname", size, align, node))
}
null
}
@@ -112,14 +113,14 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
}
if(stNode!=null) {
scope.peek().add(stNode)
scope.push(stNode)
scope.last().add(stNode)
scope.add(stNode)
}
node.children.forEach {
addToSt(it, scope)
}
if(stNode!=null)
scope.pop()
scope.removeLast()
}
private fun makeInitialArray(value: PtArray): List<StArrayElement> {
+24 -11
View File
@@ -1,7 +1,7 @@
package prog8.code.ast
import prog8.code.core.*
import java.util.*
import java.util.Objects
import kotlin.math.abs
import kotlin.math.truncate
@@ -42,7 +42,18 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
else
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 PtContainmentCheck -> {
if(other !is PtContainmentCheck || other.type != type || !(other.needle isSameAs needle))
false
else {
if(haystackHeapVar!=null)
other.haystackHeapVar!=null && other.haystackHeapVar!! isSameAs haystackHeapVar!!
else if(haystackValues!=null)
other.haystackValues!=null && other.haystackValues!! isSameAs haystackValues!!
else
false
}
}
is PtIdentifier -> other is PtIdentifier && other.type==type && other.name==name
is PtIrRegister -> other is PtIrRegister && other.type==type && other.register==register
is PtMemoryByte -> other is PtMemoryByte && other.address isSameAs address
@@ -195,10 +206,17 @@ class PtBinaryExpression(val operator: String, type: DataType, position: Positio
class PtContainmentCheck(position: Position): PtExpression(DataType.BOOL, position) {
val element: PtExpression
val needle: PtExpression
get() = children[0] as PtExpression
val iterable: PtIdentifier
get() = children[1] as PtIdentifier
val haystackHeapVar: PtIdentifier?
get() = children[1] as? PtIdentifier
val haystackValues: PtArray?
get() = children[1] as? PtArray
companion object {
val MAX_SIZE_FOR_INLINE_CHECKS_BYTE = 5
val MAX_SIZE_FOR_INLINE_CHECKS_WORD = 4
}
}
@@ -227,12 +245,6 @@ class PtMemoryByte(position: Position) : PtExpression(DataType.UBYTE, position)
class PtBool(val value: Boolean, position: Position) : PtExpression(DataType.BOOL, position) {
companion object {
fun fromNumber(number: Number, position: Position): PtBool =
PtBool(number != 0.0, position)
}
override fun hashCode(): Int = Objects.hash(type, value)
override fun equals(other: Any?): Boolean {
@@ -302,6 +314,7 @@ class PtRange(type: DataType, position: Position) : PtExpression(type, position)
fun toConstantIntegerRange(): IntProgression? {
fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
return when {
fromVal == toVal -> fromVal .. toVal
fromVal <= toVal -> when {
stepVal <= 0 -> IntRange.EMPTY
stepVal == 1 -> fromVal..toVal
@@ -5,6 +5,7 @@ import prog8.code.core.*
sealed interface IPtSubroutine {
val name: String
val scopedName: String
}
class PtAsmSub(
@@ -27,7 +27,6 @@ class CompilationOptions(val output: OutputType,
var slabsHighBank: Int? = null,
var slabsGolden: Boolean = false,
var splitWordArrays: Boolean = false,
var strictBool: Boolean = true,
var breakpointCpuInstruction: String? = null,
var outputDir: Path = Path(""),
var symbolDefs: Map<String, String> = emptyMap()
+2 -115
View File
@@ -10,9 +10,8 @@ fun optimizeIntermediateAst(program: PtProgram, options: CompilationOptions, st:
if (!options.optimize)
return
while (errors.noErrors() &&
(optimizeCommonSubExpressions(program, errors)
+ optimizeBitTest(program, options)
+ optimizeAssignTargets(program, st, errors)) > 0
(optimizeBitTest(program, options)
+ optimizeAssignTargets(program, st, errors)) > 0
) {
// keep rolling
}
@@ -28,97 +27,6 @@ private fun walkAst(root: PtNode, act: (node: PtNode, depth: Int) -> Boolean) {
}
private var tempVarCounter = 0
private fun optimizeCommonSubExpressions(program: PtProgram, errors: IErrorReporter): Int {
fun extractableSubExpr(expr: PtExpression): Boolean {
if(expr is PtArrayIndexer && expr.index.isSimple())
return false
if (expr is PtMemoryByte && expr.address.isSimple())
return false
val result = if(expr is PtBinaryExpression)
expr.type !in ByteDatatypes ||
!(expr.left.isSimple() && expr.right.isSimple()) ||
(expr.operator !in LogicalOperators && expr.operator !in BitwiseOperators)
else if (expr is PtArrayIndexer && expr.type !in ByteDatatypes)
true
else
!expr.isSimple()
return result
}
// for each Binaryexpression, recurse to find a common subexpression pair therein.
val commons = mutableMapOf<PtBinaryExpression, Pair<PtExpression, PtExpression>>()
walkAst(program) { node: PtNode, depth: Int ->
if(node is PtBinaryExpression) {
val subExpressions = mutableListOf<PtExpression>()
walkAst(node.left) { subNode: PtNode, subDepth: Int ->
if (subNode is PtExpression) {
if(extractableSubExpr(subNode)) subExpressions.add(subNode)
true
} else false
}
walkAst(node.right) { subNode: PtNode, subDepth: Int ->
if (subNode is PtExpression) {
if(extractableSubExpr(subNode)) subExpressions.add(subNode)
true
} else false
}
outer@for (first in subExpressions) {
for (second in subExpressions) {
if (first!==second && first isSameAs second) {
commons[node] = first to second
break@outer // do only 1 replacement at a time per binaryexpression
}
}
}
false
} else true
}
// replace common subexpressions by a temp variable that is assigned only once.
// TODO: check for commonalities across multiple separate expressions in the current scope, not only inside a single line
commons.forEach { binexpr, (occurrence1, occurrence2) ->
val (stmtContainer, stmt) = findContainingStatements(binexpr)
val occurrence1idx = occurrence1.parent.children.indexOf(occurrence1)
val occurrence2idx = occurrence2.parent.children.indexOf(occurrence2)
val containerScopedName = findScopeName(stmtContainer)
tempVarCounter++
val tempvarName = "prog8_subexprvar_$tempVarCounter"
// TODO: some tempvars could be reused, if they are from different lines
val datatype = occurrence1.type
val singleReplacement1 = PtIdentifier("$containerScopedName.$tempvarName", datatype, occurrence1.position)
val singleReplacement2 = PtIdentifier("$containerScopedName.$tempvarName", datatype, occurrence2.position)
occurrence1.parent.children[occurrence1idx] = singleReplacement1
singleReplacement1.parent = occurrence1.parent
occurrence2.parent.children[occurrence2idx] = singleReplacement2
singleReplacement2.parent = occurrence2.parent
val tempassign = PtAssignment(binexpr.position).also { assign ->
assign.add(PtAssignTarget(false, binexpr.position).also { tgt->
tgt.add(PtIdentifier("$containerScopedName.$tempvarName", datatype, binexpr.position))
})
assign.add(occurrence1)
occurrence1.parent = assign
}
stmtContainer.children.add(stmtContainer.children.indexOf(stmt), tempassign)
tempassign.parent = stmtContainer
val tempvar = PtVariable(tempvarName, datatype, ZeropageWish.NOT_IN_ZEROPAGE, null, null, binexpr.position)
stmtContainer.add(0, tempvar)
tempvar.parent = stmtContainer
// errors.info("common subexpressions replaced by a tempvar, maybe simplify the expression manually", binexpr.position)
}
return commons.size
}
private fun optimizeAssignTargets(program: PtProgram, st: SymbolTable, errors: IErrorReporter): Int {
var changes = 0
walkAst(program) { node: PtNode, depth: Int ->
@@ -235,24 +143,3 @@ internal fun isSame(identifier: PtIdentifier, type: DataType, returnedRegister:
}
return false // there are no identifiers directly corresponding to cpu registers
}
internal fun findScopeName(node: PtNode): String {
var parent=node
while(parent !is PtNamedNode)
parent = parent.parent
return parent.scopedName
}
internal fun findContainingStatements(node: PtNode): Pair<PtNode, PtNode> { // returns (parentstatementcontainer, childstatement)
var parent = node.parent
var child = node
while(true) {
if(parent is IPtStatementContainer) {
return parent to child
}
child=parent
parent=parent.parent
}
}
+1
View File
@@ -28,6 +28,7 @@ dependencies {
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0"
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.9.1'
testImplementation 'io.kotest:kotest-framework-datatest:5.9.1'
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
+1
View File
@@ -14,5 +14,6 @@
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
<orderEntry type="library" name="io.kotest.framework.datatest" level="project" />
</component>
</module>
@@ -9,7 +9,7 @@ import prog8.code.ast.*
import prog8.code.core.*
import prog8.code.target.Cx16Target
import prog8.codegen.cpu6502.assignment.*
import java.util.*
import kotlin.collections.ArrayDeque
import kotlin.io.path.Path
import kotlin.io.path.writeLines
@@ -232,7 +232,7 @@ class AsmGen6502Internal (
private val functioncallAsmGen = FunctionCallAsmGen(program, this)
private val programGen = ProgramAndVarsGen(program, options, errors, symbolTable, functioncallAsmGen, this, allocator, zeropage)
private val anyExprGen = AnyExprAsmGen(this)
private val assignmentAsmGen = AssignmentAsmGen(program, this, options, anyExprGen, allocator)
private val assignmentAsmGen = AssignmentAsmGen(program, this, anyExprGen, allocator)
private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this, assignmentAsmGen)
private val ifElseAsmgen = IfElseAsmGen(program, symbolTable, this, allocator, assignmentAsmGen, errors)
@@ -374,7 +374,7 @@ class AsmGen6502Internal (
val name = asmSymbolName(identifier.name)
// see if we're inside a subroutine, if so, remove the whole prefix and just make the variable name locally scoped (64tass scopes it to the proper .proc block)
val subName = identifier.definingSub()?.scopedName
val subName = identifier.definingISub()?.scopedName
return if (subName != null && name.length>subName.length && name.startsWith(subName) && name[subName.length] == '.')
name.drop(subName.length + 1)
else
@@ -385,14 +385,14 @@ class AsmGen6502Internal (
val name = asmVariableName(identifier.name)
// see if we're inside a subroutine, if so, remove the whole prefix and just make the variable name locally scoped (64tass scopes it to the proper .proc block)
val subName = identifier.definingSub()?.scopedName
val subName = identifier.definingISub()?.scopedName
return if (subName != null && name.length>subName.length && name.startsWith(subName) && name[subName.length] == '.')
name.drop(subName.length+1)
else
name
}
fun asmVariableName(st: StNode, scope: PtSub?): String {
fun asmVariableName(st: StNode, scope: IPtSubroutine?): String {
val name = asmVariableName(st.scopedName)
if(scope==null)
return name
@@ -772,7 +772,7 @@ class AsmGen6502Internal (
private fun translate(stmt: PtRepeatLoop) {
val endLabel = makeLabel("repeatend")
loopEndLabels.push(endLabel)
loopEndLabels.add(endLabel)
when (stmt.count) {
is PtNumber -> {
@@ -816,7 +816,7 @@ class AsmGen6502Internal (
}
}
loopEndLabels.pop()
loopEndLabels.removeLast()
}
private fun repeatWordCount(iterations: Int, stmt: PtRepeatLoop) {
@@ -37,7 +37,7 @@ internal class ForLoopsAsmGen(
val endLabel = asmgen.makeLabel("for_end")
val modifiedLabel = asmgen.makeLabel("for_modified")
val modifiedLabel2 = asmgen.makeLabel("for_modifiedb")
asmgen.loopEndLabels.push(endLabel)
asmgen.loopEndLabels.add(endLabel)
val stepsize=range.step.asConstInteger()!!
if(stepsize < -1) {
@@ -48,25 +48,43 @@ internal class ForLoopsAsmGen(
when(iterableDt) {
DataType.ARRAY_B, DataType.ARRAY_UB -> {
if (stepsize==1 || stepsize==-1) {
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt))
if (stepsize==-1 && range.to.asConstInteger()==0) {
// simple loop downto 0 step -1
asmgen.out(loopLabel)
asmgen.translate(stmt.statements)
asmgen.out("""
dec $varname
lda $varname
cmp #255
bne $loopLabel""")
}
else if (stepsize==-1 && range.to.asConstInteger()==1) {
// simple loop downto 1 step -1
asmgen.out(loopLabel)
asmgen.translate(stmt.statements)
asmgen.out("""
dec $varname
bne $loopLabel""")
}
else if (stepsize==1 || stepsize==-1) {
// bytes array, step 1 or -1
val incdec = if(stepsize==1) "inc" else "dec"
// loop over byte range via loopvar
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt))
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A, false)
// pre-check for end already reached
if(iterableDt==DataType.ARRAY_B) {
asmgen.out(" sta $modifiedLabel+1")
if(stepsize<0)
if(stepsize<0) {
asmgen.out("""
clc
sbc $varname
bvc +
eor #${'$'}80
+ bpl $endLabel""")
}
else
asmgen.out("""
sec
@@ -75,12 +93,13 @@ internal class ForLoopsAsmGen(
eor #${'$'}80
+ bmi $endLabel""")
} else {
if(stepsize<0)
if(stepsize<0) {
asmgen.out("""
cmp $varname
beq +
bcs $endLabel
+""")
}
else
asmgen.out(" cmp $varname | bcc $endLabel")
asmgen.out(" sta $modifiedLabel+1")
@@ -100,8 +119,6 @@ $modifiedLabel cmp #0 ; modified
// bytes, step >= 2 or <= -2
// loop over byte range via loopvar
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt))
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A, false)
// pre-check for end already reached
if(iterableDt==DataType.ARRAY_B) {
@@ -155,58 +172,85 @@ $modifiedLabel cmp #0 ; modified
}
}
DataType.ARRAY_W, DataType.ARRAY_UW -> {
when {
val varname = asmgen.asmVariableName(stmt.variable)
assignLoopvarWord(stmt, range)
if(stepsize==-1 && range.to.asConstInteger()==0) {
// simple loop downto 0 step -1 (words)
asmgen.out(loopLabel)
asmgen.translate(stmt.statements)
asmgen.out("""
lda $varname
bne ++
lda $varname+1
beq $endLabel
+ lda $varname
bne +
dec $varname+1
+ dec $varname""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
}
else if (stepsize==-1 && range.to.asConstInteger()==1) {
// simple loop downto 1 step -1 (words)
asmgen.out(loopLabel)
asmgen.translate(stmt.statements)
asmgen.out("""
lda $varname
cmp #1
bne +
lda $varname+1
beq $endLabel
+ lda $varname
bne +
dec $varname+1
+ dec $varname""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
}
else if (stepsize == 1 || stepsize == -1) {
// words, step 1 or -1
stepsize == 1 || stepsize == -1 -> {
val varname = asmgen.asmVariableName(stmt.variable)
assignLoopvarWord(stmt, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
$loopLabel""")
asmgen.translate(stmt.statements)
asmgen.out("""
asmgen.translate(stmt.statements)
asmgen.out("""
lda $varname+1
$modifiedLabel cmp #0 ; modified
bne +
lda $varname
$modifiedLabel2 cmp #0 ; modified
beq $endLabel""")
if(stepsize==1) {
asmgen.out("""
if(stepsize==1) {
asmgen.out("""
+ inc $varname
bne $loopLabel
inc $varname+1""")
asmgen.jmp(loopLabel)
} else {
asmgen.out("""
asmgen.jmp(loopLabel)
} else {
asmgen.out("""
+ lda $varname
bne +
dec $varname+1
+ dec $varname""")
asmgen.jmp(loopLabel)
}
asmgen.out(endLabel)
asmgen.jmp(loopLabel)
}
stepsize > 0 -> {
// (u)words, step >= 2
val varname = asmgen.asmVariableName(stmt.variable)
assignLoopvarWord(stmt, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
asmgen.out(endLabel)
}
else if (stepsize > 0) {
// (u)words, step >= 2
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
$loopLabel""")
asmgen.translate(stmt.statements)
asmgen.translate(stmt.statements)
if (iterableDt == DataType.ARRAY_UW) {
asmgen.out("""
if (iterableDt == DataType.ARRAY_UW) {
asmgen.out("""
lda $varname
clc
adc #<$stepsize
@@ -222,8 +266,8 @@ $modifiedLabel2 lda #0 ; modified
bcc $endLabel
bcs $loopLabel
$endLabel""")
} else {
asmgen.out("""
} else {
asmgen.out("""
lda $varname
clc
adc #<$stepsize
@@ -239,22 +283,20 @@ $modifiedLabel lda #0 ; modified
eor #$80
+ bpl $loopLabel
$endLabel""")
}
}
else -> {
}
else {
// (u)words, step <= -2
val varname = asmgen.asmVariableName(stmt.variable)
assignLoopvarWord(stmt, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
// (u)words, step <= -2
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
$loopLabel""")
asmgen.translate(stmt.statements)
asmgen.translate(stmt.statements)
asmgen.out("""
asmgen.out("""
lda $varname
sec
sbc #<${stepsize.absoluteValue}
@@ -271,13 +313,12 @@ $modifiedLabel sbc #0 ; modified
eor #$80
+ bpl $loopLabel
$endLabel""")
}
}
}
else -> throw AssemblyError("range expression can only be byte or word")
}
asmgen.loopEndLabels.pop()
asmgen.loopEndLabels.removeLast()
}
private fun precheckFromToWord(iterableDt: DataType, stepsize: Int, fromVar: String, endLabel: String) {
@@ -333,7 +374,7 @@ $endLabel""")
private fun translateForOverIterableVar(stmt: PtForLoop, iterableDt: DataType, ident: PtIdentifier) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
asmgen.loopEndLabels.add(endLabel)
val iterableName = asmgen.asmVariableName(ident)
val numElements = when(val symbol = asmgen.symbolTable.lookup(ident.name)) {
is StStaticVariable -> symbol.length!!
@@ -477,17 +518,19 @@ $loopLabel sty $indexVar
}
else -> throw AssemblyError("can't iterate over $iterableDt")
}
asmgen.loopEndLabels.pop()
asmgen.loopEndLabels.removeLast()
}
private fun translateForOverConstRange(stmt: PtForLoop, iterableDt: DataType, range: IntProgression) {
if (range.isEmpty() || range.step==0)
throw AssemblyError("empty range or step 0")
if(iterableDt==DataType.ARRAY_B || iterableDt==DataType.ARRAY_UB) {
if(range.last==range.first) return translateForSimpleByteRangeAsc(stmt, range)
if(range.step==1 && range.last>range.first) return translateForSimpleByteRangeAsc(stmt, range)
if(range.step==-1 && range.last<range.first) return translateForSimpleByteRangeDesc(stmt, range)
if(range.step==-1 && range.last<range.first) return translateForSimpleByteRangeDesc(stmt, range, iterableDt==DataType.ARRAY_UB)
}
else if(iterableDt==DataType.ARRAY_W || iterableDt==DataType.ARRAY_UW) {
if(range.last==range.first) return translateForSimpleWordRangeAsc(stmt, range)
if(range.step==1 && range.last>range.first) return translateForSimpleWordRangeAsc(stmt, range)
if(range.step==-1 && range.last<range.first) return translateForSimpleWordRangeDesc(stmt, range)
}
@@ -495,7 +538,7 @@ $loopLabel sty $indexVar
// not one of the easy cases, generate more complex code...
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
asmgen.loopEndLabels.add(endLabel)
when(iterableDt) {
DataType.ARRAY_B, DataType.ARRAY_UB -> {
// loop over byte range via loopvar, step >= 2 or <= -2
@@ -507,7 +550,7 @@ $loopLabel""")
asmgen.translate(stmt.statements)
when (range.step) {
0, 1, -1 -> {
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $range ${stmt.position}")
}
2 -> {
if(range.last==255 || range.last==254) {
@@ -601,13 +644,13 @@ $loopLabel""")
}
else -> throw AssemblyError("range expression can only be byte or word")
}
asmgen.loopEndLabels.pop()
asmgen.loopEndLabels.removeLast()
}
private fun translateForSimpleByteRangeAsc(stmt: PtForLoop, range: IntProgression) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
asmgen.loopEndLabels.add(endLabel)
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
lda #${range.first}
@@ -627,50 +670,50 @@ $endLabel""")
bne $loopLabel
$endLabel""")
}
asmgen.loopEndLabels.pop()
asmgen.loopEndLabels.removeLast()
}
private fun translateForSimpleByteRangeDesc(stmt: PtForLoop, range: IntProgression) {
private fun translateForSimpleByteRangeDesc(stmt: PtForLoop, range: IntProgression, unsigned: Boolean) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
lda #${range.first}
sta $varname
lda #${range.first}
sta $varname
$loopLabel""")
asmgen.translate(stmt.statements)
when (range.last) {
0 -> {
asmgen.out("""
lda $varname
beq $endLabel
dec $varname""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
if(!unsigned || range.first<=127) {
asmgen.out("""
dec $varname
bpl $loopLabel""")
} else {
asmgen.out("""
dec $varname
lda $varname
cmp #255
bne $loopLabel""")
}
}
1 -> {
asmgen.out("""
dec $varname
bne $loopLabel
$endLabel""")
bne $loopLabel""")
}
else -> {
asmgen.out("""
dec $varname
lda $varname
cmp #${range.last-1}
bne $loopLabel
$endLabel""")
bne $loopLabel""")
}
}
asmgen.loopEndLabels.pop()
}
private fun translateForSimpleWordRangeAsc(stmt: PtForLoop, range: IntProgression) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
asmgen.loopEndLabels.add(endLabel)
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
lda #<${range.first}
@@ -691,13 +734,13 @@ $loopLabel""")
inc $varname+1""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
asmgen.loopEndLabels.pop()
asmgen.loopEndLabels.removeLast()
}
private fun translateForSimpleWordRangeDesc(stmt: PtForLoop, range: IntProgression) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
asmgen.loopEndLabels.add(endLabel)
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
lda #<${range.first}
@@ -706,20 +749,29 @@ $loopLabel""")
sty $varname+1
$loopLabel""")
asmgen.translate(stmt.statements)
if(range.last==0) {
asmgen.out("""
lda $varname
bne ++
lda $varname+1
beq $endLabel""")
} else {
asmgen.out("""
lda $varname
cmp #<${range.last}
bne +
lda $varname+1
cmp #>${range.last}
beq $endLabel""")
}
asmgen.out("""
lda $varname
cmp #<${range.last}
bne +
lda $varname+1
cmp #>${range.last}
beq $endLabel
+ lda $varname
bne +
dec $varname+1
+ dec $varname""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
asmgen.loopEndLabels.pop()
asmgen.loopEndLabels.removeLast()
}
private fun assignLoopvarWord(stmt: PtForLoop, range: PtRange) =
@@ -82,7 +82,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
is PtAddressOf -> false
is PtIdentifier -> false
is PtIrRegister -> false
is PtMemoryByte -> true // TODO might not actually need extra registers if the value has to end up in A
is PtMemoryByte -> arg.address !is PtNumber && arg.address !is PtIdentifier
is PtNumber -> false
is PtBool -> false
else -> true
@@ -205,7 +205,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
if(value is PtIdentifier) {
val addr = PtAddressOf(Position.DUMMY)
addr.add(value)
addr.parent = sub as PtNode
addr.parent = scope as PtNode
AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target)
} else {
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
@@ -213,7 +213,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
} else {
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
}
asmgen.translateNormalAssignment(AsmAssignment(src, target, program.memsizer, Position.DUMMY), scope)
asmgen.translateNormalAssignment(AsmAssignment(src, target, program.memsizer, target.position), scope)
}
return RegisterOrStatusflag(register, null)
}
@@ -11,11 +11,12 @@ import prog8.codegen.cpu6502.VariableAllocator
import prog8.codegen.cpu6502.returnsWhatWhere
internal class AssignmentAsmGen(private val program: PtProgram,
private val asmgen: AsmGen6502Internal,
private val options: CompilationOptions,
private val anyExprGen: AnyExprAsmGen,
private val allocator: VariableAllocator) {
internal class AssignmentAsmGen(
private val program: PtProgram,
private val asmgen: AsmGen6502Internal,
private val anyExprGen: AnyExprAsmGen,
private val allocator: VariableAllocator
) {
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, asmgen, allocator)
fun translate(assignment: PtAssignment) {
@@ -47,6 +48,26 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.translate(values)
val assignmentTargets = assignment.children.dropLast(1)
if(sub.returns.size==assignmentTargets.size) {
// because we can only handle integer results right now we can just zip() it all up
val (statusFlagResults, registersResults) = sub.returns.zip(assignmentTargets).partition { it.first.register.statusflag!=null }
if (statusFlagResults.isEmpty())
assignRegisterResults(registersResults)
else if(registersResults.isEmpty())
assignOnlyTheStatusFlagsResults(false, statusFlagResults)
else
assignStatusFlagsAndRegistersResults(statusFlagResults, registersResults)
} else {
throw AssemblyError("number of values and targets don't match")
}
}
private fun assignStatusFlagsAndRegistersResults(
statusFlagResults: List<Pair<StRomSubParameter, PtNode>>,
registersResults: List<Pair<StRomSubParameter, PtNode>>
) {
fun needsToSaveA(registersResults: List<Pair<StRomSubParameter, PtNode>>): Boolean =
if(registersResults.isEmpty())
false
@@ -55,124 +76,123 @@ internal class AssignmentAsmGen(private val program: PtProgram,
else
true
fun assignCarryFlagResult(target: PtAssignTarget, saveA: Boolean) {
if(saveA) asmgen.out(" pha")
asmgen.out(" lda #0 | rol a")
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
assignRegisterByte(tgt, CpuRegister.A, false, false)
if(saveA) asmgen.out(" pla")
if(registersResults.all {
val tgt = it.second as PtAssignTarget
tgt.void || tgt.identifier!=null})
{
// all other results are just stored into identifiers directly so first handle those
// (simple store instructions that don't modify the carry flag)
assignRegisterResults(registersResults)
assignOnlyTheStatusFlagsResults(false, statusFlagResults)
} else {
val saveA = needsToSaveA(registersResults)
assignOnlyTheStatusFlagsResults(saveA, statusFlagResults)
assignRegisterResults(registersResults)
}
}
private fun assignOnlyTheStatusFlagsResults(saveA: Boolean, statusFlagResults: List<Pair<StRomSubParameter, PtNode>>) {
// assigning flags to their variables targets requires load-instructions that destroy flags
// so if there's more than 1, we need to save and restore the flags
val saveFlags = statusFlagResults.size>1
fun hasFlag(statusFlagResults: List<Pair<StRomSubParameter, PtNode>>, flag: Statusflag): PtAssignTarget? {
for ((returns, target) in statusFlagResults) {
if(returns.register.statusflag!! == flag)
return target as PtAssignTarget
}
return null
}
fun assignZeroFlagResult(target: PtAssignTarget, saveA: Boolean) {
if(saveA) asmgen.out(" pha")
asmgen.out("""
val targetCarry = hasFlag(statusFlagResults, Statusflag.Pc)
val targetZero = hasFlag(statusFlagResults, Statusflag.Pz)
val targetNeg = hasFlag(statusFlagResults, Statusflag.Pn)
val targetOverflow = hasFlag(statusFlagResults, Statusflag.Pv)
if(saveA) asmgen.out(" pha")
if(targetZero!=null && !targetZero.void)
assignZeroFlagResult(targetZero, saveFlags)
if(targetNeg!=null && !targetNeg.void)
assignNegativeFlagResult(targetNeg, saveFlags)
if(targetCarry!=null && !targetCarry.void)
assignCarryFlagResult(targetCarry)
if(targetOverflow!=null && !targetOverflow.void)
assignOverflowFlagResult(targetOverflow)
if(saveA) asmgen.out(" pla")
}
private fun assignRegisterResults(registersResults: List<Pair<StRomSubParameter, PtNode>>) {
registersResults.forEach { (returns, target) ->
target as PtAssignTarget
if(!target.void) {
val targetIdent = target.identifier
val targetMem = target.memory
if(targetIdent!=null || targetMem!=null) {
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
when(returns.type) {
in ByteDatatypesWithBoolean -> {
if(returns.register.registerOrPair in Cx16VirtualRegisters) {
assignVirtualRegister(tgt, returns.register.registerOrPair!!)
} else {
assignRegisterByte(tgt, returns.register.registerOrPair!!.asCpuRegister(), false, false)
}
}
in WordDatatypes -> {
assignRegisterpairWord(tgt, returns.register.registerOrPair!!)
}
else -> throw AssemblyError("weird dt")
}
}
else TODO("array target for multi-value assignment") // Not done yet due to result register clobbering complexity
}
}
}
private fun assignCarryFlagResult(target: PtAssignTarget) {
// overflow is not clobbered so no need to save/restore it
asmgen.out(" lda #0 | rol a")
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
assignRegisterByte(tgt, CpuRegister.A, false, false)
}
private fun assignZeroFlagResult(target: PtAssignTarget, saveFlags: Boolean) {
if(saveFlags) asmgen.out(" php")
asmgen.out("""
beq +
lda #0
beq ++
+ lda #1
+""")
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
assignRegisterByte(tgt, CpuRegister.A, false, false)
if(saveA) asmgen.out(" pla")
}
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
assignRegisterByte(tgt, CpuRegister.A, false, false)
if(saveFlags) asmgen.out(" plp")
}
fun assignNegativeFlagResult(target: PtAssignTarget, saveA: Boolean) {
if(saveA) asmgen.out(" pha")
asmgen.out("""
private fun assignNegativeFlagResult(target: PtAssignTarget, saveFlags: Boolean) {
if(saveFlags) asmgen.out(" php")
asmgen.out("""
bmi +
lda #0
beq ++
+ lda #1
+""")
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
assignRegisterByte(tgt, CpuRegister.A, false, false)
if(saveA) asmgen.out(" pla")
}
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
assignRegisterByte(tgt, CpuRegister.A, false, false)
if(saveFlags) asmgen.out(" plp")
}
fun assignOverflowFlagResult(target: PtAssignTarget, saveA: Boolean) {
if(saveA) asmgen.out(" pha")
asmgen.out("""
private fun assignOverflowFlagResult(target: PtAssignTarget) {
// overflow is not clobbered so no need to save/restore it
asmgen.out("""
bvs +
lda #0
beq ++
+ lda #1
+""")
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
assignRegisterByte(tgt, CpuRegister.A, false, false)
if(saveA) asmgen.out(" pla")
}
fun assignRegisterResults(registersResults: List<Pair<StRomSubParameter, PtNode>>) {
registersResults.forEach { (returns, target) ->
target as PtAssignTarget
if(!target.void) {
val targetIdent = target.identifier
val targetMem = target.memory
if(targetIdent!=null || targetMem!=null) {
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
when(returns.type) {
in ByteDatatypesWithBoolean -> {
if(returns.register.registerOrPair in Cx16VirtualRegisters) {
assignVirtualRegister(tgt, returns.register.registerOrPair!!)
} else {
assignRegisterByte(tgt, returns.register.registerOrPair!!.asCpuRegister(), false, false)
}
}
in WordDatatypes -> {
assignRegisterpairWord(tgt, returns.register.registerOrPair!!)
}
else -> throw AssemblyError("weird dt")
}
}
else TODO("array target for multi-value assignment") // Not done yet due to result register clobbering complexity
}
}
}
val assignmentTargets = assignment.children.dropLast(1)
if(sub.returns.size==assignmentTargets.size) {
// because we can only handle integer results right now we can just zip() it all up
val (statusFlagResults, registersResults) = sub.returns.zip(assignmentTargets).partition { it.first.register.statusflag!=null }
if(statusFlagResults.isNotEmpty()) {
if(statusFlagResults.size>1)
TODO("handle multiple status flag results")
val (returns, target) = statusFlagResults.single()
target as PtAssignTarget
if(target.void) {
// forget about the Carry status flag, only assign the normal return values
assignRegisterResults(registersResults)
return
}
if(registersResults.all {
val tgt = it.second as PtAssignTarget
tgt.void || tgt.identifier!=null})
{
// all other results are just stored into identifiers directly so first handle those
// (simple store instructions that don't modify the carry flag)
assignRegisterResults(registersResults)
return when(returns.register.statusflag!!) {
Statusflag.Pc -> assignCarryFlagResult(target, false)
Statusflag.Pz -> assignZeroFlagResult(target, false)
Statusflag.Pv -> assignOverflowFlagResult(target, false)
Statusflag.Pn -> assignNegativeFlagResult(target, false)
}
}
val saveA = needsToSaveA(registersResults)
when(returns.register.statusflag!!) {
Statusflag.Pc -> assignCarryFlagResult(target, saveA)
Statusflag.Pz -> assignZeroFlagResult(target, saveA)
Statusflag.Pv -> assignOverflowFlagResult(target, saveA)
Statusflag.Pn -> assignNegativeFlagResult(target, saveA)
}
}
assignRegisterResults(registersResults)
} else {
throw AssemblyError("number of values and targets don't match")
}
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
assignRegisterByte(tgt, CpuRegister.A, false, false)
}
fun translateNormalAssignment(assign: AsmAssignment, scope: IPtSubroutine?) {
when(assign.source.kind) {
SourceStorageKind.LITERALBOOLEAN -> {
@@ -201,7 +221,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
val variable = assign.source.asmVarname
when (assign.target.datatype) {
DataType.BOOL -> {
if (assign.source.datatype == DataType.BOOL || !options.strictBool) assignVariableByte(assign.target, variable)
if (assign.source.datatype == DataType.BOOL) assignVariableByte(assign.target, variable)
else throw AssemblyError("assigning non-bool variable to boolean, should have been typecasted")
}
DataType.UBYTE, DataType.BYTE -> assignVariableByte(assign.target, variable)
@@ -335,7 +355,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
return
}
is PtNumber -> {
val indexValue = index.number.toString()
val indexValue = index.number.toInt().toString()
asmgen.out("""
lda $pointer
sta P8ZP_SCRATCH_W2
@@ -384,7 +404,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
return
}
is PtNumber -> {
val indexValue = index.number.toString()
val indexValue = index.number.toInt().toString()
asmgen.out("""
tax
lda $pointer
@@ -1483,11 +1503,16 @@ internal class AssignmentAsmGen(private val program: PtProgram,
// special optimization for bytevalue +/- pointervar[y] (actually: bytevalue +/- @(address) )
val address = mem.address as? PtBinaryExpression
if(address is PtBinaryExpression) {
val constOffset = address.right.asConstInteger()
// check that the offset is actually a byte (so that it fits in a single register)
if(constOffset==null && address.right.type !in ByteDatatypes)
return false
if(constOffset!=null && constOffset !in -128..255)
return false
val ptrVar = address.left as? PtIdentifier
if(ptrVar!=null && asmgen.isZpVar(ptrVar)) {
assignExpressionToRegister(value, RegisterOrPair.A, false)
val pointername = asmgen.asmVariableName(ptrVar)
val constOffset = address.right.asConstInteger()
if (constOffset != null && constOffset < 256) {
// we have value + @(zpptr + 255), or value - @(zpptr+255)
asmgen.out(" ldy #$constOffset")
@@ -1805,9 +1830,56 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
private fun containmentCheckIntoA(containment: PtContainmentCheck) {
val elementDt = containment.element.type
val symbol = asmgen.symbolTable.lookup(containment.iterable.name)!!
val symbolName = asmgen.asmVariableName(symbol, containment.definingSub())
val elementDt = containment.needle.type
if(containment.haystackValues!=null) {
val haystack = containment.haystackValues!!.children.map {
if(it is PtBool) it.asInt()
else (it as PtNumber).number.toInt()
}
when(elementDt) {
in ByteDatatypesWithBoolean -> {
require(haystack.size in 0..PtContainmentCheck.MAX_SIZE_FOR_INLINE_CHECKS_BYTE)
assignExpressionToRegister(containment.needle, RegisterOrPair.A, elementDt == DataType.BYTE)
for(number in haystack) {
asmgen.out("""
cmp #$number
beq +""")
}
asmgen.out("""
lda #0
beq ++
+ lda #1
+""")
}
in WordDatatypes -> {
require(haystack.size in 0..PtContainmentCheck.MAX_SIZE_FOR_INLINE_CHECKS_WORD)
assignExpressionToRegister(containment.needle, RegisterOrPair.AY, elementDt == DataType.WORD)
val gottemLabel = asmgen.makeLabel("gottem")
val endLabel = asmgen.makeLabel("end")
for(number in haystack) {
asmgen.out("""
cmp #<$number
bne +
cpy #>$number
beq $gottemLabel
+ """)
}
asmgen.out("""
lda #0
beq $endLabel
$gottemLabel lda #1
$endLabel""")
}
DataType.FLOAT -> throw AssemblyError("containmentchecks for floats should always be done on an array variable with subroutine")
else -> throw AssemblyError("weird dt $elementDt")
}
return
}
val symbol = asmgen.symbolTable.lookup(containment.haystackHeapVar!!.name)!!
val symbolName = asmgen.asmVariableName(symbol, containment.definingISub())
val (dt, numElements) = when(symbol) {
is StStaticVariable -> symbol.dt to symbol.length!!
is StMemVar -> symbol.dt to symbol.length!!
@@ -1815,7 +1887,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
when(dt) {
DataType.STR -> {
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt == DataType.BYTE)
assignExpressionToRegister(containment.needle, RegisterOrPair.A, elementDt == DataType.BYTE)
asmgen.out(" pha") // need to keep the scratch var safe so we have to do it in this order
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingISub(), containment.position,"P8ZP_SCRATCH_W1"), symbolName, null, null)
asmgen.out(" pla")
@@ -1823,13 +1895,13 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.out(" jsr prog8_lib.containment_bytearray")
}
DataType.ARRAY_F -> {
assignExpressionToRegister(containment.element, RegisterOrPair.FAC1, true)
assignExpressionToRegister(containment.needle, RegisterOrPair.FAC1, true)
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingISub(), containment.position, "P8ZP_SCRATCH_W1"), symbolName, null, null)
asmgen.out(" ldy #$numElements")
asmgen.out(" jsr floats.containment_floatarray")
}
DataType.ARRAY_B, DataType.ARRAY_UB -> {
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt == DataType.BYTE)
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.ARRAY_BOOL -> {
assignExpressionToRegister(containment.needle, RegisterOrPair.A, elementDt == DataType.BYTE)
asmgen.out(" pha") // need to keep the scratch var safe so we have to do it in this order
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingISub(), containment.position, "P8ZP_SCRATCH_W1"), symbolName, null, null)
asmgen.out(" pla")
@@ -1837,7 +1909,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.out(" jsr prog8_lib.containment_bytearray")
}
DataType.ARRAY_W, DataType.ARRAY_UW -> {
assignExpressionToVariable(containment.element, "P8ZP_SCRATCH_W1", elementDt)
assignExpressionToVariable(containment.needle, "P8ZP_SCRATCH_W1", elementDt)
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingISub(), containment.position, "P8ZP_SCRATCH_W2"), symbolName, null, null)
asmgen.out(" ldy #$numElements")
asmgen.out(" jsr prog8_lib.containment_wordarray")
@@ -225,7 +225,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
SourceStorageKind.LITERALNUMBER -> inplacemodificationSplitWordWithLiteralval(target.asmVarname, target.datatype, index, operator, value.number!!.number.toInt())
else -> {
// TODO: more optimized code for VARIABLE, REGISTER, MEMORY, ARRAY, EXPRESSION in the case of split-word arrays
val scope = target.origAstTarget?.definingSub()
val scope = target.origAstTarget?.definingISub()
val regTarget = AsmAssignTarget.fromRegisters(RegisterOrPair.R0, false, target.position, scope, asmgen)
val assignToReg = AsmAssignment(value, regTarget, program.memsizer, target.position)
assignmentAsmGen.translateNormalAssignment(assignToReg, scope)
+1
View File
@@ -31,6 +31,7 @@ dependencies {
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.9.1'
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
testImplementation 'io.kotest:kotest-framework-datatest:5.9.1'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
@@ -14,6 +14,7 @@
<orderEntry type="module" module-name="intermediate" />
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
<orderEntry type="library" name="io.kotest.framework.datatest" level="project" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
</component>
</module>
@@ -57,10 +57,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
RegisterOrPair.XY -> IRInstruction(Opcode.LOADHXY, IRDataType.WORD, reg1=regNum)
in Cx16VirtualRegisters -> IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1=regNum, labelSymbol = "cx16.${returns.register.registerOrPair.toString().lowercase()}")
null -> {
when(returns.register.statusflag) {
Statusflag.Pc -> IRInstruction(Opcode.LOADHA, IRDataType.BYTE, reg1=regNum)
else -> throw AssemblyError("weird statusflag as returnvalue")
}
TODO("assign CPU status flag ${returns.register.statusflag!!}")
}
else -> throw AssemblyError("cannot load register")
}
@@ -580,6 +580,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val arr = (arg as? PtArrayIndexer)
val index = arr?.index?.asConstInteger()
if(arr!=null && index!=null) {
if(arr.splitWords) TODO("IR rol/ror on split words array")
val variable = arr.variable.name
val itemsize = codeGen.program.memsizer.memorySize(arr.type)
addInstr(result, IRInstruction(opcodeMemAndReg.first, vmDt, labelSymbol = variable, symbolOffset = index*itemsize), null)
@@ -168,55 +168,93 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
private fun translate(check: PtContainmentCheck): ExpressionCodeResult {
val elementDt = check.needle.type
val result = mutableListOf<IRCodeChunkBase>()
when(check.iterable.type) {
if(check.haystackValues!=null) {
val haystack = check.haystackValues!!.children.map {
if(it is PtBool) it.asInt()
else (it as PtNumber).number.toInt()
}
when(elementDt) {
in IntegerDatatypesWithBoolean -> {
if (elementDt in ByteDatatypesWithBoolean) require(haystack.size in 0..PtContainmentCheck.MAX_SIZE_FOR_INLINE_CHECKS_BYTE)
if (elementDt in WordDatatypes) require(haystack.size in 0..PtContainmentCheck.MAX_SIZE_FOR_INLINE_CHECKS_WORD)
val gottemLabel = codeGen.createLabelName()
val endLabel = codeGen.createLabelName()
val elementTr = translateExpression(check.needle)
addToResult(result, elementTr, elementTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
for(value in haystack){
it += IRInstruction(Opcode.CMPI, irType(elementDt), elementTr.resultReg, immediate = value)
it += IRInstruction(Opcode.BSTEQ, labelSymbol = gottemLabel)
}
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, elementTr.resultReg, immediate = 0)
it += IRInstruction(Opcode.JUMP, labelSymbol = endLabel)
}
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, elementTr.resultReg, immediate = 1), gottemLabel)
result += IRCodeChunk(endLabel, null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
DataType.FLOAT -> throw AssemblyError("containmentchecks for floats should always be done on an array variable with subroutine")
else -> throw AssemblyError("weird dt $elementDt")
}
}
val haystackVar = check.haystackHeapVar!!
when(haystackVar.type) {
DataType.STR -> {
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val elementTr = translateExpression(check.element)
val elementTr = translateExpression(check.needle)
addToResult(result, elementTr, elementTr.resultReg, -1)
val iterableTr = translateExpression(check.iterable)
val iterableTr = translateExpression(haystackVar)
addToResult(result, iterableTr, iterableTr.resultReg, -1)
result += codeGen.makeSyscall(IMSyscall.STRING_CONTAINS, listOf(IRDataType.BYTE to elementTr.resultReg, IRDataType.WORD to iterableTr.resultReg), IRDataType.BYTE to elementTr.resultReg)
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=elementTr.resultReg, immediate = 0), null)
return ExpressionCodeResult(result, IRDataType.BYTE, elementTr.resultReg, -1)
}
DataType.ARRAY_UB, DataType.ARRAY_B -> {
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val elementTr = translateExpression(check.element)
val elementTr = translateExpression(check.needle)
addToResult(result, elementTr, elementTr.resultReg, -1)
val iterableTr = translateExpression(check.iterable)
val iterableTr = translateExpression(haystackVar)
addToResult(result, iterableTr, iterableTr.resultReg, -1)
val lengthReg = codeGen.registers.nextFree()
val iterableLength = codeGen.symbolTable.getLength(check.iterable.name)
val iterableLength = codeGen.symbolTable.getLength(haystackVar.name)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterableLength!!), null)
result += codeGen.makeSyscall(IMSyscall.BYTEARRAY_CONTAINS, listOf(IRDataType.BYTE to elementTr.resultReg, IRDataType.WORD to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to elementTr.resultReg)
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=elementTr.resultReg, immediate = 0), null)
return ExpressionCodeResult(result, IRDataType.BYTE, elementTr.resultReg, -1)
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val elementTr = translateExpression(check.element)
val elementTr = translateExpression(check.needle)
addToResult(result, elementTr, elementTr.resultReg, -1)
val iterableTr = translateExpression(check.iterable)
val iterableTr = translateExpression(haystackVar)
addToResult(result, iterableTr, iterableTr.resultReg, -1)
val lengthReg = codeGen.registers.nextFree()
val iterableLength = codeGen.symbolTable.getLength(check.iterable.name)
val iterableLength = codeGen.symbolTable.getLength(haystackVar.name)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterableLength!!), null)
result += codeGen.makeSyscall(IMSyscall.WORDARRAY_CONTAINS, listOf(IRDataType.WORD to elementTr.resultReg, IRDataType.WORD to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to elementTr.resultReg)
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=elementTr.resultReg, immediate = 0), null)
return ExpressionCodeResult(result, IRDataType.BYTE, elementTr.resultReg, -1)
}
DataType.ARRAY_F -> {
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val elementTr = translateExpression(check.element)
val elementTr = translateExpression(check.needle)
addToResult(result, elementTr, -1, elementTr.resultFpReg)
val iterableTr = translateExpression(check.iterable)
val iterableTr = translateExpression(haystackVar)
addToResult(result, iterableTr, iterableTr.resultReg, -1)
val lengthReg = codeGen.registers.nextFree()
val resultReg = codeGen.registers.nextFree()
val iterableLength = codeGen.symbolTable.getLength(check.iterable.name)
val iterableLength = codeGen.symbolTable.getLength(haystackVar.name)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterableLength!!), null)
result += codeGen.makeSyscall(IMSyscall.FLOATARRAY_CONTAINS, listOf(IRDataType.FLOAT to elementTr.resultFpReg, IRDataType.WORD to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to resultReg)
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=resultReg, immediate = 0), null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
else -> throw AssemblyError("weird iterable dt ${check.iterable.type} for ${check.iterable.name}")
else -> throw AssemblyError("weird iterable dt ${haystackVar.type} for ${haystackVar.name}")
}
}
@@ -487,49 +487,76 @@ class IRCodeGen(
val loopLabel = createLabelName()
val result = mutableListOf<IRCodeChunkBase>()
val toTr = expressionEval.translateExpression(iterable.to)
addToResult(result, toTr, toTr.resultReg, -1)
val fromTr = expressionEval.translateExpression(iterable.from)
addToResult(result, fromTr, fromTr.resultReg, -1)
val labelAfterFor = createLabelName()
val precheckInstruction = if(loopvarDt in SignedDatatypes) {
if(step>0)
IRInstruction(Opcode.BGTSR, loopvarDtIr, fromTr.resultReg, toTr.resultReg, labelSymbol=labelAfterFor)
else
IRInstruction(Opcode.BGTSR, loopvarDtIr, toTr.resultReg, fromTr.resultReg, labelSymbol=labelAfterFor)
} else {
if(step>0)
IRInstruction(Opcode.BGTR, loopvarDtIr, fromTr.resultReg, toTr.resultReg, labelSymbol=labelAfterFor)
else
IRInstruction(Opcode.BGTR, loopvarDtIr, toTr.resultReg, fromTr.resultReg, labelSymbol=labelAfterFor)
}
addInstr(result, precheckInstruction, null)
addInstr(result, IRInstruction(Opcode.STOREM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol=loopvarSymbol), null)
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
if(step==1 || step==-1) {
// if endvalue == loopvar, stop loop, else iterate
if(loopvarDtIr==IRDataType.BYTE && step==-1 && iterable.to.asConstInteger()==0) {
// downto 0 optimization (byte)
val fromTr = expressionEval.translateExpression(iterable.from)
addToResult(result, fromTr, fromTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.STOREM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol=loopvarSymbol), null)
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
result += addConstMem(loopvarDtIr, null, loopvarSymbol, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = fromTr.resultReg, labelSymbol = loopvarSymbol)
it += IRInstruction(Opcode.CMP, loopvarDtIr, reg1=toTr.resultReg, reg2=fromTr.resultReg)
it += IRInstruction(Opcode.BSTEQ, labelSymbol = labelAfterFor)
it += IRInstruction(Opcode.CMPI, loopvarDtIr, reg1 = fromTr.resultReg, immediate = 255)
it += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
}
result += addConstMem(loopvarDtIr, null, loopvarSymbol, step)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = loopLabel), null)
} else {
// ind/dec index, then:
// ascending: if endvalue >= loopvar, iterate
// descending: if loopvar >= endvalue, iterate
result += addConstMem(loopvarDtIr, null, loopvarSymbol, step)
addInstr(result, IRInstruction(Opcode.LOADM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol = loopvarSymbol), null)
if(step > 0)
addInstr(result, IRInstruction(Opcode.CMP, loopvarDtIr, reg1 = toTr.resultReg, fromTr.resultReg), null)
else
addInstr(result, IRInstruction(Opcode.CMP, loopvarDtIr, reg1 = fromTr.resultReg, toTr.resultReg), null)
addInstr(result, IRInstruction(Opcode.BSTPOS, labelSymbol = loopLabel), null)
}
result += IRCodeChunk(labelAfterFor, null)
else if(step==-1 && iterable.to.asConstInteger()==1) {
// downto 1 optimization (byte and word)
val fromTr = expressionEval.translateExpression(iterable.from)
addToResult(result, fromTr, fromTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.STOREM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol=loopvarSymbol), null)
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
result += addConstMem(loopvarDtIr, null, loopvarSymbol, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = fromTr.resultReg, labelSymbol = loopvarSymbol)
it += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
}
}
else {
val toTr = expressionEval.translateExpression(iterable.to)
addToResult(result, toTr, toTr.resultReg, -1)
val fromTr = expressionEval.translateExpression(iterable.from)
addToResult(result, fromTr, fromTr.resultReg, -1)
val labelAfterFor = createLabelName()
val precheckInstruction = if(loopvarDt in SignedDatatypes) {
if(step>0)
IRInstruction(Opcode.BGTSR, loopvarDtIr, fromTr.resultReg, toTr.resultReg, labelSymbol=labelAfterFor)
else
IRInstruction(Opcode.BGTSR, loopvarDtIr, toTr.resultReg, fromTr.resultReg, labelSymbol=labelAfterFor)
} else {
if(step>0)
IRInstruction(Opcode.BGTR, loopvarDtIr, fromTr.resultReg, toTr.resultReg, labelSymbol=labelAfterFor)
else
IRInstruction(Opcode.BGTR, loopvarDtIr, toTr.resultReg, fromTr.resultReg, labelSymbol=labelAfterFor)
}
addInstr(result, precheckInstruction, null)
addInstr(result, IRInstruction(Opcode.STOREM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol=loopvarSymbol), null)
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
if(step==1 || step==-1) {
// if endvalue == loopvar, stop loop, else iterate
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = fromTr.resultReg, labelSymbol = loopvarSymbol)
it += IRInstruction(Opcode.CMP, loopvarDtIr, reg1=toTr.resultReg, reg2=fromTr.resultReg)
it += IRInstruction(Opcode.BSTEQ, labelSymbol = labelAfterFor)
}
result += addConstMem(loopvarDtIr, null, loopvarSymbol, step)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = loopLabel), null)
} else {
// ind/dec index, then:
// ascending: if endvalue >= loopvar, iterate
// descending: if loopvar >= endvalue, iterate
result += addConstMem(loopvarDtIr, null, loopvarSymbol, step)
addInstr(result, IRInstruction(Opcode.LOADM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol = loopvarSymbol), null)
if(step > 0)
addInstr(result, IRInstruction(Opcode.CMP, loopvarDtIr, reg1 = toTr.resultReg, fromTr.resultReg), null)
else
addInstr(result, IRInstruction(Opcode.CMP, loopvarDtIr, reg1 = fromTr.resultReg, toTr.resultReg), null)
addInstr(result, IRInstruction(Opcode.BSTPOS, labelSymbol = loopLabel), null)
}
result += IRCodeChunk(labelAfterFor, null)
}
return result
}
@@ -558,9 +585,25 @@ class IRCodeGen(
result += chunk
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
val chunk2 = addConstMem(loopvarDtIr, null, loopvarSymbol, iterable.step)
chunk2 += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = indexReg, labelSymbol = loopvarSymbol)
chunk2 += IRInstruction(Opcode.CMPI, loopvarDtIr, reg1 = indexReg, immediate = rangeEndExclusiveWrapped)
chunk2 += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
if(loopvarDtIr==IRDataType.BYTE && iterable.step==-1 && iterable.last==0) {
// downto 0 optimization (byte)
if(loopvarDt==DataType.BYTE || iterable.first<=127) {
chunk2 += IRInstruction(Opcode.BSTPOS, labelSymbol = loopLabel)
} else {
chunk2 += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = indexReg, labelSymbol = loopvarSymbol)
chunk2 += IRInstruction(Opcode.CMPI, loopvarDtIr, reg1 = indexReg, immediate = rangeEndExclusiveWrapped)
chunk2 += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
}
}
else if(iterable.step==-1 && iterable.last==1) {
// downto 1 optimization (byte and word)
chunk2 += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
} else {
// downto some other value
chunk2 += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = indexReg, labelSymbol = loopvarSymbol)
chunk2 += IRInstruction(Opcode.CMPI, loopvarDtIr, reg1 = indexReg, immediate = rangeEndExclusiveWrapped)
chunk2 += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
}
result += chunk2
return result
}
@@ -1648,7 +1691,7 @@ class IRCodeGen(
private fun translate(parameters: List<PtSubroutineParameter>) =
parameters.map {
val flattenedName = it.definingSub()!!.name + "." + it.name
val flattenedName = it.definingISub()!!.name + "." + it.name
if(symbolTable.lookup(flattenedName)==null)
TODO("fix missing lookup for: $flattenedName parameter")
val orig = symbolTable.lookup(flattenedName) as StStaticVariable
@@ -66,7 +66,7 @@ class ConstExprEvaluator {
// DataType.WORD -> result = result.toShort().toInt()
// else -> { /* keep as it is */ }
// }
return NumericLiteral.optimalNumeric(result.toDouble(), left.position)
return NumericLiteral.optimalNumeric(left.type, null, result.toDouble(), left.position)
}
private fun bitwiseXor(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
@@ -133,7 +133,7 @@ class ConstExprEvaluator {
val error = "cannot add $left and $right"
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteral.optimalInteger(left.number.toInt() + right.number.toInt(), left.position)
in IntegerDatatypes -> NumericLiteral.optimalInteger(left.type, right.type, left.number.toInt() + right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteral(DataType.FLOAT, left.number.toInt() + right.number, left.position)
else -> throw ExpressionError(error, left.position)
}
@@ -150,7 +150,7 @@ class ConstExprEvaluator {
val error = "cannot subtract $left and $right"
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteral.optimalInteger(left.number.toInt() - right.number.toInt(), left.position)
in IntegerDatatypes -> NumericLiteral.optimalInteger(left.type, right.type, left.number.toInt() - right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteral(DataType.FLOAT, left.number.toInt() - right.number, left.position)
else -> throw ExpressionError(error, left.position)
}
@@ -167,7 +167,7 @@ class ConstExprEvaluator {
val error = "cannot multiply ${left.type} and ${right.type}"
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteral.optimalInteger(left.number.toInt() * right.number.toInt(), left.position)
in IntegerDatatypes -> NumericLiteral.optimalInteger(left.type, right.type, left.number.toInt() * right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteral(DataType.FLOAT, left.number.toInt() * right.number, left.position)
else -> throw ExpressionError(error, left.position)
}
@@ -190,7 +190,7 @@ class ConstExprEvaluator {
in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position)
val result: Int = left.number.toInt() / right.number.toInt()
NumericLiteral.optimalInteger(result, left.position)
NumericLiteral.optimalInteger(left.type, right.type, result, left.position)
}
DataType.FLOAT -> {
if(right.number==0.0) divideByZeroError(right.position)
@@ -219,7 +219,7 @@ class ConstExprEvaluator {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position)
NumericLiteral.optimalNumeric(left.number.toInt().toDouble() % right.number.toInt().toDouble(), left.position)
NumericLiteral.optimalNumeric(left.type, right.type, left.number.toInt().toDouble() % right.number.toInt().toDouble(), left.position)
}
DataType.FLOAT -> {
if(right.number ==0.0) divideByZeroError(right.position)
@@ -33,6 +33,14 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
}
override fun after(numLiteral: NumericLiteral, parent: Node): Iterable<IAstModification> {
if(numLiteral.type==DataType.LONG) {
// see if LONG values may be reduced to something smaller
val smaller = NumericLiteral.optimalInteger(numLiteral.number.toInt(), numLiteral.position)
if(smaller.type!=DataType.LONG)
return listOf(IAstModification.ReplaceNode(numLiteral, smaller, parent))
}
if(parent is Assignment) {
val iDt = parent.target.inferType(program)
if(iDt.isKnown && !iDt.isBool && !iDt.istype(numLiteral.type)) {
@@ -164,13 +172,13 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
if(leftconst==null && rightconst!=null && rightconst.number<0.0) {
if (expr.operator == "-") {
// X - -1 ---> X + 1
val posNumber = NumericLiteral.optimalNumeric(-rightconst.number, rightconst.position)
val posNumber = NumericLiteral.optimalNumeric(rightconst.type, null, -rightconst.number, rightconst.position)
val plusExpr = BinaryExpression(expr.left, "+", posNumber, expr.position)
return listOf(IAstModification.ReplaceNode(expr, plusExpr, parent))
}
else if (expr.operator == "+") {
// X + -1 ---> X - 1
val posNumber = NumericLiteral.optimalNumeric(-rightconst.number, rightconst.position)
val posNumber = NumericLiteral.optimalNumeric(rightconst.type, null, -rightconst.number, rightconst.position)
val plusExpr = BinaryExpression(expr.left, "-", posNumber, expr.position)
return listOf(IAstModification.ReplaceNode(expr, plusExpr, parent))
}
@@ -384,12 +392,13 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
val numval = decl.value as? NumericLiteral
if(decl.type== VarDeclType.CONST && numval!=null) {
val valueDt = numval.inferType(program)
if(valueDt istype DataType.LONG) {
return noModifications // this is handled in the numericalvalue case
}
if(valueDt isnot decl.datatype) {
if(decl.datatype!=DataType.BOOL || valueDt.isnot(DataType.UBYTE)) {
val cast = numval.cast(decl.datatype, true)
if (cast.isValid)
return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl))
}
val cast = numval.cast(decl.datatype, true)
if (cast.isValid)
return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl))
}
}
return noModifications
@@ -490,8 +490,7 @@ internal class ConstantIdentifierReplacer(
if(numericLv!=null) {
// arraysize initializer is a single value, and we know the array size.
if(numericLv.type!=DataType.BOOL) {
if(options.strictBool || numericLv.type !in ByteDatatypes)
errors.err("initializer value is not a boolean", numericLv.position)
errors.err("initializer value is not a boolean", numericLv.position)
return null
}
val array = Array(size) {numericLv.number}.map { NumericLiteral(DataType.BOOL, it, numericLv.position) }.toTypedArray<Expression>()
@@ -11,9 +11,9 @@ import prog8.ast.walk.IAstModification
import prog8.code.core.*
import kotlin.math.abs
import kotlin.math.log2
import kotlin.math.pow
class ExpressionSimplifier(private val program: Program, private val options: CompilationOptions, private val errors: IErrorReporter) : AstWalker() {
class ExpressionSimplifier(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
val mods = mutableListOf<IAstModification>()
@@ -246,20 +246,15 @@ class ExpressionSimplifier(private val program: Program, private val options: Co
}
}
if (rightVal?.number == 1.0) {
if (options.strictBool) {
if (rightDt != leftDt) {
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
} else
return listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
if (rightDt != leftDt) {
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
}
else if (rightVal?.number == 0.0) {
if (options.strictBool) {
if (rightDt != leftDt) {
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
if (rightDt != leftDt) {
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
}
}
@@ -274,21 +269,16 @@ class ExpressionSimplifier(private val program: Program, private val options: Co
}
}
if (rightVal?.number == 1.0) {
if(options.strictBool) {
if(rightDt!=leftDt) {
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
if(rightDt!=leftDt) {
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
}
else if (rightVal?.number == 0.0) {
if(options.strictBool) {
if(rightDt!=leftDt) {
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
} else
return listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
if(rightDt!=leftDt) {
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
}
}
@@ -59,8 +59,8 @@ fun Program.inlineSubroutines(options: CompilationOptions): Int {
return inliner.applyModifications()
}
fun Program.simplifyExpressions(errors: IErrorReporter, options: CompilationOptions) : Int {
val opti = ExpressionSimplifier(this, options, errors)
fun Program.simplifyExpressions(errors: IErrorReporter) : Int {
val opti = ExpressionSimplifier(this, errors)
opti.visit(this)
return opti.applyModifications()
}
@@ -200,35 +200,6 @@ class StatementOptimizer(private val program: Program,
}
}
val loopvarDt = forLoop.loopVarDt(program)
if(loopvarDt.istype(DataType.UWORD) || loopvarDt.istype(DataType.UBYTE)) {
fun incOrDec(inc: Boolean): Assignment {
val pos = forLoop.position
val loopVar = forLoop.loopVar
val addSubOne = BinaryExpression(loopVar.copy(), if(inc) "+" else "-", NumericLiteral.optimalInteger(1, pos), pos, false)
return Assignment(AssignTarget(loopVar.copy(), null, null, null, false, pos), addSubOne, AssignmentOrigin.USERCODE, pos)
}
if (range != null && range.to.constValue(program)?.number == 0.0 && range.step.constValue(program)?.number==-1.0) {
val fromExpr = range.from
if(fromExpr.constValue(program)==null) {
// FOR X = something DOWNTO 0 {...} --> X=something, DO { ... , X-- } UNTIL X=255 (or 65535 if uword)
val pos = forLoop.position
val checkValue = NumericLiteral(loopvarDt.getOr(DataType.UNDEFINED), if(loopvarDt.istype(DataType.UBYTE)) 255.0 else 65535.0, pos)
val condition = BinaryExpression(forLoop.loopVar.copy(), "==", checkValue, pos)
val decOne = incOrDec(false)
forLoop.body.statements.add(decOne)
val replacement = AnonymousScope(mutableListOf(
Assignment(AssignTarget(forLoop.loopVar.copy(), null, null, null, false, pos),
fromExpr, AssignmentOrigin.OPTIMIZER, pos),
UntilLoop(forLoop.body, condition, pos)
), pos)
return listOf(IAstModification.ReplaceNode(forLoop, replacement, parent))
}
}
}
return noModifications
}
+1
View File
@@ -41,6 +41,7 @@ dependencies {
testImplementation project(':codeCore')
testImplementation project(':intermediate')
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.9.1'
testImplementation 'io.kotest:kotest-framework-datatest:5.9.1'
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
+2 -1
View File
@@ -15,7 +15,7 @@
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
<orderEntry type="library" name="antlr.antlr4" level="project" />
<orderEntry type="library" name="io.kotest.framework.datatest" level="project" />
<orderEntry type="module" module-name="codeCore" />
<orderEntry type="module" module-name="compilerAst" />
<orderEntry type="module" module-name="codeOptimizers" />
@@ -24,5 +24,6 @@
<orderEntry type="module" module-name="codeGenIntermediate" />
<orderEntry type="module" module-name="virtualmachine" />
<orderEntry type="module" module-name="intermediate" scope="TEST" />
<orderEntry type="library" name="antlr.antlr4" level="project" />
</component>
</module>
+41 -2
View File
@@ -36,7 +36,7 @@ romsub $b1bf = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 100-101
; there is also floats.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
; there is also floats.FREADS32 that reads from 98-101 ($62-$65) MSB FIRST
; there is also floats.FREADUS32 that reads from 98-101 ($62-$65) MSB FIRST
; there is also floats.FREADS24AXY that reads signed int24 into fac1 from A/X/Y (lo/mid/hi bytes)
; there is also floats.FREAD{S,U}24AXY that read (un)signed int24 into fac1 from A/X/Y (lo/mid/hi bytes)
romsub $b391 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
romsub $b3a2 = FREADUY(ubyte value @ Y) clobbers(A,X,Y) ; 8 bit unsigned Y -> float in fac1
@@ -104,7 +104,6 @@ asmsub FREADUS32 () clobbers(A,X,Y) {
asmsub FREADS24AXY (ubyte lo @ A, ubyte mid @ X, ubyte hi @ Y) clobbers(A,X,Y) {
; ---- fac1 = signed int24 (A/X/Y contain lo/mid/hi bytes)
; note: there is no FREADU24AXY (unsigned), use FREADUS32 instead.
%asm {{
sty $62
stx $63
@@ -119,6 +118,39 @@ asmsub FREADS24AXY (ubyte lo @ A, ubyte mid @ X, ubyte hi @ Y) clobbers(A,X,Y)
}}
}
asmsub FREADU24AXY(ubyte lo @ A, ubyte mid @ X, ubyte hi @ Y) clobbers(A, X, Y) -> float @FAC1 {
; ---- fac1 = unsigned int24 (A/X/Y contain lo/mid/hi bytes)
%asm{{
FAC = $61
sty FAC+1
stx FAC+2
sta FAC+3
cpy #$00
bne +
cpx #$00
bne +
cmp #$00
beq ++
+ ldx #$98
bit FAC+1
bmi +
- dex
asl FAC+3
rol FAC+2
rol FAC+1
bpl -
+ stx FAC
lda #$00
sta FAC+4
sta FAC+5
rts
}}
}
asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
%asm {{
@@ -177,6 +209,13 @@ asmsub normalize(float value @FAC1) -> float @ FAC1 {
}}
}
; get the jiffy clock as a float
asmsub time() -> float @ FAC1 {
%asm {{
jsr cbm.RDTIM
jmp floats.FREADU24AXY
}}
}
%asminclude "library:c64/floats.asm"
%asminclude "library:c64/floats_funcs.asm"
+20 -3
View File
@@ -288,17 +288,30 @@ hline_zero2
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
; Midpoint algorithm, filled
; Midpoint algorithm, filled.
if radius==0
return
ubyte @zp yy = 0
word decisionOver2 = (1 as word)-radius
ubyte last_y3 = ycenter+radius
ubyte last_y4 = ycenter-radius
ubyte new_y3, new_y4
while radius>=yy {
horizontal_line(xcenter-radius, ycenter+yy, radius*2+1)
horizontal_line(xcenter-radius, ycenter-yy, radius*2+1)
horizontal_line(xcenter-yy, ycenter+radius, yy*2+1)
horizontal_line(xcenter-yy, ycenter-radius, yy*2+1)
new_y3 = ycenter+radius
if new_y3 != last_y3 {
horizontal_line(xcenter-yy, last_y3, yy*2+1)
last_y3 = new_y3
}
new_y4 = ycenter-radius
if new_y4 != last_y4 {
horizontal_line(xcenter-yy, last_y4, yy*2+1)
last_y4 = new_y4
}
yy++
if decisionOver2>=0 {
radius--
@@ -307,6 +320,10 @@ hline_zero2
decisionOver2 += yy*$0002
decisionOver2++
}
; draw the final two spans
yy--
horizontal_line(xcenter-yy, last_y3, yy*2+1)
horizontal_line(xcenter-yy, last_y4, yy*2+1)
}
+8 -9
View File
@@ -935,15 +935,14 @@ io_error:
return
cx16.r0 = cx16.r1 = cx16.r2 = cx16.r3 = 0
sub read4hex() -> uword {
str hex = "0000"
hex[0] = cbm.CHRIN()
hex[1] = cbm.CHRIN()
hex[2] = cbm.CHRIN()
hex[3] = cbm.CHRIN()
return conv.hex2uword(hex)
}
}
sub read4hex() -> uword {
str hex = "0000"
hex[0] = cbm.CHRIN()
hex[1] = cbm.CHRIN()
hex[2] = cbm.CHRIN()
hex[3] = cbm.CHRIN()
return conv.hex2uword(hex)
}
}
+21
View File
@@ -62,4 +62,25 @@ emudbg {
if is_emulator()
EMU_DBGOUT2 = value
}
sub reset_cpu_cycles() {
if is_emulator()
EMU_CPUCLK_L = 0
}
asmsub cpu_cycles() -> uword @R0, uword @R1 {
; -- returns the 32 bits cpu clock counter in R1:R0,
; returns lower 16 bits in R0, upper 16 bits in R1.
%asm {{
lda p8v_EMU_CPUCLK_L
sta cx16.r0L
lda p8v_EMU_CPUCLK_M
sta cx16.r0H
lda p8v_EMU_CPUCLK_H
sta cx16.r1L
lda p8v_EMU_CPUCLK_U
sta cx16.r1H
rts
}}
}
}
+40 -1
View File
@@ -90,6 +90,38 @@ asmsub FREADSA (byte value @A) clobbers(A,X,Y) {
}}
}
asmsub FREADU24AXY(ubyte lo @ A, ubyte mid @ X, ubyte hi @ Y) clobbers(A, X, Y) -> float @FAC1 {
; ---- fac1 = unsigned int24 (A/X/Y contain lo/mid/hi bytes)
%asm{{
FAC = $C3
sty FAC+1
stx FAC+2
sta FAC+3
cpy #$00
bne +
cpx #$00
bne +
cmp #$00
beq ++
+ ldx #$98
bit FAC+1
bmi +
- dex
asl FAC+3
rol FAC+2
rol FAC+1
bpl -
+ stx FAC
stz FAC+4
stz FAC+5
rts
}}
}
asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
%asm {{
@@ -157,7 +189,6 @@ _msg .text 13,"?rom 47+ required for val1",13,0
}}
}
&uword AYINT_facmo = $c6 ; $c6/$c7 contain result of AYINT (See "basic.sym" kernal symbol file)
sub rnd() -> float {
@@ -173,6 +204,14 @@ asmsub normalize(float value @FAC1) -> float @ FAC1 {
}}
}
; get the jiffy clock as a float
asmsub time() -> float @ FAC1 {
%asm {{
jsr cbm.RDTIM
jmp floats.FREADU24AXY
}}
}
%asminclude "library:c64/floats.asm"
%asminclude "library:c64/floats_funcs.asm"
+44 -4
View File
@@ -15,6 +15,9 @@
;
; NOTE: the bitmap screen data is positioned in vram at $0:0000
;
; NOTE: In the future, these routines might be split out to separate modules, 1 for each screen mode,
; so they can be optimized a lot better. There's already a "gfx_lores" module with a few routines for lores 256C mode.
;
; SCREEN MODE LIST:
; mode 0 = reset back to default text mode
; mode 1 = bitmap 320 x 240 x 256c (8 bpp)
@@ -285,6 +288,9 @@ gfx2 {
sub line(uword @zp x1, uword @zp y1, uword @zp x2, uword @zp y2, ubyte color) {
; Bresenham algorithm.
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
; NOTE: if you are using lores 256c (mode 1), the line routine in the fastline module can be used and is MUCH faster than this one.
if y1>y2 {
; make sure dy is always positive to have only 4 instead of 8 special cases
cx16.r0 = x1
@@ -479,12 +485,25 @@ gfx2 {
return
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-radius
uword last_y3 = ycenter+radius
uword last_y4 = ycenter-radius
uword new_y3, new_y4
while radius>=yy {
horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, color)
horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, color)
horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, color)
horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, color)
new_y3 = ycenter+radius
if new_y3 != last_y3 {
horizontal_line(xcenter-yy, last_y3, yy*$0002+1, color)
last_y3 = new_y3
}
new_y4 = ycenter-radius
if new_y4 != last_y4 {
horizontal_line(xcenter-yy, last_y4, yy*$0002+1, color)
last_y4 = new_y4
}
yy++
if decisionOver2>=0 {
radius--
@@ -493,6 +512,11 @@ gfx2 {
decisionOver2 += yy*$0002
decisionOver2++
}
; draw the final two spans
yy--
horizontal_line(xcenter-yy, last_y3, yy*$0002+1, color)
horizontal_line(xcenter-yy, last_y4, yy*$0002+1, color)
}
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, ubyte color) {
@@ -502,12 +526,23 @@ gfx2 {
return
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-radius
uword last_y3 = ycenter+radius
uword last_y4 = ycenter-radius
uword new_y3, new_y4
while radius>=yy {
safe_horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, color)
safe_horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, color)
safe_horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, color)
safe_horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, color)
new_y3 = ycenter+radius
if new_y3 != last_y3 {
safe_horizontal_line(xcenter-yy, last_y3, yy*$0002+1, color)
last_y3 = new_y3
}
new_y4 = ycenter-radius
if new_y4 != last_y4 {
safe_horizontal_line(xcenter-yy, last_y4, yy*$0002+1, color)
last_y4 = new_y4
}
yy++
if decisionOver2>=0 {
radius--
@@ -516,6 +551,10 @@ gfx2 {
decisionOver2 += yy*$0002
decisionOver2++
}
; draw the final two spans
yy--
safe_horizontal_line(xcenter-yy, last_y3, yy*$0002+1, color)
safe_horizontal_line(xcenter-yy, last_y4, yy*$0002+1, color)
}
sub plot(uword @zp xx, uword @zp yy, ubyte @zp color) {
@@ -759,6 +798,7 @@ skip:
cx16.VERA_CTRL = 1
cx16.VERA_ADDR_H = cx16.r1L
cx16.VERA_ADDR = cx16.r0
cx16.VERA_CTRL = 0
}
sub fill_scanline_left_8bpp() -> bool {
+235
View File
@@ -0,0 +1,235 @@
; optimized graphics routines for just the single screen mode: lores 320*240, 256c (8bpp)
; bitmap image needs to start at VRAM addres $00000.
; This is compatible with the CX16's screen mode 128. (void cx16.set_screen_mode(128))
gfx_lores {
%option ignore_unused
sub set_screen_mode() {
cx16.VERA_CTRL=0
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 64
cx16.VERA_DC_VSCALE = 64
cx16.VERA_L1_CONFIG = %00000111
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = 0
clear_screen(0)
}
sub clear_screen(ubyte color) {
cx16.VERA_CTRL=0
cx16.VERA_ADDR=0
cx16.VERA_ADDR_H = 1<<4 ; 1 pixel auto increment
repeat 240
cs_innerloop320(color)
cx16.VERA_ADDR=0
cx16.VERA_ADDR_H = 0
}
sub line(uword x1, ubyte y1, uword x2, ubyte y2, ubyte color) {
; Bresenham algorithm.
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
; NOTE: this is about twice as fast as the kernal routine GRAPH_draw_line, and ~3-4 times as fast as gfx2.line()
; it trades memory for speed (uses inline plot routine and multiplication lookup tables)
;
; NOTE: is currently still a regular 6502 routine, could likely be made much faster with the VeraFX line helper.
cx16.r3L = y2 ; ensure zeropage
cx16.r1L = y1 ; ensure zeropage
if cx16.r1L > cx16.r3L {
; make sure dy is always positive to have only 4 instead of 8 special cases
cx16.r0 = x1
x1 = x2
x2 = cx16.r0
cx16.r0L = cx16.r1L
cx16.r1L = cx16.r3L
cx16.r3L = cx16.r0L
}
word @zp dx = x2 as word
word @zp dy = cx16.r3L
dx -= x1
dy -= cx16.r1L
if dx==0 {
vertical_line(x1, cx16.r1L, lsb(dy)+1, color)
return
}
if dy==0 {
if x1>x2
x1=x2
horizontal_line(x1, cx16.r1L, abs(dx) as uword +1, color)
return
}
word @zp d = 0
bool positive_ix = true
if dx < 0 {
dx = -dx
positive_ix = false
}
word @zp dx2 = dx*2
word @zp dy2 = dy*2
cx16.r0 = x1 ; ensure zeropage
cx16.r2 = x2 ; ensure zeropage
cx16.VERA_CTRL = 0
if dx >= dy {
if positive_ix {
repeat {
plot()
if cx16.r0==cx16.r2
return
cx16.r0++
d += dy2
if d > dx {
cx16.r1L++
d -= dx2
}
}
} else {
repeat {
plot()
if cx16.r0==cx16.r2
return
cx16.r0--
d += dy2
if d > dx {
cx16.r1L++
d -= dx2
}
}
}
}
else {
if positive_ix {
repeat {
plot()
if cx16.r1L == cx16.r3L
return
cx16.r1L++
d += dx2
if d > dy {
cx16.r0++
d -= dy2
}
}
} else {
repeat {
plot()
if cx16.r1L == cx16.r3L
return
cx16.r1L++
d += dx2
if d > dy {
cx16.r0--
d -= dy2
}
}
}
}
asmsub plot() {
; x in r0, y in r1, color.
%asm {{
ldy cx16.r1L
clc
lda times320_lo,y
adc cx16.r0L
sta cx16.VERA_ADDR_L
lda times320_mid,y
adc cx16.r0H
sta cx16.VERA_ADDR_M
lda #0
adc times320_hi,y
sta cx16.VERA_ADDR_H
lda p8v_color
sta cx16.VERA_DATA0
rts
}}
}
%asm {{
; multiplication by 320 lookup table
times320 := 320*range(240)
times320_lo .byte <times320
times320_mid .byte >times320
times320_hi .byte `times320
}}
}
sub horizontal_line(uword xx, ubyte yy, uword length, ubyte color) {
if length==0
return
vera_setaddr(xx, yy)
; set vera auto-increment to 1 pixel
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | (1<<4)
%asm {{
lda p8v_color
ldx p8v_length+1
beq +
ldy #0
- sta cx16.VERA_DATA0
iny
bne -
dex
bne -
+ ldy p8v_length ; remaining
beq +
- sta cx16.VERA_DATA0
dey
bne -
+
}}
}
sub vertical_line(uword xx, ubyte yy, ubyte lheight, ubyte color) {
vera_setaddr(xx,yy)
; set vera auto-increment to 320 pixel increment (=next line)
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | (14<<4)
%asm {{
ldy p8v_lheight
beq +
lda p8v_color
- sta cx16.VERA_DATA0
dey
bne -
+
}}
}
asmsub cs_innerloop320(ubyte color @A) clobbers(Y) {
; using verafx 32 bits writes here would make this faster but it's safer to
; use verafx only explicitly when you know what you're doing.
%asm {{
ldy #40
- sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
dey
bne -
rts
}}
}
inline asmsub vera_setaddr(uword xx @R0, ubyte yy @R1) {
; set the correct vera start address (no auto increment yet!)
%asm {{
jsr p8s_line.p8s_plot
}}
}
}
+14 -67
View File
@@ -76,83 +76,30 @@ graphics {
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
;cx16.r0 = xcenter - radius/2
;cx16.r1 = ycenter - radius/2
;cx16.r2 = radius*2
;cx16.r3 = radius*2
;cx16.GRAPH_draw_oval(false) ; currently this call is not implemented on cx16, does a BRK
; Midpoint algorithm
if radius==0
return
ubyte @zp xx = radius
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-xx
while xx>=yy {
cx16.r0 = xcenter + xx
cx16.r1 = ycenter + yy
FB_cursor_position2()
cx16.FB_set_pixel(stroke_color)
cx16.r0 = xcenter - xx
FB_cursor_position2()
cx16.FB_set_pixel(stroke_color)
cx16.r0 = xcenter + xx
cx16.r1 = ycenter - yy
FB_cursor_position2()
cx16.FB_set_pixel(stroke_color)
cx16.r0 = xcenter - xx
FB_cursor_position2()
cx16.FB_set_pixel(stroke_color)
cx16.r0 = xcenter + yy
cx16.r1 = ycenter + xx
FB_cursor_position2()
cx16.FB_set_pixel(stroke_color)
cx16.r0 = xcenter - yy
FB_cursor_position2()
cx16.FB_set_pixel(stroke_color)
cx16.r0 = xcenter + yy
cx16.r1 = ycenter - xx
FB_cursor_position2()
cx16.FB_set_pixel(stroke_color)
cx16.r0 = xcenter - yy
FB_cursor_position2()
cx16.FB_set_pixel(stroke_color)
yy++
if decisionOver2>=0 {
xx--
decisionOver2 -= xx*$0002
}
decisionOver2 += yy*$0002
decisionOver2++
}
cx16.GRAPH_draw_oval(xcenter - radius, ycenter - radius, radius*2, radius*2, false)
}
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
; Midpoint algorithm, filled
if radius==0
return
ubyte @zp yy = 0
word decisionOver2 = (1 as word)-radius
cx16.GRAPH_draw_oval(xcenter - radius, ycenter - radius, radius*2, radius*2, true)
}
while radius>=yy {
horizontal_line(xcenter-radius, ycenter+yy, radius*2+1)
horizontal_line(xcenter-radius, ycenter-yy, radius*2+1)
horizontal_line(xcenter-yy, ycenter+radius, yy*2+1)
horizontal_line(xcenter-yy, ycenter-radius, yy*2+1)
sub oval(uword xcenter, ubyte ycenter, uword h_radius, ubyte v_radius) {
; specific for the X16 which has a kernal routine that can draw ovals
if h_radius==0 or v_radius==0
return
cx16.GRAPH_draw_oval(xcenter - h_radius, ycenter - v_radius, h_radius*2, v_radius*2, false)
}
yy++
if decisionOver2>=0 {
radius--
decisionOver2 -= radius*$0002
}
decisionOver2 += yy*$0002
decisionOver2++
}
sub filled_oval(uword xcenter, ubyte ycenter, uword h_radius, ubyte v_radius) {
; specific for the X16 which has a kernal routine that can draw ovals
if h_radius==0 or v_radius==0
return
cx16.GRAPH_draw_oval(xcenter - h_radius, ycenter - v_radius, h_radius*2, v_radius*2, true)
}
inline asmsub plot(uword plotx @R0, uword ploty @R1) clobbers(A, X, Y) {
+42 -12
View File
@@ -32,7 +32,7 @@ monogfx {
width = 320
height = 240
mode = MODE_NORMAL
clear_screen(0)
clear_screen(false)
}
sub hires() {
@@ -47,7 +47,7 @@ monogfx {
width = 640
height = 480
mode = MODE_NORMAL
clear_screen(0)
clear_screen(false)
}
sub textmode() {
@@ -61,16 +61,16 @@ monogfx {
mode = dm
}
sub clear_screen(ubyte color) {
sub clear_screen(bool draw) {
position(0, 0)
when width {
320 -> {
repeat 240/2/8
cs_innerloop640(color)
cs_innerloop640(draw)
}
640 -> {
repeat 480/8
cs_innerloop640(color)
cs_innerloop640(draw)
}
}
position(0, 0)
@@ -90,8 +90,8 @@ monogfx {
}
sub fillrect(uword xx, uword yy, uword rwidth, uword rheight, bool draw) {
; Draw a filled rectangle of the given size and color.
; To fill the whole screen, use clear_screen(color) instead - it is much faster.
; Draw a filled rectangle of the given size.
; To fill the whole screen, use clear_screen(draw) instead - it is much faster.
if rwidth==0
return
repeat rheight {
@@ -563,12 +563,23 @@ drawmode: ora cx16.r15L
return
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-radius
uword last_y3 = ycenter+radius
uword last_y4 = ycenter-radius
uword new_y3, new_y4
while radius>=yy {
horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, draw)
horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, draw)
horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, draw)
horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, draw)
new_y3 = ycenter+radius
if new_y3 != last_y3 {
horizontal_line(xcenter-yy, last_y3, yy*$0002+1, draw)
last_y3 = new_y3
}
new_y4 = ycenter-radius
if new_y4 != last_y4 {
horizontal_line(xcenter-yy, last_y4, yy*$0002+1, draw)
last_y4 = new_y4
}
yy++
if decisionOver2>=0 {
radius--
@@ -577,6 +588,10 @@ drawmode: ora cx16.r15L
decisionOver2 += yy*$0002
decisionOver2++
}
; draw the final two spans
yy--
horizontal_line(xcenter-yy, last_y3, yy*$0002+1, draw)
horizontal_line(xcenter-yy, last_y4, yy*$0002+1, draw)
}
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, bool draw) {
@@ -586,12 +601,23 @@ drawmode: ora cx16.r15L
return
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-radius
uword last_y3 = ycenter+radius
uword last_y4 = ycenter-radius
uword new_y3, new_y4
while radius>=yy {
safe_horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, draw)
safe_horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, draw)
safe_horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, draw)
safe_horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, draw)
new_y3 = ycenter+radius
if new_y3 != last_y3 {
safe_horizontal_line(xcenter-yy, last_y3, yy*$0002+1, draw)
last_y3 = new_y3
}
new_y4 = ycenter-radius
if new_y4 != last_y4 {
safe_horizontal_line(xcenter-yy, last_y4, yy*$0002+1, draw)
last_y4 = new_y4
}
yy++
if decisionOver2>=0 {
radius--
@@ -600,6 +626,10 @@ drawmode: ora cx16.r15L
decisionOver2 += yy*$0002
decisionOver2++
}
; draw the final two spans
yy--
safe_horizontal_line(xcenter-yy, last_y3, yy*$0002+1, draw)
safe_horizontal_line(xcenter-yy, last_y4, yy*$0002+1, draw)
}
sub plot(uword @zp xx, uword @zp yy, bool @zp draw) {
@@ -970,7 +1000,7 @@ cdraw_mod2 ora cx16.VERA_DATA1
}
}
asmsub cs_innerloop640(ubyte color @A) clobbers(Y) {
asmsub cs_innerloop640(bool draw @A) clobbers(Y) {
; using verafx 32 bits writes here would make this faster but it's safer to
; use verafx only explicitly when you know what you're doing.
%asm {{
+10 -2
View File
@@ -23,7 +23,7 @@ sprites {
ubyte databank, uword dataaddr,
ubyte width_flag, ubyte height_flag,
ubyte colors_flag, ubyte palette_offset) {
hide(spritenum)
pos(spritenum, -64, -64) ; move sprite off-screen initially
cx16.VERA_DC_VIDEO |= %01000000 ; enable sprites globally
dataaddr >>= 5
dataaddr |= (databank as uword)<<11
@@ -138,7 +138,15 @@ sprites {
}
sub hide(ubyte spritenum) {
pos(spritenum, -64, -64)
cx16.vpoke_and(1, VERA_SPRITEREGS + 6 + spritenum*$0008, %11110011)
}
sub show(ubyte spritenum) {
cx16.vpoke_or(1, VERA_SPRITEREGS + 6 + spritenum*$0008, %00001100)
}
sub zdepth(ubyte spritenum, ubyte depth) {
cx16.vpoke_mask(1, VERA_SPRITEREGS + 6 + spritenum*$0008, %11110011, depth<<2)
}
sub flipx(ubyte spritenum, bool flipped) {
+10 -1
View File
@@ -554,7 +554,8 @@ const ubyte EXTAPI_ps2data_raw = $09
const ubyte EXTAPI_cursor_blink = $0A
const ubyte EXTAPI_led_update = $0B
const ubyte EXTAPI_mouse_set_position = $0C
const ubyte EXTAPI_scnsiz = $0D ; rom R48+
const ubyte EXTAPI_scnsiz = $0D
const ubyte EXTAPI_kbd_leds = $0E
; extapi16 call numbers
const ubyte EXTAPI16_test = $00
@@ -603,6 +604,14 @@ asmsub mouse_pos() -> ubyte @A, uword @R0, uword @R1, byte @X {
}}
}
sub mouse_present() -> bool {
; -- check if a mouse is connected to the machine
cx16.r0L, void = cx16.i2c_read_byte($42, $22) ; $22 = I2C_GET_MOUSE_DEVICE_ID
if_cs
return false
return cx16.r0L != $fc ; $fc = BAT_FAIL
}
; shims for the kernal routines called via the extapi call:
asmsub mouse_set_pos(uword xpos @R0, uword ypos @R1) clobbers(X) {
+1 -1
View File
@@ -233,7 +233,7 @@ math {
sub direction(ubyte x1, ubyte y1, ubyte x2, ubyte y2) -> ubyte {
; From a pair of positive coordinates, calculate discrete direction between 0 and 23 into A.
; This adjusts the atan() result so that the direction N is centered on the angle=N instead of having it as a boundary
ubyte angle = atan2(x1, y1, x2, y2) - 256/48
ubyte angle = atan2(x1, y1, x2, y2) - (256/48 as ubyte)
return 23-lsb(mkword(angle,0) / 2730)
}
+41 -10
View File
@@ -23,7 +23,7 @@ monogfx {
width = 320
height = 240
mode = MODE_NORMAL
clear_screen(0)
clear_screen(false)
}
sub hires() {
@@ -32,7 +32,7 @@ monogfx {
width = 640
height = 480
mode = MODE_NORMAL
clear_screen(0)
clear_screen(false)
}
sub textmode() {
@@ -43,9 +43,10 @@ monogfx {
mode = dm
}
sub clear_screen(ubyte color) {
if color!=0
color=255
sub clear_screen(bool draw) {
ubyte color = 0
if draw
color = 255
sys.gfx_clear(color)
}
@@ -64,7 +65,7 @@ monogfx {
sub fillrect(uword xx, uword yy, uword rwidth, uword rheight, bool draw) {
; Draw a filled rectangle of the given size and color.
; To fill the whole screen, use clear_screen(color) instead - it is much faster.
; To fill the whole screen, use clear_screen(draw) instead - it is much faster.
if rwidth==0
return
repeat rheight {
@@ -300,12 +301,23 @@ monogfx {
return
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-radius
uword last_y3 = ycenter+radius
uword last_y4 = ycenter-radius
uword new_y3, new_y4
while radius>=yy {
horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, draw)
horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, draw)
horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, draw)
horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, draw)
new_y3 = ycenter+radius
if new_y3 != last_y3 {
horizontal_line(xcenter-yy, last_y3, yy*$0002+1, draw)
last_y3 = new_y3
}
new_y4 = ycenter-radius
if new_y4 != last_y4 {
horizontal_line(xcenter-yy, last_y4, yy*$0002+1, draw)
last_y4 = new_y4
}
yy++
if decisionOver2>=0 {
radius--
@@ -314,6 +326,10 @@ monogfx {
decisionOver2 += yy*$0002
decisionOver2++
}
; draw the final two spans
yy--
horizontal_line(xcenter-yy, last_y3, yy*$0002+1, draw)
horizontal_line(xcenter-yy, last_y4, yy*$0002+1, draw)
}
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, bool draw) {
@@ -323,12 +339,23 @@ monogfx {
return
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-radius
uword last_y3 = ycenter+radius
uword last_y4 = ycenter-radius
uword new_y3, new_y4
while radius>=yy {
safe_horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, draw)
safe_horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, draw)
safe_horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, draw)
safe_horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, draw)
new_y3 = ycenter+radius
if new_y3 != last_y3 {
safe_horizontal_line(xcenter-yy, last_y3, yy*$0002+1, draw)
last_y3 = new_y3
}
new_y4 = ycenter-radius
if new_y4 != last_y4 {
safe_horizontal_line(xcenter-yy, last_y4, yy*$0002+1, draw)
last_y4 = new_y4
}
yy++
if decisionOver2>=0 {
radius--
@@ -337,6 +364,10 @@ monogfx {
decisionOver2 += yy*$0002
decisionOver2++
}
; draw the final two spans
yy--
safe_horizontal_line(xcenter-yy, last_y3, yy*$0002+1, draw)
safe_horizontal_line(xcenter-yy, last_y4, yy*$0002+1, draw)
}
sub plot(uword @zp xx, uword @zp yy, bool @zp draw) {
+5 -3
View File
@@ -19,6 +19,11 @@ import kotlin.system.exitProcess
fun main(args: Array<String>) {
// NOTE: if your compiler/IDE complains here about "Unresolved reference: buildversion",
// it means that you have to run the gradle task once to generate this file.
// Do that with this command: gradle createVersionFile
println("\nProg8 compiler v${prog8.buildversion.VERSION} by Irmen de Jong (irmen@razorvine.net)")
if('-' in prog8.buildversion.VERSION) {
println("Prerelease version from git commit ${prog8.buildversion.GIT_SHA.take(8)} in branch ${prog8.buildversion.GIT_BRANCH}")
@@ -45,7 +50,6 @@ private fun compileMain(args: Array<String>): Boolean {
val dumpVariables by cli.option(ArgType.Boolean, fullName = "dumpvars", description = "print a dump of the variables in the program")
val dumpSymbols by cli.option(ArgType.Boolean, fullName = "dumpsymbols", description = "print a dump of the variable declarations and subroutine signatures")
val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code")
val noStrictBool by cli.option(ArgType.Boolean, fullName = "nostrictbool", description = "allow implicit conversions between bool and bytes")
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform code optimizations")
val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".")
val quietAssembler by cli.option(ArgType.Boolean, fullName = "quietasm", description = "don't print assembler output results")
@@ -178,7 +182,6 @@ private fun compileMain(args: Array<String>): Boolean {
breakpointCpuInstruction,
printAst1 == true,
printAst2 == true,
noStrictBool != true,
processedSymbols,
srcdirs,
outputPath
@@ -259,7 +262,6 @@ private fun compileMain(args: Array<String>): Boolean {
breakpointCpuInstruction,
printAst1 == true,
printAst2 == true,
noStrictBool != true,
processedSymbols,
srcdirs,
outputPath
+4 -6
View File
@@ -54,7 +54,6 @@ class CompilerArguments(val filepath: Path,
val breakpointCpuInstruction: String?,
val printAst1: Boolean,
val printAst2: Boolean,
val strictBool: Boolean,
val symbolDefs: Map<String, String>,
val sourceDirs: List<String> = emptyList(),
val outputDir: Path = Path(""),
@@ -91,7 +90,6 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
splitWordArrays = args.splitWordArrays
outputDir = args.outputDir.normalize()
symbolDefs = args.symbolDefs
strictBool = args.strictBool
}
resultingProgram = program
importedFiles = imported
@@ -412,7 +410,7 @@ private fun processAst(program: Program, errors: IErrorReporter, compilerOptions
errors.report()
program.reorderStatements(errors)
errors.report()
program.desugaring(errors, compilerOptions)
program.desugaring(errors)
errors.report()
program.changeNotExpressionAndIfComparisonExpr(errors, compilerOptions.compTarget)
errors.report()
@@ -438,7 +436,7 @@ private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, e
removeUnusedCode(program, errors,compilerOptions)
while (true) {
// keep optimizing expressions and statements until no more steps remain
val optsDone1 = program.simplifyExpressions(errors, compilerOptions)
val optsDone1 = program.simplifyExpressions(errors)
val optsDone2 = program.optimizeStatements(errors, functions, compilerOptions)
val optsDone3 = program.inlineSubroutines(compilerOptions)
program.constantFold(errors, compilerOptions) // because simplified statements and expressions can result in more constants that can be folded away
@@ -452,7 +450,7 @@ private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, e
removeUnusedCode(program, errors, compilerOptions)
if(errors.noErrors()) {
// last round of optimizations because constFold may have enabled more...
program.simplifyExpressions(errors, compilerOptions)
program.simplifyExpressions(errors)
program.optimizeStatements(errors, functions, compilerOptions)
program.constantFold(errors, compilerOptions) // because simplified statements and expressions can result in more constants that can be folded away
}
@@ -461,7 +459,7 @@ private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, e
}
private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
program.desugaring(errors, compilerOptions)
program.desugaring(errors)
program.addTypecasts(errors, compilerOptions)
errors.report()
program.variousCleanups(errors, compilerOptions)
@@ -163,9 +163,7 @@ internal class AstChecker(private val program: Program,
override fun visit(ifElse: IfElse) {
if(!ifElse.condition.inferType(program).isBool) {
val ctype = ifElse.condition.inferType(program).getOr(DataType.UNDEFINED)
if(compilerOptions.strictBool || ctype !in ByteDatatypes)
errors.err("condition should be a boolean", ifElse.condition.position)
errors.err("condition should be a boolean", ifElse.condition.position)
}
val constvalue = ifElse.condition.constValue(program)
@@ -506,9 +504,7 @@ internal class AstChecker(private val program: Program,
override fun visit(untilLoop: UntilLoop) {
if(!untilLoop.condition.inferType(program).isBool) {
val ctype = untilLoop.condition.inferType(program).getOr(DataType.UNDEFINED)
if(compilerOptions.strictBool || ctype !in ByteDatatypes)
errors.err("condition should be a boolean", untilLoop.condition.position)
errors.err("condition should be a boolean", untilLoop.condition.position)
}
super.visit(untilLoop)
@@ -516,9 +512,7 @@ internal class AstChecker(private val program: Program,
override fun visit(whileLoop: WhileLoop) {
if(!whileLoop.condition.inferType(program).isBool) {
val ctype = whileLoop.condition.inferType(program).getOr(DataType.UNDEFINED)
if(compilerOptions.strictBool || ctype !in ByteDatatypes)
errors.err("condition should be a boolean", whileLoop.condition.position)
errors.err("condition should be a boolean", whileLoop.condition.position)
}
super.visit(whileLoop)
@@ -780,12 +774,8 @@ internal class AstChecker(private val program: Program,
if (iDt isnot decl.datatype) {
if(decl.isArray) {
val eltDt = ArrayToElementTypes.getValue(decl.datatype)
if(iDt isnot eltDt) {
if(compilerOptions.strictBool)
err("initialisation value has incompatible type ($iDt) for the variable (${decl.datatype})")
else if(!(iDt.isBool && eltDt==DataType.UBYTE || iDt.istype(DataType.UBYTE) && eltDt==DataType.BOOL))
err("initialisation value has incompatible type ($iDt) for the variable (${decl.datatype})")
}
if(iDt isnot eltDt)
err("initialisation value has incompatible type ($iDt) for the variable (${decl.datatype})")
} else {
if(!(iDt.isBool && decl.datatype==DataType.UBYTE || iDt.istype(DataType.UBYTE) && decl.datatype==DataType.BOOL))
err("initialisation value has incompatible type ($iDt) for the variable (${decl.datatype})")
@@ -1048,8 +1038,7 @@ internal class AstChecker(private val program: Program,
}
else if(expr.operator == "not") {
if(dt!=DataType.BOOL) {
if(compilerOptions.strictBool || dt !in ByteDatatypes)
errors.err("logical not is for booleans", expr.position)
errors.err("logical not is for booleans", expr.position)
}
}
super.visit(expr)
@@ -1168,8 +1157,7 @@ internal class AstChecker(private val program: Program,
if(expr.operator in LogicalOperators) {
if (leftDt != DataType.BOOL || rightDt != DataType.BOOL) {
if(compilerOptions.strictBool || leftDt !in ByteDatatypes || rightDt !in ByteDatatypes)
errors.err("logical operator requires boolean operands", expr.right.position)
errors.err("logical operator requires boolean operands", expr.right.position)
}
}
else {
@@ -1248,7 +1236,7 @@ internal class AstChecker(private val program: Program,
errors.warn("sgn() of unsigned type is always 0 or 1, this is perhaps not what was intended", functionCallExpr.args.first().position)
}
val error = VerifyFunctionArgTypes.checkTypes(functionCallExpr, program, compilerOptions)
val error = VerifyFunctionArgTypes.checkTypes(functionCallExpr, program)
if(error!=null)
errors.err(error.first, error.second)
@@ -1339,7 +1327,7 @@ internal class AstChecker(private val program: Program,
}
val error = VerifyFunctionArgTypes.checkTypes(functionCallStatement, program, compilerOptions)
val error = VerifyFunctionArgTypes.checkTypes(functionCallStatement, program)
if(error!=null)
errors.err(error.first, error.second)
@@ -1727,8 +1715,7 @@ internal class AstChecker(private val program: Program,
}
DataType.BOOL -> {
if (value.type!=DataType.BOOL) {
if (compilerOptions.strictBool || value.type !in ByteDatatypes)
err("type of value ${value.type} doesn't match target $targetDt")
err("type of value ${value.type} doesn't match target $targetDt")
}
}
in ArrayDatatypes -> {
@@ -1829,12 +1816,10 @@ internal class AstChecker(private val program: Program,
// this is allowed: a pass-by-reference datatype into a uword (pointer value).
}
else if(sourceDatatype==DataType.BOOL && targetDatatype!=DataType.BOOL) {
if(compilerOptions.strictBool || targetDatatype !in ByteDatatypes)
errors.err("type of value $sourceDatatype doesn't match target $targetDatatype", position)
errors.err("type of value $sourceDatatype doesn't match target $targetDatatype", position)
}
else if(targetDatatype==DataType.BOOL && sourceDatatype!=DataType.BOOL) {
if(compilerOptions.strictBool || sourceDatatype !in ByteDatatypes)
errors.err("type of value $sourceDatatype doesn't match target $targetDatatype", position)
errors.err("type of value $sourceDatatype doesn't match target $targetDatatype", position)
}
else {
errors.err("type of value $sourceDatatype doesn't match target $targetDatatype", position)
@@ -92,8 +92,8 @@ internal fun Program.addTypecasts(errors: IErrorReporter, options: CompilationOp
caster.applyModifications()
}
fun Program.desugaring(errors: IErrorReporter, options: CompilationOptions) {
val desugar = CodeDesugarer(this, options, errors)
fun Program.desugaring(errors: IErrorReporter) {
val desugar = CodeDesugarer(this, errors)
desugar.visit(this)
while(errors.noErrors() && desugar.applyModifications()>0)
desugar.visit(this)
@@ -48,22 +48,30 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
if(decl.name in BuiltinFunctions)
errors.err("builtin function cannot be redefined", decl.position)
if(decl.names.size<2) {
val existingInSameScope = decl.definingScope.lookup(listOf(decl.name))
fun checkNameForErrors(name: String) {
val existingInSameScope = decl.definingScope.lookup(listOf(name))
if (existingInSameScope != null && existingInSameScope !== decl)
nameError(decl.name, decl.position, existingInSameScope)
nameError(name, decl.position, existingInSameScope)
val existingOuter = decl.parent.definingScope.lookup(listOf(decl.name))
val existingOuter = decl.parent.definingScope.lookup(listOf(name))
if (existingOuter != null && existingOuter !== decl) {
if (existingOuter is VarDecl) {
if (existingOuter.parent !== decl.parent)
nameShadowWarning(decl.name, decl.position, existingOuter)
nameShadowWarning(name, decl.position, existingOuter)
else
nameError(decl.name, decl.position, existingOuter)
nameError(name, decl.position, existingOuter)
}
}
}
if(decl.names.size<2) {
checkNameForErrors(decl.name)
}
else {
for (name in decl.names)
checkNameForErrors(name)
}
if(decl.definingBlock.name==decl.name)
nameError(decl.name, decl.position, decl.definingBlock)
if(decl.definingSubroutine?.name==decl.name)
@@ -72,6 +80,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
super.visit(decl)
}
override fun visit(subroutine: Subroutine) {
if(subroutine.name in BuiltinFunctions) {
// the builtin functions can't be redefined
@@ -8,7 +8,7 @@ import prog8.ast.walk.IAstModification
import prog8.code.core.*
internal class CodeDesugarer(val program: Program, private val options: CompilationOptions, private val errors: IErrorReporter) : AstWalker() {
internal class CodeDesugarer(val program: Program, private val errors: IErrorReporter) : AstWalker() {
// Some more code shuffling to simplify the Ast that the codegenerator has to process.
// Several changes have already been done by the StatementReorderer !
@@ -123,9 +123,7 @@ if not CONDITION
override fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
if(!whileLoop.condition.inferType(program).isBool) {
val ctype = whileLoop.condition.inferType(program).getOr(DataType.UNDEFINED)
if(options.strictBool || ctype !in ByteDatatypes)
errors.err("condition should be a boolean", whileLoop.condition.position)
errors.err("condition should be a boolean", whileLoop.condition.position)
}
/*
@@ -11,9 +11,9 @@ import prog8.ast.statements.*
import prog8.code.ast.*
import prog8.code.core.*
import prog8.compiler.builtinFunctionReturnType
import java.io.File
import kotlin.io.path.Path
import kotlin.io.path.isRegularFile
import java.io.File
/**
@@ -623,11 +623,10 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr
}
when(srcCheck.iterable) {
is IdentifierReference -> {
is IdentifierReference, is ArrayLiteral -> {
val check = PtContainmentCheck(srcCheck.position)
check.add(transformExpression(srcCheck.element))
val iterable = transformExpression(srcCheck.iterable)
check.add(iterable)
check.add(transformExpression(srcCheck.iterable))
return check
}
is RangeExpression -> {
@@ -4,19 +4,14 @@ import prog8.ast.IFunctionCall
import prog8.ast.IStatementContainer
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.expressions.*
import prog8.ast.statements.Assignment
import prog8.ast.statements.VarDecl
import prog8.ast.statements.WhenChoice
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.DataType
import prog8.code.core.IErrorReporter
import prog8.code.core.NumericDatatypesWithBoolean
import prog8.code.core.SplitWordArrayTypes
import prog8.code.ast.PtContainmentCheck
import prog8.code.core.*
internal class LiteralsToAutoVars(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
@@ -54,6 +49,16 @@ internal class LiteralsToAutoVars(private val program: Program, private val erro
}
} else {
val arrayDt = array.guessDatatype(program)
val elementDt = ArrayToElementTypes.getValue(arrayDt.getOr(DataType.UNDEFINED))
val maxSize = when(elementDt) {
in ByteDatatypesWithBoolean -> PtContainmentCheck.MAX_SIZE_FOR_INLINE_CHECKS_BYTE
in WordDatatypes -> PtContainmentCheck.MAX_SIZE_FOR_INLINE_CHECKS_WORD
else -> 0
}
if(parent is ContainmentCheck && array.value.size <= maxSize) {
// keep the array in the containmentcheck inline
return noModifications
}
if(arrayDt.isKnown) {
val parentAssign = parent as? Assignment
val targetDt = parentAssign?.target?.inferType(program) ?: arrayDt
@@ -140,7 +140,7 @@ internal class StatementReorderer(
// change 'str' and 'ubyte[]' parameters into 'uword' (just treat it as an address)
val stringParams = subroutine.parameters.filter { it.type==DataType.STR || it.type==DataType.ARRAY_UB }
val parameterChanges = stringParams.map {
val uwordParam = SubroutineParameter(it.name, DataType.UWORD, it.position)
val uwordParam = SubroutineParameter(it.name, DataType.UWORD, it.zp, it.position)
IAstModification.ReplaceNode(it, uwordParam, subroutine)
}
// change 'str' and 'ubyte[]' return types into 'uword' (just treat it as an address)
@@ -24,11 +24,6 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
val valueDt = declValue.inferType(program)
if(valueDt isnot decl.datatype) {
if(decl.isArray && !options.strictBool) {
if(tryConvertBooleanArrays(decl, declValue, parent))
return noModifications
}
if(valueDt.isInteger && decl.isArray) {
if(decl.datatype == DataType.ARRAY_BOOL) {
val integer = declValue.constValue(program)?.number
@@ -54,69 +49,6 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
return noModifications
}
private fun tryConvertBooleanArrays(decl: VarDecl, declValue: Expression, parent: Node): Boolean {
val valueNumber = declValue.constValue(program)
val valueArray = declValue as? ArrayLiteral
when (decl.datatype) {
DataType.ARRAY_BOOL -> {
if(valueNumber!=null) {
decl.value = NumericLiteral.fromBoolean(valueNumber.number!=0.0, declValue.position)
decl.linkParents(parent)
return true
} else if(valueArray!=null) {
val newArray = valueArray.value.map {
if(it.inferType(program).isBytes) {
TypecastExpression(it, DataType.BOOL, false, it.position)
} else {
it
}
}
decl.value = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_BOOL), newArray.toTypedArray(), valueArray.position)
decl.linkParents(parent)
return true
}
}
DataType.ARRAY_B -> {
if(valueNumber!=null) {
decl.value = NumericLiteral(DataType.BYTE, if(valueNumber.asBooleanValue) 1.0 else 0.0, declValue.position)
decl.linkParents(parent)
return true
} else if(valueArray!=null) {
val newArray = valueArray.value.map {
if(it.inferType(program).isBool) {
TypecastExpression(it, DataType.BYTE, false, it.position)
} else {
it
}
}
decl.value = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_B), newArray.toTypedArray(), valueArray.position)
decl.linkParents(parent)
return true
}
}
DataType.ARRAY_UB -> {
if(valueNumber!=null) {
decl.value = NumericLiteral(DataType.UBYTE, if(valueNumber.asBooleanValue) 1.0 else 0.0, declValue.position)
decl.linkParents(parent)
return true
} else if(valueArray!=null) {
val newArray = valueArray.value.map {
if(it.inferType(program).isBool) {
TypecastExpression(it, DataType.UBYTE, false, it.position)
} else {
it
}
}
decl.value = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_UB), newArray.toTypedArray(), valueArray.position)
decl.linkParents(parent)
return true
}
}
else -> { /* no casting possible */ }
}
return false
}
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
val leftDt = expr.left.inferType(program)
val rightDt = expr.right.inferType(program)
@@ -155,26 +87,6 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
}
if(leftDt!=rightDt) {
if(!options.strictBool) {
if (expr.operator in LogicalOperators) {
if (leftDt.isBool) {
val cast = TypecastExpression(expr.right, DataType.BOOL, false, expr.right.position)
return listOf(IAstModification.ReplaceNode(expr.right, cast, expr))
} else {
val cast = TypecastExpression(expr.left, DataType.BOOL, false, expr.left.position)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
}
} else {
if(leftDt.isBool && rightDt.isBytes) {
val cast = TypecastExpression(expr.left, rightDt.getOr(DataType.UNDEFINED), false, expr.left.position)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
} else if(rightDt.isBool && leftDt.isBytes) {
val cast = TypecastExpression(expr.right, leftDt.getOr(DataType.UNDEFINED), false, expr.right.position)
return listOf(IAstModification.ReplaceNode(expr.right, cast, expr))
}
}
}
// convert a negative operand for bitwise operator to the 2's complement positive number instead
if(expr.operator in BitwiseOperators && leftDt.isInteger && rightDt.isInteger) {
if(leftCv!=null && leftCv.number<0) {
@@ -278,42 +190,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
}
val cvalue = assignment.value.constValue(program)
if(cvalue!=null) {
val number = cvalue.number
// more complex comparisons if the type is different, but the constant value is compatible
if(!options.strictBool) {
if (targettype == DataType.BOOL && valuetype in ByteDatatypes) {
val cast = NumericLiteral.fromBoolean(number!=0.0, cvalue.position)
return listOf(IAstModification.ReplaceNode(assignment.value, cast, assignment))
}
if (targettype in ByteDatatypes && valuetype == DataType.BOOL) {
val cast = NumericLiteral(targettype, if(cvalue.asBooleanValue) 1.0 else 0.0, cvalue.position)
return listOf(IAstModification.ReplaceNode(assignment.value, cast, assignment))
}
}
if (valuetype == DataType.BYTE && targettype == DataType.UBYTE) {
if(number>0)
return castLiteral(cvalue)
} else if (valuetype == DataType.WORD && targettype == DataType.UWORD) {
if(number>0)
return castLiteral(cvalue)
} else if (valuetype == DataType.UBYTE && targettype == DataType.BYTE) {
if(number<0x80)
return castLiteral(cvalue)
} else if (valuetype == DataType.UWORD && targettype == DataType.WORD) {
if(number<0x8000)
return castLiteral(cvalue)
}
} else {
if(!options.strictBool) {
if (targettype == DataType.BOOL && valuetype in ByteDatatypes) {
val cast = TypecastExpression(assignment.value, targettype, false, assignment.value.position)
return listOf(IAstModification.ReplaceNode(assignment.value, cast, assignment))
}
if (targettype in ByteDatatypes && valuetype == DataType.BOOL) {
val cast = TypecastExpression(assignment.value, targettype, false, assignment.value.position)
return listOf(IAstModification.ReplaceNode(assignment.value, cast, assignment))
}
}
return castLiteral(cvalue)
}
}
}
@@ -365,8 +242,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
} else if(argDt isAssignableTo targetDt) {
if(argDt!=DataType.STR || targetDt!=DataType.UWORD)
addTypecastOrCastedValueModification(modifications, it.second, targetDt, call as Node)
} else if(!options.strictBool && targetDt in ByteDatatypes && argDt==DataType.BOOL)
addTypecastOrCastedValueModification(modifications, it.second, targetDt, call as Node)
}
}
} else {
val identifier = it.second as? IdentifierReference
@@ -432,18 +308,6 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
if(subroutine.returntypes.size==1) {
val subReturnType = subroutine.returntypes.first()
val returnDt = returnValue.inferType(program)
if(!options.strictBool) {
if(subReturnType==DataType.BOOL && returnDt.isBytes) {
val cast = TypecastExpression(returnValue, DataType.BOOL, false, returnValue.position)
return listOf(IAstModification.ReplaceNode(returnValue, cast, returnStmt))
}
if(subReturnType in ByteDatatypes && returnDt.isBool) {
val cast = TypecastExpression(returnValue, subReturnType, false, returnValue.position)
return listOf(IAstModification.ReplaceNode(returnValue, cast, returnStmt))
}
}
if (returnDt istype subReturnType or returnDt.isNotAssignableTo(subReturnType))
return noModifications
if (returnValue is NumericLiteral) {
@@ -466,7 +330,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
val values = whenChoice.values
values?.toTypedArray()?.withIndex()?.forEach { (index, value) ->
val valueDt = value.inferType(program)
if(valueDt!=conditionDt) {
if(valueDt.isKnown && valueDt!=conditionDt) {
val castedValue = value.typecastTo(conditionDt.getOr(DataType.UNDEFINED), valueDt.getOr(DataType.UNDEFINED), true)
if(castedValue.first) {
castedValue.second.linkParents(whenChoice)
@@ -479,19 +343,68 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
}
override fun after(range: RangeExpression, parent: Node): Iterable<IAstModification> {
val fromConst = range.from.constValue(program)
val toConst = range.to.constValue(program)
if(fromConst!=null) {
val smaller = NumericLiteral.optimalInteger(fromConst.number.toInt(), fromConst.position)
if(fromConst.type.largerThan(smaller.type)) {
val toType = range.to.inferType(program)
if(toType isnot smaller.type) {
if(toConst!=null) {
// can we make the to value into the same smaller type?
val smallerTo = NumericLiteral.optimalInteger(toConst.number.toInt(), toConst.position)
if(smaller.type==smallerTo.type) {
val newRange = RangeExpression(smaller, smallerTo, range.step, range.position)
return listOf(IAstModification.ReplaceNode(range, newRange, parent))
}
}
} else {
val newRange = RangeExpression(smaller, range.to, range.step, range.position)
return listOf(IAstModification.ReplaceNode(range, newRange, parent))
}
}
}
if(toConst!=null) {
val smaller = NumericLiteral.optimalInteger(toConst.number.toInt(), toConst.position)
if(toConst.type.largerThan(smaller.type)) {
val fromType = range.from.inferType(program)
if(fromType isnot smaller.type) {
if(fromConst!=null) {
// can we make the from value into the same smaller type?
val smallerFrom = NumericLiteral.optimalInteger(fromConst.number.toInt(), fromConst.position)
if(smaller.type==smallerFrom.type) {
val newRange = RangeExpression(smallerFrom, smaller, range.step, range.position)
return listOf(IAstModification.ReplaceNode(range, newRange, parent))
}
}
} else {
val newRange = RangeExpression(range.from, smaller, range.step, range.position)
return listOf(IAstModification.ReplaceNode(range, newRange, parent))
}
}
}
val modifications = mutableListOf<IAstModification>()
val fromDt = range.from.inferType(program).getOr(DataType.UNDEFINED)
val toDt = range.to.inferType(program).getOr(DataType.UNDEFINED)
val modifications = mutableListOf<IAstModification>()
val (commonDt, toChange) = BinaryExpression.commonDatatype(fromDt, toDt, range.from, range.to)
if(toChange!=null)
addTypecastOrCastedValueModification(modifications, toChange, commonDt, range)
return modifications
}
override fun after(ifElse: IfElse, parent: Node): Iterable<IAstModification> {
if(!options.strictBool && ifElse.condition.inferType(program).isBytes) {
val cast = TypecastExpression(ifElse.condition, DataType.BOOL, false, ifElse.condition.position)
return listOf(IAstModification.ReplaceNode(ifElse.condition, cast, ifElse))
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
val constIdx = arrayIndexedExpression.indexer.constIndex()
if(constIdx!=null) {
val smaller = NumericLiteral.optimalInteger(constIdx, arrayIndexedExpression.indexer.position)
val idxDt = arrayIndexedExpression.indexer.indexExpr.inferType(program).getOr(DataType.UNDEFINED)
if(idxDt.largerThan(smaller.type)) {
val newIdx = ArrayIndex(smaller, smaller.position)
val newIndexer = ArrayIndexedExpression(arrayIndexedExpression.arrayvar, newIdx, arrayIndexedExpression.position)
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression, newIndexer, parent))
}
}
return noModifications
}
@@ -505,18 +418,6 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
val sourceDt = expressionToCast.inferType(program).getOr(DataType.UNDEFINED)
if(sourceDt == requiredType)
return
if(!options.strictBool) {
if(requiredType==DataType.BOOL && sourceDt in ByteDatatypes) {
val cast = TypecastExpression(expressionToCast, DataType.BOOL, false, expressionToCast.position)
modifications.add(IAstModification.ReplaceNode(expressionToCast, cast, parent))
return
}
if(requiredType in ByteDatatypes && sourceDt==DataType.BOOL) {
val cast = TypecastExpression(expressionToCast, requiredType, false, expressionToCast.position)
modifications.add(IAstModification.ReplaceNode(expressionToCast, cast, parent))
return
}
}
if(requiredType==DataType.BOOL) {
return
}
@@ -97,16 +97,6 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
return listOf(IAstModification.ReplaceNode(expr, expr.expression, parent))
}
if(!options.strictBool && expr.operator=="not") {
if(expr.expression.inferType(program).isBytes) {
// not bytevalue --> bytevalue==0
val cmp = BinaryExpression(expr.expression, "==",
NumericLiteral(expr.expression.inferType(program).getOr(DataType.UNDEFINED), 0.0, expr.expression.position),
expr.expression.position)
return listOf(IAstModification.ReplaceNode(expr, cmp, parent))
}
}
return noModifications
}
@@ -145,9 +135,25 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
}
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") }
if(values.size==2 || values.size==3 && (elementType==DataType.UBYTE || elementType==DataType.UWORD)) {
val numbers = values.map{it.number}.toSet()
if(numbers == setOf(0.0, 1.0)) {
// we can replace unsigned x==0 or x==1 with x<2
val compare = BinaryExpression(needle, "<", NumericLiteral(elementType, 2.0, expr.position), expr.position)
return listOf(IAstModification.ReplaceNode(expr, compare, parent))
}
if(numbers == setOf(0.0, 1.0, 2.0)) {
// we can replace unsigned x==0 or x==1 or x==2 with x<3
val compare = BinaryExpression(needle, "<", NumericLiteral(elementType, 3.0, expr.position), expr.position)
return listOf(IAstModification.ReplaceNode(expr, compare, parent))
}
}
if(values.size<2)
return noModifications
// replace x==1 or x==2 or x==3 with a containment check x in [1,2,3]
val valueCopies = values.sortedBy { it.number }.map { it.copy() }
val arrayType = ElementToArrayTypes.getValue(elementType)
val valuesArray = ArrayLiteral(InferredTypes.InferredType.known(arrayType), valueCopies.toTypedArray(), expr.position)
val containment = ContainmentCheck(needle, valuesArray, expr.position)
@@ -41,7 +41,7 @@ internal class VerifyFunctionArgTypes(val program: Program, val options: Compila
private val memorySlabs = mutableListOf<Slab>()
override fun visit(functionCallExpr: FunctionCallExpression) {
val error = checkTypes(functionCallExpr as IFunctionCall, program, options)
val error = checkTypes(functionCallExpr as IFunctionCall, program)
if(error!=null)
errors.err(error.first, error.second)
else {
@@ -62,7 +62,7 @@ internal class VerifyFunctionArgTypes(val program: Program, val options: Compila
}
override fun visit(functionCallStatement: FunctionCallStatement) {
val error = checkTypes(functionCallStatement as IFunctionCall, program, options)
val error = checkTypes(functionCallStatement as IFunctionCall, program)
if(error!=null)
errors.err(error.first, error.second)
@@ -85,7 +85,7 @@ internal class VerifyFunctionArgTypes(val program: Program, val options: Compila
return false
}
fun checkTypes(call: IFunctionCall, program: Program, options: CompilationOptions): Pair<String, Position>? {
fun checkTypes(call: IFunctionCall, program: Program): Pair<String, Position>? {
val argITypes = call.args.map { it.inferType(program) }
val firstUnknownDt = argITypes.indexOfFirst { it.isUnknown }
if(firstUnknownDt>=0) {
@@ -105,12 +105,7 @@ internal class VerifyFunctionArgTypes(val program: Program, val options: Compila
if(mismatch>=0) {
val actual = argtypes[mismatch]
val expected = consideredParamTypes[mismatch]
return if(expected==DataType.BOOL && !options.strictBool && actual in ByteDatatypes)
null
else if(expected in ByteDatatypes && !options.strictBool && actual==DataType.BOOL)
null
else
Pair("argument ${mismatch + 1} type mismatch, was: $actual expected: $expected", call.args[mismatch].position)
return Pair("argument ${mismatch + 1} type mismatch, was: $actual expected: $expected", call.args[mismatch].position)
}
if(target.isAsmSubroutine) {
if(target.asmReturnvaluesRegisters.size>1) {
+1 -1
View File
@@ -1,4 +1,4 @@
package prog8tests
package prog8tests.compiler
import com.github.michaelbull.result.getErrorOrElse
import com.github.michaelbull.result.getOrElse
+1 -1
View File
@@ -1,4 +1,4 @@
package prog8tests
package prog8tests.compiler
import io.kotest.core.config.AbstractProjectConfig
import io.kotest.core.spec.SpecExecutionOrder
+1 -1
View File
@@ -1,4 +1,4 @@
package prog8tests
package prog8tests.compiler
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
+1 -1
View File
@@ -1,4 +1,4 @@
package prog8tests
package prog8tests.compiler
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
+1 -1
View File
@@ -1,4 +1,4 @@
package prog8tests
package prog8tests.compiler
import io.kotest.assertions.fail
import io.kotest.assertions.withClue
+41 -40
View File
@@ -1,6 +1,7 @@
package prog8tests
package prog8tests.compiler
import io.kotest.core.spec.style.FunSpec
import io.kotest.datatest.withData
import io.kotest.matchers.shouldNotBe
import prog8.code.core.ICompilationTarget
import prog8.code.target.*
@@ -11,7 +12,6 @@ import prog8tests.helpers.*
import java.nio.file.Path
import kotlin.io.path.absolute
import kotlin.io.path.exists
import kotlin.io.path.readText
/**
@@ -43,7 +43,6 @@ private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilat
breakpointCpuInstruction = null,
printAst1 = false,
printAst2 = false,
strictBool = true,
symbolDefs = emptyMap(),
outputDir = outputDir
)
@@ -90,13 +89,14 @@ class TestCompilerOnExamplesC64: FunSpec({
listOf(false, true)
)
onlyC64.forEach {
val (source, optimize) = it
val target = C64Target()
val (displayName, filepath) = prepareTestFiles(source, optimize, target)
test(displayName) {
compileTheThing(filepath, optimize, target) shouldNotBe null
}
val target = C64Target()
withData(
nameFn = { it.second.first },
onlyC64.map { it to prepareTestFiles(it.first, it.second, target) }
) { (params, prep) ->
val filepath = prep.second
val optimize = params.second
compileTheThing(filepath, optimize, target) shouldNotBe null
}
})
@@ -130,6 +130,7 @@ class TestCompilerOnExamplesCx16: FunSpec({
"highresbitmap",
"kefrenbars",
"keyboardhandler",
"life",
"mandelbrot",
"multi-irq-old",
"multi-irq-new",
@@ -145,13 +146,14 @@ class TestCompilerOnExamplesCx16: FunSpec({
listOf(false, true)
)
onlyCx16.forEach {
val (source, optimize) = it
val target = Cx16Target()
val (displayName, filepath) = prepareTestFiles(source, optimize, target)
test(displayName) {
compileTheThing(filepath, optimize, target) shouldNotBe null
}
val target = Cx16Target()
withData(
nameFn = { it.second.first },
onlyCx16.map { it to prepareTestFiles(it.first, it.second, target) }
) { (params, prep) ->
val filepath = prep.second
val optimize = params.second
compileTheThing(filepath, optimize, target) shouldNotBe null
}
})
@@ -181,40 +183,39 @@ class TestCompilerOnExamplesBothC64andCx16: FunSpec({
"tehtriz",
"textelite",
),
listOf(false, true)
listOf(false, true),
listOf(C64Target(), Cx16Target())
)
bothCx16AndC64.forEach {
val (source, optimize) = it
val c64target = C64Target()
val cx16target = Cx16Target()
val (displayNameC64, filepathC64) = prepareTestFiles(source, optimize, c64target)
val (displayNameCx16, filepathCx16) = prepareTestFiles(source, optimize, cx16target)
test(displayNameC64) {
compileTheThing(filepathC64, optimize, c64target) shouldNotBe null
}
test(displayNameCx16) {
compileTheThing(filepathCx16, optimize, cx16target) shouldNotBe null
}
withData(
nameFn = { it.third.first },
bothCx16AndC64.map { Triple(it.second, it.third, prepareTestFiles(it.first, it.second, it.third)) }
) { params ->
val filepath = params.third.second
val optimize = params.first
compileTheThing(filepath, optimize, params.second) shouldNotBe null
}
})
class TestCompilerOnExamplesVirtual: FunSpec({
val onlyVirtual = listOf(
val onlyVirtual = cartesianProduct(
listOf(
"bouncegfx",
"bsieve",
"pixelshader",
"sincos"
)
),
listOf(false, true)
)
onlyVirtual.forEach {
val target = VMTarget()
val (displayName, filepath) = prepareTestFiles(it, false, target)
test(displayName) {
val src = filepath.readText()
compileText(target, false, src, writeAssembly = true) shouldNotBe null
compileText(target, true, src, writeAssembly = true) shouldNotBe null
}
val target = VMTarget()
withData(
nameFn = { it.second.first },
onlyVirtual.map { it to prepareTestFiles(it.first, it.second, target) }
) { (params, prep) ->
val filepath = prep.second
val optimize = params.second
compileTheThing(filepath, optimize, target) shouldNotBe null
}
})
@@ -1,4 +1,4 @@
package prog8tests
package prog8tests.compiler
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
+21 -22
View File
@@ -1,7 +1,8 @@
package prog8tests
package prog8tests.compiler
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.datatest.withData
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
@@ -93,26 +94,26 @@ class TestCompilerOnRanges: FunSpec({
}
context("floatArrayInitializerWithRange") {
val combos = cartesianProduct(
listOf("", "42", "41"), // sizeInDecl
listOf("%import floats", ""), // optEnableFloats
listOf(Cx16Target(), C64Target()), // platform
listOf(false, true) // optimize
)
combos.forEach {
val (sizeInDecl, optEnableFloats, platform, optimize) = it
val displayName =
when (sizeInDecl) {
withData(
nameFn = {
when (it.first) {
"" -> "no"
"42" -> "correct"
else -> "wrong"
} + " array size given" +
", " + (if (optEnableFloats == "") "without" else "with") + " %option enable_floats" +
", ${platform.name}, optimize: $optimize"
", " + (if (it.second == "") "without" else "with") + " %option enable_floats" +
", ${it.third.name}, optimize: ${it.fourth}"
},
cartesianProduct(
listOf("", "42", "41"), // sizeInDecl
listOf("%import floats", ""), // optEnableFloats
listOf(Cx16Target(), C64Target()), // platform
listOf(false, true) // optimize
)
) { seq ->
val (sizeInDecl, optEnableFloats, platform, optimize) = seq
test(displayName) {
val result = compileText(platform, optimize, """
val result = compileText(platform, optimize, """
$optEnableFloats
main {
sub start() {
@@ -121,12 +122,10 @@ class TestCompilerOnRanges: FunSpec({
}
}
""")
if (optEnableFloats != "" && (sizeInDecl=="" || sizeInDecl=="42"))
result shouldNotBe null
else
result shouldBe null
}
if (optEnableFloats != "" && (sizeInDecl=="" || sizeInDecl=="42"))
result shouldNotBe null
else
result shouldBe null
}
}
+1 -2
View File
@@ -1,4 +1,4 @@
package prog8tests
package prog8tests.compiler
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldNotBe
@@ -41,7 +41,6 @@ class TestCompilerOptionSourcedirs: FunSpec({
breakpointCpuInstruction = null,
printAst1 = false,
printAst2 = false,
strictBool = true,
symbolDefs = emptyMap(),
sourceDirs,
outputDir
+1 -1
View File
@@ -1,4 +1,4 @@
package prog8tests
package prog8tests.compiler
import com.github.michaelbull.result.expectError
import com.github.michaelbull.result.getOrThrow
@@ -1,4 +1,4 @@
package prog8tests
package prog8tests.compiler
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
+1 -1
View File
@@ -1,4 +1,4 @@
package prog8tests
package prog8tests.compiler
import io.kotest.core.spec.style.FunSpec
import prog8.code.target.VMTarget
+35 -6
View File
@@ -1,6 +1,8 @@
package prog8tests
package prog8tests.compiler
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FunSpec
import io.kotest.datatest.withData
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import prog8.ast.Module
@@ -10,11 +12,8 @@ import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteral
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.code.core.*
import prog8.code.target.*
import prog8tests.helpers.DummyFunctions
import prog8tests.helpers.DummyMemsizer
import prog8tests.helpers.DummyStringEncoder
@@ -244,4 +243,34 @@ class TestMemory: FunSpec({
}
""", writeAssembly = true) shouldBe null
}
context("memsizer") {
withData(VMTarget(), AtariTarget(), C64Target(), PETTarget(), AtariTarget(), C128Target()) { target ->
shouldThrow<IllegalArgumentException> {
target.memorySize(DataType.UNDEFINED)
}
shouldThrow<IllegalArgumentException> {
target.memorySize(DataType.LONG)
}
target.memorySize(DataType.BOOL) shouldBe 1
target.memorySize(DataType.BYTE) shouldBe 1
target.memorySize(DataType.WORD) shouldBe 2
target.memorySize(DataType.FLOAT) shouldBe target.machine.FLOAT_MEM_SIZE
target.memorySize(DataType.STR) shouldBe 2
target.memorySize(DataType.ARRAY_UB) shouldBe 2
target.memorySize(DataType.ARRAY_UW) shouldBe 2
target.memorySize(DataType.ARRAY_F) shouldBe 2
shouldThrow<NoSuchElementException> {
target.memorySize(DataType.UBYTE, 10)
}
target.memorySize(DataType.UWORD, 10) shouldBe 10 // uword is pointer to array of bytes
target.memorySize(DataType.ARRAY_B, 10) shouldBe 10
target.memorySize(DataType.ARRAY_UB, 10) shouldBe 10
target.memorySize(DataType.ARRAY_F, 10) shouldBe 10*target.machine.FLOAT_MEM_SIZE
target.memorySize(DataType.ARRAY_UW, 10) shouldBe 20
target.memorySize(DataType.ARRAY_W_SPLIT, 10) shouldBe 20
target.memorySize(DataType.ARRAY_UW_SPLIT, 10) shouldBe 20
}
}
})
+1 -1
View File
@@ -1,4 +1,4 @@
package prog8tests
package prog8tests.compiler
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FunSpec
+1 -1
View File
@@ -1,4 +1,4 @@
package prog8tests
package prog8tests.compiler
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FunSpec
+35 -5
View File
@@ -1,4 +1,4 @@
package prog8tests
package prog8tests.compiler
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
@@ -523,21 +523,24 @@ main {
xxValue.right shouldBe NumericLiteral(DataType.UWORD, 10.0, Position.DUMMY)
}
test("multi-comparison replaced by containment check") {
test("multi-comparison with many values replaced by containment check on heap variable") {
val src="""
main {
sub start() {
ubyte @shared source=99
ubyte @shared thingy=42
if source==3 or source==4 or source==99 or source==1
if source==3 or source==4 or source==99 or source==1 or source==2 or source==42
thingy++
}
}"""
compileText(VMTarget(), optimize=true, src, writeAssembly=true) shouldNotBe null
val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!!
/*
expected result:
ubyte[] auto_heap_var = [1,4,99,3]
ubyte[] auto_heap_var = [1,2,3,4,42,99]
ubyte source
source = 99
ubyte thingy
@@ -553,16 +556,43 @@ main {
(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
arrayDecl.arraysize?.constIndex() shouldBe 6
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(2, Position.DUMMY),
NumericLiteral.optimalInteger(3, Position.DUMMY),
NumericLiteral.optimalInteger(4, Position.DUMMY),
NumericLiteral.optimalInteger(42, Position.DUMMY),
NumericLiteral.optimalInteger(99, Position.DUMMY))
}
test("multi-comparison with few values replaced by inline containment check") {
val src="""
main {
sub start() {
ubyte @shared source=99
ubyte @shared thingy=42
if source==3 or source==4 or source==99
thingy++
}
}"""
compileText(VMTarget(), optimize=true, src, writeAssembly=true) shouldNotBe null
val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!!
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 5
val ifStmt = stmts[4] as IfElse
val containment = ifStmt.condition as ContainmentCheck
(containment.element as IdentifierReference).nameInSource shouldBe listOf("source")
val array = (containment.iterable as ArrayLiteral)
array.value.size shouldBe 3
array.value.map { (it as NumericLiteral).number } shouldBe listOf(3.0, 4.0, 99.0)
}
test("invalid multi-comparison (not all equals) not replaced") {
val src="""
main {
+1 -1
View File
@@ -1,4 +1,4 @@
package prog8tests
package prog8tests.compiler
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FunSpec
+1 -1
View File
@@ -1,4 +1,4 @@
package prog8tests
package prog8tests.compiler
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
+1 -1
View File
@@ -1,4 +1,4 @@
package prog8tests
package prog8tests.compiler
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.expectError
+1 -1
View File
@@ -1,4 +1,4 @@
package prog8tests
package prog8tests.compiler
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
+1 -1
View File
@@ -1,4 +1,4 @@
package prog8tests
package prog8tests.compiler
import io.kotest.assertions.fail
import io.kotest.core.spec.style.FunSpec
+19 -1
View File
@@ -1,4 +1,4 @@
package prog8tests
package prog8tests.compiler
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.ints.shouldBeGreaterThan
@@ -839,4 +839,22 @@ main {
compileText(VMTarget(), false, src, writeAssembly = true) shouldNotBe null
compileText(C64Target(), false, src, writeAssembly = true) shouldNotBe null
}
test("undefined symbol error instead of type cast error") {
val src="""
main {
const ubyte foo = 0
ubyte bar = 0
sub start() {
when foo {
notdefined -> bar = 1
else -> bar = 2
}
}
}"""
val errors = ErrorReporterForTests()
compileText(C64Target(), false, src, writeAssembly = false, errors = errors) shouldBe null
errors.errors.size shouldBe 2
errors.errors[1] shouldContain "undefined symbol"
}
})
+1 -1
View File
@@ -1,4 +1,4 @@
package prog8tests
package prog8tests.compiler
import com.github.michaelbull.result.expectError
import com.github.michaelbull.result.getOrElse
+44
View File
@@ -193,4 +193,48 @@ main {
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "statement"
}
test("redefined variable name in single declaration is reported") {
val src="""
main {
sub start() {
const ubyte count=11
cx16.r0++
ubyte count = 88 ; redefinition
cx16.r0 = count
}
}"""
val errors=ErrorReporterForTests()
compileText(C64Target(), false, src, writeAssembly = false, errors=errors) shouldBe null
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "name conflict"
errors.clear()
compileText(C64Target(), true, src, writeAssembly = false, errors=errors) shouldBe null
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "name conflict"
}
test("redefined variable name in multi declaration is reported") {
val src="""
main {
sub start() {
ubyte i
i++
ubyte i, j ; redefinition
i++
j++
}
}
"""
val errors=ErrorReporterForTests()
compileText(C64Target(), false, src, writeAssembly = false, errors=errors) shouldBe null
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "name conflict"
errors.clear()
compileText(C64Target(), true, src, writeAssembly = false, errors=errors) shouldBe null
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "name conflict"
}
})
+10
View File
@@ -1046,4 +1046,14 @@ main {
(assigns[3].value as NumericLiteral).number shouldBe 3000001.141592654
}
test("oneliner") {
val src="""
main { sub start() { cx16.r0++ cx16.r1++ } }
other { asmsub thing() { %asm {{ inx }} } }
""";
val result = compileText(VMTarget(), false, src, writeAssembly = false)!!
val st = result.compilerAst.entrypoint.statements
st.size shouldBe 2
}
})
+3 -3
View File
@@ -10,8 +10,8 @@ import prog8.code.ast.PtAssignTarget
import prog8.code.ast.PtAssignment
import prog8.code.core.DataType
import prog8.code.core.SourceCode
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.code.target.VMTarget
import prog8.parser.Prog8Parser.parseModule
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
@@ -136,7 +136,7 @@ main {
}}
}
}"""
compileText(VMTarget(), false, src, writeAssembly = true) shouldNotBe null
compileText(C64Target(), false, src, writeAssembly = true) shouldNotBe null
val errors = ErrorReporterForTests()
val result = compileText(Cx16Target(), false, src, errors, true)!!
errors.errors.size shouldBe 0
@@ -186,7 +186,7 @@ main {
romsub ${'$'}8003 = test3() -> uword @R1, bool @Pc, ubyte @X
}"""
compileText(VMTarget(), false, src, writeAssembly = true) shouldNotBe null
compileText(C64Target(), false, src, writeAssembly = true) shouldNotBe null
val errors = ErrorReporterForTests()
val result = compileText(Cx16Target(), false, src, errors, true)!!
errors.errors.size shouldBe 0
@@ -111,16 +111,23 @@ main {
sub start() {
}
}"""
compileText(C64Target(), false, srcWrong1, writeAssembly = false) shouldBe null
val errors = ErrorReporterForTests()
compileText(C64Target(), false, srcWrong1, writeAssembly = false, errors=errors) shouldBe null
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "split can only be used on word arrays"
val srcWrong2 = """
%option enable_floats
main {
float[10] @split sf
sub start() {
}
}"""
compileText(C64Target(), false, srcWrong2, writeAssembly = false) shouldBe null
errors.clear()
compileText(C64Target(), false, srcWrong2, writeAssembly = false, errors=errors) shouldBe null
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "split can only be used on word arrays"
}
test("split word arrays in asm as lsb/msb") {
@@ -383,4 +383,61 @@ main {
compileText(AtariTarget(), false, text, writeAssembly = true) shouldNotBe null
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
}
test("for loops with just 1 iteration") {
val src="""
main {
sub start() {
for cx16.r0L in 100 downto 100 {
cx16.r1++
}
for cx16.r0L in 100 to 100 {
cx16.r1++
}
for cx16.r0 in 2222 downto 2222 {
cx16.r1++
}
for cx16.r0 in 2222 to 2222 {
cx16.r1++
}
for cx16.r0L in 100 downto 100 step -5 {
cx16.r1++
}
for cx16.r0L in 100 to 100 step 5 {
cx16.r1++
}
for cx16.r0 in 2222 downto 2222 step -5 {
cx16.r1++
}
for cx16.r0 in 2222 to 2222 step 5 {
cx16.r1++
}
}
}"""
compileText(VMTarget(), true, src, writeAssembly = true) shouldNotBe null
compileText(VMTarget(), false, src, writeAssembly = true) shouldNotBe null
compileText(Cx16Target(), true, src, writeAssembly = true) shouldNotBe null
compileText(Cx16Target(), false, src, writeAssembly = true) shouldNotBe null
}
test("multiple status flags return values from asmsub") {
val src="""
main {
romsub 5000 = carryAndNegativeAndByteAndWord() -> bool @Pc, bool @Pn, ubyte @X, uword @AY
sub start() {
ubyte @shared x
uword @shared w
bool @shared flag1
bool @shared flag2
flag1, flag2, x, w = carryAndNegativeAndByteAndWord()
flag1, void, void, w = carryAndNegativeAndByteAndWord()
void, void, void, void = carryAndNegativeAndByteAndWord()
void carryAndNegativeAndByteAndWord()
}
}"""
compileText(Cx16Target(), false, src, writeAssembly = true) shouldNotBe null
}
})
-1
View File
@@ -43,7 +43,6 @@ internal fun compileFile(
breakpointCpuInstruction = null,
printAst1 = false,
printAst2 = false,
strictBool = true
)
return compileProgram(args)
}
-35
View File
@@ -29,38 +29,3 @@ fun <T, U, V, W> cartesianProduct(
yield(Product(a, b, c, d))
}
}
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()
+1 -1
View File
@@ -1,4 +1,4 @@
package prog8tests
package prog8tests.compiler
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.assertions.withClue
@@ -189,7 +189,7 @@ private fun Asmsub_declContext.toAst(): AsmsubDecl {
val params = asmsub_params()?.toAst() ?: emptyList()
val returns = asmsub_returns()?.toAst() ?: emptyList()
val clobbers = asmsub_clobbers()?.clobber()?.toAst() ?: emptySet()
val normalParameters = params.map { SubroutineParameter(it.name, it.type, it.position) }
val normalParameters = params.map { SubroutineParameter(it.name, it.type, it.zp, it.position) }
val normalReturntypes = returns.map { it.type }
val paramRegisters = params.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag) }
val returnRegisters = returns.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag) }
@@ -200,7 +200,7 @@ private class AsmSubroutineParameter(name: String,
type: DataType,
val registerOrPair: RegisterOrPair?,
val statusflag: Statusflag?,
position: Position) : SubroutineParameter(name, type, position)
position: Position) : SubroutineParameter(name, type, ZeropageWish.DONTCARE, position)
private class AsmSubroutineReturn(val type: DataType,
val registerOrPair: RegisterOrPair?,
@@ -310,6 +310,8 @@ private fun SubroutineContext.toAst() : Subroutine {
private fun Sub_paramsContext.toAst(): List<SubroutineParameter> =
vardecl().map {
val options = it.decloptions()
val zp = getZpOption(options)
var datatype = it.datatype()?.toAst() ?: DataType.UNDEFINED
if(it.ARRAYSIG()!=null || it.arrayindex()!=null)
datatype = ElementToArrayTypes.getValue(datatype)
@@ -318,9 +320,20 @@ private fun Sub_paramsContext.toAst(): List<SubroutineParameter> =
if(identifiers.size>1)
throw SyntaxError("parameter name must be singular", identifiers[0].toPosition())
val identifiername = identifiers[0].NAME() ?: identifiers[0].UNDERSCORENAME() ?: identifiers[0].VOID()
SubroutineParameter(identifiername.text, datatype, it.toPosition())
SubroutineParameter(identifiername.text, datatype, zp, it.toPosition())
}
private fun getZpOption(options: DecloptionsContext?): ZeropageWish {
if(options==null)
return ZeropageWish.DONTCARE
return when {
options.ZEROPAGEREQUIRE().isNotEmpty() -> ZeropageWish.REQUIRE_ZEROPAGE
options.ZEROPAGE().isNotEmpty() -> ZeropageWish.PREFER_ZEROPAGE
options.ZEROPAGENOT().isNotEmpty() -> ZeropageWish.NOT_IN_ZEROPAGE
else -> ZeropageWish.DONTCARE
}
}
private fun Assign_targetContext.toAst() : AssignTarget {
return when(this) {
is IdentifierTargetContext -> {
@@ -671,12 +684,7 @@ private fun When_choiceContext.toAst(): WhenChoice {
private fun VardeclContext.toAst(type: VarDeclType, value: Expression?): VarDecl {
val options = decloptions()
val zp = when {
options.ZEROPAGEREQUIRE().isNotEmpty() -> ZeropageWish.REQUIRE_ZEROPAGE
options.ZEROPAGE().isNotEmpty() -> ZeropageWish.PREFER_ZEROPAGE
options.ZEROPAGENOT().isNotEmpty() -> ZeropageWish.NOT_IN_ZEROPAGE
else -> ZeropageWish.DONTCARE
}
val zp = getZpOption(options)
val identifiers = identifier()
val identifiername = identifiers[0].NAME() ?: identifiers[0].UNDERSCORENAME() ?: identifiers[0].VOID()
val name = if(identifiers.size==1) identifiername.text else "<multiple>"
@@ -5,14 +5,13 @@ import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.ExpressionError
import prog8.ast.base.FatalAstException
import prog8.ast.base.UndefinedSymbolError
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstVisitor
import prog8.code.core.*
import prog8.code.target.encodings.JapaneseCharacterConverter
import java.io.CharConversionException
import java.util.*
import java.util.Objects
import kotlin.math.abs
import kotlin.math.floor
import kotlin.math.truncate
@@ -498,6 +497,24 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
fun fromBoolean(bool: Boolean, position: Position) =
NumericLiteral(DataType.BOOL, if(bool) 1.0 else 0.0, position)
fun optimalNumeric(origType1: DataType, origType2: DataType?, value: Number, position: Position) : NumericLiteral {
val optimal = optimalNumeric(value, position)
val largestOrig = if(origType2==null) origType1 else if(origType1.largerThan(origType2)) origType1 else origType2
if(largestOrig.largerThan(optimal.type))
return NumericLiteral(largestOrig, optimal.number, position)
else
return optimal
}
fun optimalInteger(origType1: DataType, origType2: DataType?, value: Int, position: Position): NumericLiteral {
val optimal = optimalInteger(value, position)
val largestOrig = if(origType2==null) origType1 else if(origType1.largerThan(origType2)) origType1 else origType2
if(largestOrig.largerThan(optimal.type))
return NumericLiteral(largestOrig, optimal.number, position)
else
return optimal
}
fun optimalNumeric(value: Number, position: Position): NumericLiteral {
val digits = floor(value.toDouble()) - value.toDouble()
return if(value is Double && digits!=0.0) {
@@ -1006,6 +1023,7 @@ class RangeExpression(var from: Expression,
fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
return when {
fromVal == toVal -> fromVal .. toVal
fromVal <= toVal -> when {
stepVal <= 0 -> IntRange.EMPTY
stepVal == 1 -> fromVal..toVal
@@ -1081,7 +1099,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
if(sub.parameters.any { it.name==nameInSource.last() })
return null
}
throw UndefinedSymbolError(this)
return null
}
val vardecl = node as? VarDecl
if(vardecl==null) {
@@ -1089,7 +1107,16 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
} else if(vardecl.type!= VarDeclType.CONST) {
return null
}
return vardecl.value?.constValue(program)
// the value of a variable can (temporarily) be a different type as the vardecl itself.
// don't return the value if the types don't match yet!
val value = vardecl.value?.constValue(program)
if(value==null || value.type==vardecl.datatype)
return value
val optimal = NumericLiteral.optimalNumeric(value.number, value.position)
if(optimal.type==vardecl.datatype)
return optimal
return null
}
override fun toString(): String {
@@ -1,7 +1,7 @@
package prog8.ast.expressions
import prog8.code.core.*
import java.util.*
import java.util.Objects
object InferredTypes {
@@ -236,7 +236,7 @@ class VarDecl(val type: VarDeclType,
fun fromParameter(param: SubroutineParameter): VarDecl {
val dt = if(param.type in ArrayDatatypes) DataType.UWORD else param.type
return VarDecl(VarDeclType.VAR, VarDeclOrigin.SUBROUTINEPARAM, dt, ZeropageWish.DONTCARE, null, param.name, emptyList(), null,
return VarDecl(VarDeclType.VAR, VarDeclOrigin.SUBROUTINEPARAM, dt, param.zp, null, param.name, emptyList(), null,
sharedWithAsm = false,
splitArray = false,
position = param.position
@@ -813,6 +813,7 @@ class Subroutine(override val name: String,
open class SubroutineParameter(val name: String,
val type: DataType,
val zp: ZeropageWish,
final override val position: Position) : Node {
override lateinit var parent: Node
@@ -824,7 +825,7 @@ open class SubroutineParameter(val name: String,
throw FatalAstException("can't replace anything in a subroutineparameter node")
}
override fun copy() = SubroutineParameter(name, type, position)
override fun copy() = SubroutineParameter(name, type, zp, position)
override fun toString() = "Param($type:$name)"
override fun referencesIdentifier(nameInSource: List<String>): Boolean = nameInSource.size==1 && name==nameInSource[0]
}
+16 -5
View File
@@ -160,11 +160,22 @@ class CallGraph(private val program: Program) : IAstVisitor {
fun unused(module: Module) = module !in usedModules
fun unused(sub: Subroutine): Boolean {
return sub !in usedSubroutines && !nameInAssemblyCode(sub.name, listOf("p8s_", ""))
if(sub in usedSubroutines)
return false
val asm = nameInAssemblyCode(sub.name, listOf("p8s_", ""))
if(asm!=null) {
// check that the asm block that calls us, is inside a subroutine that is unused or not
val containingSub = asm.definingSubroutine
return if(containingSub != null && containingSub !== sub)
unused(containingSub)
else
false
}
return true
}
fun unused(block: Block): Boolean {
return block !in usedBlocks && !nameInAssemblyCode(block.name, listOf("p8b_", ""))
return block !in usedBlocks && nameInAssemblyCode(block.name, listOf("p8b_", ""))==null
}
fun unused(decl: VarDecl): Boolean {
@@ -187,13 +198,13 @@ class CallGraph(private val program: Program) : IAstVisitor {
}
private val prefixes = listOf("p8b_", "p8v_", "p8s_", "p8c_", "p8l_", "p8_", "")
private fun nameInAssemblyCode(name: String, knownAsmPrefixes: List<String> = emptyList()): Boolean {
private fun nameInAssemblyCode(name: String, knownAsmPrefixes: List<String> = emptyList()): InlineAssembly? {
if(knownAsmPrefixes.isNotEmpty())
return allAssemblyNodes.any {
return allAssemblyNodes.firstOrNull {
knownAsmPrefixes.any { prefix -> prefix+name in it.names }
}
return allAssemblyNodes.any {
return allAssemblyNodes.firstOrNull {
prefixes.any { prefix -> prefix+name in it.names }
}
}
@@ -1,5 +1,5 @@
Prog8 compiler v10.4 by Irmen de Jong (irmen@razorvine.net)
Prog8 compiler v10.4.1 by Irmen de Jong (irmen@razorvine.net)
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
Compiling program import-all-atari.p8
@@ -1,5 +1,5 @@
Prog8 compiler v10.4 by Irmen de Jong (irmen@razorvine.net)
Prog8 compiler v10.4.1 by Irmen de Jong (irmen@razorvine.net)
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
Compiling program import-all-c128.p8
@@ -1,11 +1,10 @@
Prog8 compiler v10.4 by Irmen de Jong (irmen@razorvine.net)
Prog8 compiler v10.4.1 by Irmen de Jong (irmen@razorvine.net)
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
Compiling program import-all-c64.p8
Compiler target: c64
LIBRARY MODULE NAME: conv
-------------------------
conv {
@@ -470,6 +469,9 @@ cx16 {
}
LIBRARY MODULE NAME: shared_cbm_textio_functions
------------------------------------------------
LIBRARY MODULE NAME: diskio
---------------------------
diskio {
@@ -548,6 +550,9 @@ string {
}
LIBRARY MODULE NAME: shared_string_functions
--------------------------------------------
LIBRARY MODULE NAME: floats
---------------------------
floats {
@@ -581,6 +586,7 @@ floats {
FREADS32 () -> clobbers (A,X,Y)
FREADSA (byte value @A) -> clobbers (A,X,Y) = $bc3c
FREADSTR (ubyte length @A) -> clobbers (A,X,Y) = $b7b5
FREADU24AXY (ubyte lo @A, ubyte mid @X, ubyte hi @Y) -> clobbers (A,X,Y) -> float @FAC1
FREADUS32 () -> clobbers (A,X,Y)
FREADUY (ubyte value @Y) -> clobbers (A,X,Y) = $b3a2
FSUB (uword mflpt @AY) -> clobbers (A,X,Y) = $b850
@@ -635,6 +641,7 @@ floats {
secant (float value) -> float
sin (float angle) -> float
tan (float value) -> float
time () -> float @FAC1
tostr (float value @FAC1) -> clobbers (X) -> str @AY
}
@@ -1,5 +1,5 @@
Prog8 compiler v10.4 by Irmen de Jong (irmen@razorvine.net)
Prog8 compiler v10.4.1 by Irmen de Jong (irmen@razorvine.net)
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
Compiling program import-all-cx16.p8
@@ -86,7 +86,7 @@ diskio {
load_raw (uword filenameptr, uword startaddress) -> uword
load_size (ubyte startbank, uword startaddress, uword endaddress) -> uword
mkdir (str name)
relabel (str name)
relabel (str name)
rename (uword oldfileptr, uword newfileptr)
reset_read_channel ()
reset_write_channel ()
@@ -258,6 +258,7 @@ cx16 {
const ubyte EXTAPI_getlfs
const ubyte EXTAPI_iso_cursor_char
const ubyte EXTAPI_joystick_ps2_keycodes
const ubyte EXTAPI_kbd_leds
const ubyte EXTAPI_led_update
const ubyte EXTAPI_mouse_set_position
const ubyte EXTAPI_mouse_sprite_offset
@@ -566,6 +567,7 @@ cx16 {
mouse_get (ubyte zdataptr @X) -> ubyte @A, byte @X = $ff6b
mouse_get_sprite_offset () -> clobbers (A,X,Y) -> word @R0, word @R1
mouse_pos () -> ubyte @A, uword @R0, uword @R1, byte @X
mouse_present () -> bool
mouse_scan () -> clobbers (A,X,Y) = $ff71
mouse_set_pos (uword xpos @R0, uword ypos @R1) -> clobbers (X)
mouse_set_sprite_offset (word xoffset @R0, word yoffset @R1) -> clobbers (A,X,Y)
@@ -796,7 +798,9 @@ emudbg {
console_value1 (ubyte value)
console_value2 (ubyte value)
console_write (str isoString)
cpu_cycles () -> uword @R0, uword @R1
is_emulator () -> bool
reset_cpu_cycles ()
}
@@ -830,6 +834,7 @@ floats {
FPWR (uword mflpt @AY) -> clobbers (A,X,Y) = $fe36
FPWRT () -> clobbers (A,X,Y) = $fe39
FREADSA (byte value @A) -> clobbers (A,X,Y)
FREADU24AXY (ubyte lo @A, ubyte mid @X, ubyte hi @Y) -> clobbers (A,X,Y) -> float @FAC1
FREADUY (ubyte value @Y)
FSUB (uword mflpt @AY) -> clobbers (A,X,Y) = $fe12
FSUBT () -> clobbers (A,X,Y) = $fe15
@@ -888,6 +893,7 @@ floats {
secant (float value) -> float
sin (float angle) -> float
tan (float value) -> float
time () -> float @FAC1
tostr (float value @FAC1) -> clobbers (X) -> str @AY
}
@@ -945,9 +951,11 @@ graphics {
disable_bitmap_mode ()
disc (uword xcenter, ubyte ycenter, ubyte radius)
enable_bitmap_mode ()
filled_oval (uword xcenter, ubyte ycenter, uword h_radius, ubyte v_radius)
fillrect (uword xx, uword yy, uword width, uword height)
horizontal_line (uword xx, uword yy, uword length)
line (uword x1, ubyte y1, uword x2, ubyte y2)
oval (uword xcenter, ubyte ycenter, uword h_radius, ubyte v_radius)
plot (uword plotx @R0, uword ploty @R1) -> clobbers (A,X,Y)
rect (uword xx, uword yy, uword width, uword height)
vertical_line (uword xx, uword yy, uword height)
@@ -1002,8 +1010,8 @@ monogfx {
ubyte mode
uword width
circle (uword xcenter, uword ycenter, ubyte radius, bool draw)
clear_screen (ubyte color)
cs_innerloop640 (ubyte color @A) -> clobbers (Y)
clear_screen (bool draw)
cs_innerloop640 (bool draw @A) -> clobbers (Y)
disc (uword xcenter, uword ycenter, ubyte radius, bool draw)
drawmode (ubyte dm)
fill (uword x, uword y, bool draw)
@@ -1104,6 +1112,8 @@ sprites {
set_palette_offset (ubyte spritenum, ubyte offset)
setx (ubyte spritenum, word xpos)
sety (ubyte spritenum, word ypos)
show (ubyte spritenum)
zdepth (ubyte spritenum, ubyte depth)
}
@@ -1,5 +1,5 @@
Prog8 compiler v10.4 by Irmen de Jong (irmen@razorvine.net)
Prog8 compiler v10.4.1 by Irmen de Jong (irmen@razorvine.net)
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
Compiling program import-all-pet32.p8
@@ -1,5 +1,5 @@
Prog8 compiler v10.4 by Irmen de Jong (irmen@razorvine.net)
Prog8 compiler v10.4.1 by Irmen de Jong (irmen@razorvine.net)
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
Compiling program import-all-virtual.p8
@@ -334,7 +334,7 @@ monogfx {
ubyte mode
uword width
circle (uword xcenter, uword ycenter, ubyte radius, bool draw)
clear_screen (ubyte color)
clear_screen (bool draw)
disc (uword xcenter, uword ycenter, ubyte radius, bool draw)
drawmode (ubyte dm)
fill (uword x, uword y, bool draw)
+6 -14
View File
@@ -65,8 +65,12 @@ For normal use, the ``installDist`` task should suffice and after succesful comp
but the actual prog8 compiler should run on all operating systems that provide a java runtime (version 11 or newer).
If you do have trouble building or running the compiler on your operating system, please let me know!
To successfully build and debug in IDEA, you have to manually generate the Antlr-parser classes first.
The easiest way to do this is the following:
To successfully build and debug in IDEA, you have to do two things manually first:
1. you have to generate the buildversion file, do this with the shell command: ``gradle createVersionFile``
2. manually generate the Antlr-parser classes first.
The easiest way to build the parser classes this is the following:
1. make sure you have the Antlr4 plugin installed in IDEA
2. right click the grammar file Prog8ANTLR.g4 in the parser project, and choose "Generate Antlr Recognizer" from the menu.
@@ -155,10 +159,6 @@ One or more .p8 module files
Don't perform any code optimizations.
Useful for debugging or faster compilation cycles.
``-nostrictbool``
Relax the strict boolean type checks: bytes and booleans can be interchanged again without explicit type casts.
*This option will likely disappear in a future prog8 version, so you may want to prepare for that in your code!*
``-optfloatx``
Also optimize float expressions if optimizations are enabled.
Warning: can increase program size significantly if a lot of floating point expressions are used.
@@ -382,14 +382,6 @@ Also avoid using 64tass' built-in function or type names as symbols in your inli
The 64tass manual contains `a list of those <https://tass64.sourceforge.net/#functions>`_.
Community
---------
Most of the development on Prog8 and the use of it is currently centered around
the `Commander X16 <https://www.commanderx16.com/>`_ retro computer.
Their `Discord server <https://discord.gg/nS2PqEC>`_ contains a small channel
dedicated to Prog8. Other than that, use the issue tracker on github.
Examples
--------
+9
View File
@@ -38,6 +38,15 @@ This is free software, as defined in the GNU GPL 3.0 (https://www.gnu.org/licens
are excluded from this particular license: you can do with those *whatever you want*.
This means, for instance, that you can use the Prog8 compiler to create commercial software as long as you only sell *the actual resulting program*.
Community
^^^^^^^^^
Most of the development on Prog8 and the use of it is currently centered around
the `Commander X16 <https://www.commanderx16.com/>`_ retro computer.
However, the other compiler targets are also still worked on, most notably the C64 target where it all started with!
The X16 `Discord server <https://discord.gg/nS2PqEC>`_ contains a small channel
dedicated to Prog8. Besides that, you can use the issue tracker on github for questions or problems or contributions.
Want to buy me a coffee or a pizza perhaps?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This project was created over the last couple of years by dedicating thousands of hours of my free time to it, to make it the best I possibly can.
+10 -1
View File
@@ -903,7 +903,7 @@ to see what's in there.
gfx2 (cx16 only)
-----------------
Full-screen multicolor bitmap graphics routines, available on the Cx16 machine only.
Same interface as monogfx, but for color screens. For 1 bpp monochrome screens, use monogfx.
Same interface as monogfx, but for color screens. For 1 bpp monochrome screens: use monogfx.
- multiple full-screen bitmap color resolutions
- clearing screen, switching screen mode, also back to text mode
@@ -916,6 +916,15 @@ Read the `gfx2 source code <https://github.com/irmen/prog8/tree/master/compiler/
to see what's in there.
gfx_lores (cx16 only)
----------------------
Heavily optimized graphics routines for just the single screen mode: lores 320*240, 256c (8bpp)
This is screen mode 1 from the gfx2 module (and also compatible with X16's basic screen mode 128).
Read the `gfx_lores source code <https://github.com/irmen/prog8/tree/master/compiler/res/prog8lib/cx16/gfx_lores.p8>`_
to see what's in there.
palette (cx16 only)
--------------------
Available for the Cx16 target. Various routines to set the display color palette.
+6
View File
@@ -518,6 +518,12 @@ Loops
-----
The *for*-loop is used to let a variable iterate over a range of values. Iteration is done in steps of 1, but you can change this.
.. sidebar::
Optimization
Usually a loop in descending order downto 0 or 1, produces more efficient assembly code than the same loop in ascending order.
The loop variable must be declared separately as byte or word earlier, so that you can reuse it for multiple occasions.
Iterating with a floating point variable is not supported. If you want to loop over a floating-point array, use a loop with an integer index variable instead.
If the from value is already outside of the loop range, the whole for loop is skipped.
+2 -2
View File
@@ -75,8 +75,8 @@ Directives
during normal system operation.
When the program exits, it simply returns to the BASIC ready prompt.
- style ``full`` -- claim the whole ZP for variables for the program, overwriting everything,
except the few addresses mentioned above that are used by the system's IRQ routine.
Even though the default IRQ routine is still active, it is impossible to use most BASIC and Kernal ROM routines.
except for a few addresses that are used by the system's IRQ handler.
Even though that default IRQ handler is still active, it is impossible to use most BASIC and Kernal ROM routines.
This includes many floating point operations and several utility routines that do I/O, such as ``print``.
This option makes programs smaller and faster because even more variables can
be stored in the ZP (which allows for more efficient assembly code).
Loaded 100 of 123 files, more files were not shown because too many files have changed in this diff. Show more